From 162d4a1d0514fa7e6982b2e809219c9e58e2719c Mon Sep 17 00:00:00 2001 From: Rikitav Date: Mon, 9 Mar 2026 03:22:23 +0400 Subject: [PATCH] * StateKeeper system rework * StateKeepers are deleted * Added IStateMachine and IStateStorage * Added IStateStorage as provider to containers and handlers * Added default IStateStorage implementation * Added default StateMachine * minor bug fixes --- ...plicitHandlerBuilderExtensionsGenerator.cs | 21 +- .../MemberDeclarationSyntaxExtensions.cs | 38 - docs/Telegrator.Hosting.xml | 2 +- docs/Telegrator.xml | 1380 ++--------------- .../Telegrator.Hosting.Web.csproj | 2 +- .../Polling/HostUpdateRouter.cs | 4 +- .../Telegrator.Hosting.csproj | 2 +- src/Telegrator/Annotations/StateAttribute.cs | 21 + .../StateKeeping/EnumStateAttribute.cs | 44 - .../StateKeeping/NumericStateAttribute.cs | 43 - .../Annotations/StateKeeping/SpecialState.cs | 21 - .../StateKeeping/StringStateAttribute.cs | 43 - .../{Targetted => }/WelcomeAttribute.cs | 2 +- .../Attributes/StateKeeperAttribute.cs | 86 - .../Attributes/StateKeeperAttributeBase.cs | 35 - .../Descriptors/DefaultCustomDescriptors.cs | 2 +- .../Descriptors/DescribedHandlerDescriptor.cs | 9 + .../Core/Descriptors/HandlerDescriptor.cs | 2 +- .../Core/Descriptors/HandlerInspector.cs | 7 +- .../Core/Filters/FilterExecutionContext.cs | 16 +- .../Core/Handlers/AbstractUpdateHandler.cs | 6 + .../Handlers/Building/HandlerBuilderBase.cs | 38 +- .../Building/IAwaiterHandlerBuilder.cs | 6 +- .../Core/Handlers/Building/IHandlerBuilder.cs | 32 +- .../Core/Handlers/Building/StateKeepFilter.cs | 80 - .../Core/Handlers/EmptyHandlerContainer.cs | 4 + .../Core/Handlers/IHandlerContainer.cs | 6 + .../Core/Handlers/UpdateHandlerBase.cs | 2 +- src/Telegrator/Core/IPollingProvider.cs | 19 - src/Telegrator/Core/IUpdateRouter.cs | 18 +- .../Core/StateKeeping/IStateKeyResolver.cs | 18 - .../Core/StateKeeping/StateKeeperBase.cs | 150 -- .../Core/States/IStateKeyResolver.cs | 16 + src/Telegrator/Core/States/IStateMachine.cs | 9 + src/Telegrator/Core/States/IStateStorage.cs | 8 + src/Telegrator/Filters/StateKeyFilter.cs | 33 +- .../Building/AwaiterHandlerBuilder.cs | 24 +- .../Handlers/Building/TypesExtensions.cs | 150 +- src/Telegrator/Handlers/CommandHandler.cs | 3 + src/Telegrator/Handlers/HandlerContainer.cs | 16 +- .../DefaultRouterExceptionHandler.cs | 2 +- src/Telegrator/Mediation/UpdateRouter.cs | 13 +- .../StateKeeping/ArrayStateKeeper.cs | 59 - .../StateKeeping/EnumStateKeeper.cs | 75 - .../StateKeeping/NumericStateKeeper.cs | 92 -- .../StateKeeping/SenderIdResolver.cs | 21 - .../StateKeeping/StringStateKeeper.cs | 87 -- .../ChatIdResolver.cs | 10 +- src/Telegrator/States/DefaultStateStorage.cs | 43 + src/Telegrator/States/EnumStateMachine.cs | 62 + src/Telegrator/States/SenderIdResolver.cs | 20 + src/Telegrator/States/StateMachine.cs | 63 + src/Telegrator/Telegrator.csproj | 6 +- src/Telegrator/TelegratorClient.cs | 4 +- src/Telegrator/TypesExtensions.cs | 57 +- tests/Telegrator.Tests/Filters/FilterTests.cs | 12 +- 56 files changed, 563 insertions(+), 2481 deletions(-) delete mode 100644 dev/Telegrator.RoslynGenerators/RoslynExtensions/MemberDeclarationSyntaxExtensions.cs create mode 100644 src/Telegrator/Annotations/StateAttribute.cs delete mode 100644 src/Telegrator/Annotations/StateKeeping/EnumStateAttribute.cs delete mode 100644 src/Telegrator/Annotations/StateKeeping/NumericStateAttribute.cs delete mode 100644 src/Telegrator/Annotations/StateKeeping/SpecialState.cs delete mode 100644 src/Telegrator/Annotations/StateKeeping/StringStateAttribute.cs rename src/Telegrator/Annotations/{Targetted => }/WelcomeAttribute.cs (94%) delete mode 100644 src/Telegrator/Attributes/StateKeeperAttribute.cs delete mode 100644 src/Telegrator/Core/Attributes/StateKeeperAttributeBase.cs delete mode 100644 src/Telegrator/Core/Handlers/Building/StateKeepFilter.cs delete mode 100644 src/Telegrator/Core/IPollingProvider.cs delete mode 100644 src/Telegrator/Core/StateKeeping/IStateKeyResolver.cs delete mode 100644 src/Telegrator/Core/StateKeeping/StateKeeperBase.cs create mode 100644 src/Telegrator/Core/States/IStateKeyResolver.cs create mode 100644 src/Telegrator/Core/States/IStateMachine.cs create mode 100644 src/Telegrator/Core/States/IStateStorage.cs delete mode 100644 src/Telegrator/StateKeeping/ArrayStateKeeper.cs delete mode 100644 src/Telegrator/StateKeeping/EnumStateKeeper.cs delete mode 100644 src/Telegrator/StateKeeping/NumericStateKeeper.cs delete mode 100644 src/Telegrator/StateKeeping/SenderIdResolver.cs delete mode 100644 src/Telegrator/StateKeeping/StringStateKeeper.cs rename src/Telegrator/{StateKeeping => States}/ChatIdResolver.cs (69%) create mode 100644 src/Telegrator/States/DefaultStateStorage.cs create mode 100644 src/Telegrator/States/EnumStateMachine.cs create mode 100644 src/Telegrator/States/SenderIdResolver.cs create mode 100644 src/Telegrator/States/StateMachine.cs diff --git a/dev/Telegrator.RoslynGenerators/ImplicitHandlerBuilderExtensionsGenerator.cs b/dev/Telegrator.RoslynGenerators/ImplicitHandlerBuilderExtensionsGenerator.cs index 54af759..1d993ac 100644 --- a/dev/Telegrator.RoslynGenerators/ImplicitHandlerBuilderExtensionsGenerator.cs +++ b/dev/Telegrator.RoslynGenerators/ImplicitHandlerBuilderExtensionsGenerator.cs @@ -5,10 +5,6 @@ using System.Collections.Immutable; using System.Text; using Telegrator.RoslynGenerators.RoslynExtensions; -#if DEBUG -using System.Diagnostics; -#endif - namespace Telegrator.RoslynGenerators; [Generator(LanguageNames.CSharp)] @@ -72,6 +68,9 @@ public class ImplicitHandlerBuilderExtensionsGenerator : IIncrementalGenerator if (className == "FilterAnnotation") continue; + if (className == "StateAttribute") + continue; + MethodDeclarationSyntax? targeter = classDeclaration.Members.OfType().SingleOrDefault(IsTargeterMethod); if (targeter != null) { @@ -140,16 +139,15 @@ public class ImplicitHandlerBuilderExtensionsGenerator : IIncrementalGenerator { ClassDeclarationSyntax extensionsClass = SyntaxFactory.ClassDeclaration("HandlerBuilderExtensions") .WithModifiers(Modifiers(SyntaxKind.PublicKeyword, SyntaxKind.StaticKeyword, SyntaxKind.PartialKeyword)) - .AddMembers([.. targetters.Values, .. extensions]) - .DecorateType(1); + .AddMembers([.. targetters.Values, .. extensions]); NamespaceDeclarationSyntax namespaceDeclaration = SyntaxFactory.NamespaceDeclaration(SyntaxFactory.ParseName("Telegrator")) - .WithMembers([extensionsClass]) - .Decorate(); + .WithMembers([extensionsClass]); CompilationUnitSyntax compilationUnit = SyntaxFactory.CompilationUnit() .WithUsings([.. usings]) - .WithMembers([namespaceDeclaration]); + .WithMembers([namespaceDeclaration]) + .NormalizeWhitespace(); context.AddSource("GeneratedHandlerBuilderExtensions.cs", compilationUnit.ToFullString()); } @@ -175,7 +173,7 @@ public class ImplicitHandlerBuilderExtensionsGenerator : IIncrementalGenerator if (targetterMethod.ExpressionBody != null) method = method.WithExpressionBody(targetterMethod.ExpressionBody).WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken)); - return method.DecorateMember(2); + return method; } private static MethodDeclarationSyntax GeneratedExtensionsMethod(ClassDeclarationSyntax classDeclaration, ParameterListSyntax methodParameters, ArgumentListSyntax invokerArguments, MethodDeclarationSyntax targetterMethod) @@ -204,11 +202,10 @@ public class ImplicitHandlerBuilderExtensionsGenerator : IIncrementalGenerator MethodDeclarationSyntax method = SyntaxFactory.MethodDeclaration(returnType, identifier) .WithParameterList(parameters) - .WithBody(body.DecorateBlock(2)) + .WithBody(body) .WithTypeParameterList(typeParameters) .WithModifiers(Modifiers(SyntaxKind.PublicKeyword, SyntaxKind.StaticKeyword)) .WithConstraintClauses([typeParameterConstraint]) - .DecorateMember(2) .WithLeadingTrivia(xmlDoc); return method; diff --git a/dev/Telegrator.RoslynGenerators/RoslynExtensions/MemberDeclarationSyntaxExtensions.cs b/dev/Telegrator.RoslynGenerators/RoslynExtensions/MemberDeclarationSyntaxExtensions.cs deleted file mode 100644 index 557b9c9..0000000 --- a/dev/Telegrator.RoslynGenerators/RoslynExtensions/MemberDeclarationSyntaxExtensions.cs +++ /dev/null @@ -1,38 +0,0 @@ -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.CSharp.Syntax; - -namespace Telegrator.RoslynGenerators.RoslynExtensions -{ - public static class MemberDeclarationSyntaxExtensions - { - private static SyntaxTrivia TabulationTrivia => SyntaxFactory.SyntaxTrivia(SyntaxKind.WhitespaceTrivia, "\t"); - private static SyntaxTrivia WhitespaceTrivia => SyntaxFactory.SyntaxTrivia(SyntaxKind.WhitespaceTrivia, " "); - private static SyntaxTrivia NewLineTrivia => SyntaxFactory.SyntaxTrivia(SyntaxKind.EndOfLineTrivia, "\n"); - - public static SyntaxTokenList Decorate(this SyntaxTokenList tokens) - => new SyntaxTokenList(tokens.Select(token => token.WithoutTrivia().WithTrailingTrivia(WhitespaceTrivia)).ToArray()); - - public static BlockSyntax DecorateBlock(this BlockSyntax block, int times) => block - .WithStatements([.. block.Statements.Select(statement => statement.DecorateStatememnt(times + 1))]) - .WithOpenBraceToken(SyntaxFactory.Token(SyntaxKind.OpenBraceToken).WithLeadingTrivia(WhitespaceTrivia).WithTrailingTrivia(NewLineTrivia)) - .WithCloseBraceToken(SyntaxFactory.Token(SyntaxKind.CloseBraceToken).WithLeadingTrivia(TabulationTrivia.Repeat(times)).WithTrailingTrivia(NewLineTrivia)); - - public static T DecorateStatememnt(this T statememnt, int times) where T : StatementSyntax => statememnt - .WithoutTrivia().WithLeadingTrivia(TabulationTrivia.Repeat(times)).WithTrailingTrivia(NewLineTrivia); - - public static T DecorateMember(this T typeDeclaration, int times) where T : MemberDeclarationSyntax => typeDeclaration - .WithoutTrivia().WithLeadingTrivia(TabulationTrivia.Repeat(times)).WithTrailingTrivia(NewLineTrivia); - - public static NamespaceDeclarationSyntax Decorate(this NamespaceDeclarationSyntax namespaceDeclaration) => namespaceDeclaration - .WithName(namespaceDeclaration.Name.WithoutTrivia().WithLeadingTrivia(WhitespaceTrivia)) - .WithOpenBraceToken(SyntaxFactory.Token(SyntaxKind.OpenBraceToken).WithLeadingTrivia(NewLineTrivia).WithTrailingTrivia(NewLineTrivia)) - .WithCloseBraceToken(SyntaxFactory.Token(SyntaxKind.CloseBraceToken)); - - public static T DecorateType(this T typeDeclaration, int times = 1) where T : TypeDeclarationSyntax => (T)typeDeclaration - .WithoutTrivia().WithLeadingTrivia(TabulationTrivia.Repeat(times)) - .WithIdentifier(typeDeclaration.Identifier.WithoutTrivia().WithLeadingTrivia(WhitespaceTrivia).WithTrailingTrivia(NewLineTrivia)) - .WithOpenBraceToken(SyntaxFactory.Token(SyntaxKind.OpenBraceToken).WithLeadingTrivia(TabulationTrivia.Repeat(times)).WithTrailingTrivia(NewLineTrivia)) - .WithCloseBraceToken(SyntaxFactory.Token(SyntaxKind.CloseBraceToken).WithLeadingTrivia(TabulationTrivia.Repeat(times)).WithTrailingTrivia(NewLineTrivia)); - } -} diff --git a/docs/Telegrator.Hosting.xml b/docs/Telegrator.Hosting.xml index f576f08..2431094 100644 --- a/docs/Telegrator.Hosting.xml +++ b/docs/Telegrator.Hosting.xml @@ -203,7 +203,7 @@ of this router - + diff --git a/docs/Telegrator.xml b/docs/Telegrator.xml index 88ee2c8..ce8c582 100644 --- a/docs/Telegrator.xml +++ b/docs/Telegrator.xml @@ -769,132 +769,15 @@ - - - Attribute for managing enum-based states in Telegram bot handlers. - Provides a convenient way to associate enum values with state management functionality. - - The enum type to be used for state management. - - - - Initializes a new instance of the EnumStateAttribute with a special state and custom key resolver. - - The special state to be managed. - The resolver for extracting keys from updates. - - - - Initializes a new instance of the EnumStateAttribute with a specific enum state and custom key resolver. - - The specific enum state to be managed. - The resolver for extracting keys from updates. - - - - Initializes a new instance of the EnumStateAttribute with a special state and default sender ID resolver. - - The special state to be managed. - - - - Initializes a new instance of the EnumStateAttribute with a specific enum state and default sender ID resolver. - - The specific enum state to be managed. - - - - Attribute for associating a handler or method with a numeric (integer) state keeper. - Provides constructors for flexible state and key resolver configuration. - - - - - Initializes the attribute with a special state and a custom key resolver. - - The special state to associate - The key resolver for state keeping - - - - Initializes the attribute with a specific numeric state and a custom key resolver. - - The integer state to associate - The key resolver for state keeping - - - - Initializes the attribute with a special state and the default sender ID resolver. - - The special state to associate - - - - Initializes the attribute with a specific numeric state and the default sender ID resolver. - - The integer state to associate - - - - Represents special states for state keeping logic. - - - - - No special state. - - - - - Indicates that no state is present. - - - - - Indicates that any state is acceptable. - - - - - Attribute for associating a handler or method with a string-based state keeper. - Provides various constructors for flexible state and key resolver configuration. - - - - - Initializes the attribute with a special state and a custom key resolver. - - The special state to associate - The key resolver for state keeping - - - - Initializes the attribute with a specific state and a custom key resolver. - - The string state to associate - The key resolver for state keeping - - - - Initializes the attribute with a special state and the default sender ID resolver. - - The special state to associate - - - - Initializes the attribute with a specific state and the default sender ID resolver. - - The string state to associate - - + Attribute for filtering message with command "start" in bot's private chats. Allows handlers to respond to "welcome" bot commands. - + - Creates new instance of + Creates new instance of @@ -997,56 +880,6 @@ NOT modifier. The inverse of this filter should match. - - - Abstract attribute for associating a handler or method with a state keeper. - Provides logic for state-based filtering and state management. - - The type of the key used for state keeping (e.g., chat ID). - The type of the state value (e.g., string, int). - The type of the state keeper implementation. - - - - Gets or sets the singleton instance of the state keeper for this attribute type. - - - - - Gets the default state value of this statekeeper. - - - - - Gets the state value associated with this attribute instance. - - - - - Gets the special state mode for this attribute instance. - - - - - Initializes the attribute with a specific state and a custom key resolver. - - The state value to associate - The key resolver for state keeping - - - - Initializes the attribute with a special state and a custom key resolver. - - The special state mode - The key resolver for state keeping - - - - Determines whether the current update context passes the state filter. - - The filter execution context - True if the state matches the filter; otherwise, false. - Abstract base attribute for defining update filters for a specific type of update target. @@ -1160,28 +993,6 @@ Disposes the instance and stops the continuous action. - - - Sets the state in which the can be executed - - - - - - - - Creates a new instance - - - - - - - Realizes a for validation of the current in the polling routing - - - - Defines the to validation for entry into execution of the @@ -1303,6 +1114,11 @@ The awaiting provider to fetch new updates inside handler + + + The state storage to handling state machines + + The Telegram bot client used for this handler. @@ -1343,13 +1159,14 @@ The final execution result. - + Initializes a new instance of the class. The descriptor from which this handler was described. The update router. The awaiting provider. + The state storage. The Telegram bot client. The handler instance. The filter execution context. @@ -2115,6 +1932,11 @@ The type of the input for the filter. + + + Gets the for the current context. + + Gets the for the current context. @@ -2145,20 +1967,22 @@ Gets the input object for the filter. - + Initializes a new instance of the class with all parameters. + The router, that invoked filter. The bot info. The update. The input object. The additional data dictionary. The list of completed filters. - + Initializes a new instance of the class with default data and filters. + The router, that invoked filter. The bot info. The update. The input object. @@ -2256,6 +2080,11 @@ Provider for awaiting asynchronous operations. + + + Storage of bot states. + + Initializes a new instance and checks that the update type matches . @@ -2460,26 +2289,13 @@ The filters to add. The builder instance. - + Sets a state keeper for the handler using a specific state and key resolver. - The type of the key. - The type of the state. - The type of the state keeper. - The state value. - The key resolver. - The builder instance. - - - - Sets a state keeper for the handler using a special state and key resolver. - - The type of the key. - The type of the state. - The type of the state keeper. - The special state value. - The key resolver. + The key resolver. + The state value. + The state value. The builder instance. @@ -2506,11 +2322,11 @@ The type of update to await. - + Awaits an update using the specified key resolver and cancellation token. - The to resolve the key. + The to resolve the key. The cancellation token. A representing the awaited update. @@ -2562,26 +2378,13 @@ The filters to add. The builder instance. - + Sets a state keeper for the handler using a specific state and key resolver. - The type of the key. - The type of the state. - The type of the state keeper. - The state value. - The key resolver. - The builder instance. - - - - Sets a state keeper for the handler using a special state and key resolver. - - The type of the key. - The type of the state. - The type of the state keeper. - The special state value. - The key resolver. + The key resolver. + The state value. + The state value. The builder instance. @@ -2614,50 +2417,6 @@ The delegate to execute the handler logic. - - - Filter for state keeping logic, allowing filtering based on state and special state conditions. - - The type of the key for state resolution. - The type of the state. - The type of the state keeper. - - - - Gets or sets the state keeper instance. - - - - - Gets the state value for this filter. - - - - - Gets the special state value for this filter. - - - - - Initializes a new instance of the class with a specific state. - - The state value. - The key resolver. - - - - Initializes a new instance of the class with a special state. - - The special state value. - The key resolver. - - - - Determines whether the filter can pass for the given context based on state logic. - - The filter execution context. - True if the filter passes; otherwise, false. - Delegate for validating an update in a filter context. @@ -2708,6 +2467,9 @@ + + + Represents a token that tracks the lifetime of a handler instance. @@ -2777,6 +2539,11 @@ Gets the for awaiting operations. + + + Gets the for state managment. + + Factory interface for creating handler containers. @@ -3001,22 +2768,6 @@ True if the provider is empty; otherwise, false. - - - Interface for polling providers that manage both regular and awaiting handlers. - Provides access to handlers for different types of update processing during polling operations. - - - - - Gets the that manages handlers for polling. - - - - - Gets the that manages awaiting handlers for polling. - - Interface for handling exceptions that occur during update routing operations. @@ -3108,6 +2859,21 @@ Gets the that manages handler execution. + + + Gets the that manages handlers for polling. + + + + + Gets the that manages awaiting handlers for polling. + + + + + Gets the that manages storing of handlers state. + + Gets or sets the for handling exceptions. @@ -3118,105 +2884,18 @@ Default hand;er container factory - + Defines a resolver for extracting a key from an update for state keeping purposes. - The type of the key. - + Resolves a key from the specified . The update to resolve the key from. The resolved key. - - - Base class for managing state associated with updates and keys. - - The type of the key used for state resolution. - The type of the state. - - - - Gets or sets the key resolver used to resolve keys from updates. - - - - - Gets the default state value. - - - - - Sets the state for the specified update. - - The update to use as a key source. - The new state value. - - - - Gets the state for the specified update. - - The update to use as a key source. - The state value. - - - - Tries to get the state for the specified update. - - The update to use as a key source. - When this method returns, contains the state value if found; otherwise, the default value. - True if the state was found; otherwise, false. - - - - Determines whether a state exists for the specified update. - - The update to use as a key source. - True if the state exists; otherwise, false. - - - - Creates a state for the specified update using the default state value. - - The update to use as a key source. - - - - Deletes the state for the specified update. - - The update to use as a key source. - - - - Moves the state forward for the specified update. - - The update to use as a key source. - - - - Moves the state backward for the specified update. - - The update to use as a key source. - - - - Moves the state forward for the specified current state and key. - - The current state value. - The key. - The new state value. - - - - Moves the state backward for the specified current state and key. - - The current state value. - The key. - The new state value. - Enumeration of dice types supported by Telegram. @@ -4676,20 +4355,20 @@ The filter execution context. True if the regex matches; otherwise, false. - + Filters updates by comparing a resolved state key with a target key. - The type of the key used for state resolution. + The type of the key resolver used to get state key. + The type of the key used for state resolution. - + - Initializes a new instance of the class. + Initializes a new instance of the class. - The key resolver to extract the key from the update. The target key to compare with. - + @@ -4806,7 +4485,7 @@ The cancellation token to cancel the wait operation. The awaited update of type TUpdate. - + Awaits for an update of the specified type using a custom state key resolver. @@ -4896,11 +4575,8 @@ - - - - - + + @@ -4924,86 +4600,6 @@ The filters to add. The builder instance. - - - Sets a numeric state keeper with a custom key resolver. - - The type of the handler builder. - The handler builder. - The numeric state value. - The key resolver for the state. - The handler builder for method chaining. - - - - Sets a numeric state keeper with a special state and custom key resolver. - - The type of the handler builder. - The handler builder. - The special state value. - The key resolver for the state. - The handler builder for method chaining. - - - - Sets a numeric state keeper with the default sender ID resolver. - - The type of the handler builder. - The handler builder. - The numeric state value. - The handler builder for method chaining. - - - - Sets a numeric state keeper with a special state and the default sender ID resolver. - - The type of the handler builder. - The handler builder. - The special state value. - The handler builder for method chaining. - - - - Sets an enum state keeper with a custom key resolver. - - The type of the handler builder. - The type of the enum state. - The handler builder. - The enum state value. - The key resolver for the state. - The handler builder for method chaining. - - - - Sets an enum state keeper with a special state and custom key resolver. - - The type of the handler builder. - The type of the enum state. - The handler builder. - The special state value. - The key resolver for the state. - The handler builder for method chaining. - - - - Sets an enum state keeper with the default sender ID resolver. - - The type of the handler builder. - The type of the enum state. - The handler builder. - The enum state value. - The handler builder for method chaining. - - - - Sets an enum state keeper with a special state and the default sender ID resolver. - - The type of the handler builder. - The type of the enum state. - The handler builder. - The special state value. - The handler builder for method chaining. - Attribute that marks a handler to process callback query updates. @@ -5381,13 +4977,16 @@ + + + Initializes new instance of - + Initializes new instance of @@ -6324,6 +5923,9 @@ + + + @@ -6336,12 +5938,13 @@ - + Initializes a new instance of the class. The provider for regular handlers. The provider for awaiting handlers. + The state storage. The bot configuration options. @@ -6931,56 +6534,13 @@ - - - Abstract base class for state keepers that manage state transitions using an array of predefined states. - Provides forward and backward navigation through a fixed sequence of states. - - The type of the key used to identify state contexts. - The type of the state values. Must be non-null. - The array of states that define the allowed state sequence. - - - - Abstract base class for state keepers that manage state transitions using an array of predefined states. - Provides forward and backward navigation through a fixed sequence of states. - - The type of the key used to identify state contexts. - The type of the state values. Must be non-null. - The array of states that define the allowed state sequence. - - - - The array of states that defines the allowed state sequence for navigation. - - - - - Moves to the previous state in the array sequence. - - The current state to move backward from. - The key parameter (unused in this implementation). - The previous state in the array sequence. - Thrown when the current state is not found in the array. - Thrown when trying to move backward from the first state. - - - - Moves to the next state in the array sequence. - - The current state to move forward from. - The key parameter (unused in this implementation). - The next state in the array sequence. - Thrown when the current state is not found in the array. - Thrown when trying to move forward from the last state. - - + Resolves chat ID from Telegram updates for state management purposes. Extracts the chat identifier from various types of updates to provide a consistent key for state operations. - + Resolves the chat ID from a Telegram update. @@ -6988,182 +6548,32 @@ The chat ID as a long value. Thrown when the update does not contain a valid chat ID. - + - State keeper implementation for enum-based states. + State machine implementation for enum-based states. Automatically creates an array of all enum values for state navigation. The enum type to be used for state management. - - - State keeper implementation for enum-based states. - Automatically creates an array of all enum values for state navigation. - - The enum type to be used for state management. + + - - - Gets the default state, which is the first value in the enum. - + + - - - Extension methods for working with enum-based states in handler containers. - Provides convenient methods for state management operations. - - - Provides extension methods for managing numeric states in handler containers. - - - Provides extension methods for managing string states in handler containers. - + + - - - Gets the enum state keeper for the specified enum type. - - The enum type to get the state keeper for. - The handler container (unused parameter for extension method syntax). - The enum state keeper instance. + + - - - Creates a new enum state for the current update. - - The enum type for state management. - The handler container. - - - - Deletes the enum state for the current update. - - The enum type for state management. - The handler container. - - - - Sets the enum state to a specific value for the current update. - - The enum type for state management. - The handler container. - The new state value. If null, uses the default state. - - - - Moves the enum state forward to the next value in the enum sequence. - - The enum type for state management. - The handler container. - - - - Moves the enum state backward to the previous value in the enum sequence. - - The enum type for state management. - The handler container. - - - - Gets the numeric state keeper instance associated with the handler container. - - The handler container instance - The instance - - - - Creates a new numeric state for the current update being handled. - - The handler container instance - - - - Deletes the numeric state for the current update being handled. - - The handler container instance - - - - Sets the numeric state for the current update being handled. - If the new state is null, uses the default state from the state keeper. - - The handler container instance - The new numeric state to set, or null to use default - - - - Moves the numeric state forward by incrementing the current value. - - The handler container instance - - - - Moves the numeric state backward by decrementing the current value. - - The handler container instance - - - - Gets the string state keeper instance associated with the handler container. - - The handler container instance - The instance - - - - Creates a new string state for the current update being handled. - - The handler container instance - - - - Deletes the string state for the current update being handled. - - The handler container instance - - - - Sets the string state for the current update being handled. - If the new state is null, uses the default state from the state keeper. - - The handler container instance - The new string state to set, or null to use default - - - - State keeper that manages numeric (integer) states for chat sessions. - Inherits from with long keys and int states. - Provides automatic increment/decrement functionality for state transitions. - - - - - Gets the default state value, which is 1. - - - - - Moves the numeric state backward by decrementing the current state value. - - The current numeric state value - The chat ID (unused in this implementation) - The decremented state value - - - - Moves the numeric state forward by incrementing the current state value. - - The current numeric state value - The chat ID (unused in this implementation) - The incremented state value - - + Resolves sender ID from Telegram updates for state management purposes. Extracts the sender identifier from various types of updates to provide a consistent key for state operations. - + Resolves the sender ID from a Telegram update. @@ -7171,27 +6581,6 @@ The sender ID as a long value. Thrown when the update does not contain a valid sender ID. - - - State keeper that manages string-based states for chat sessions. - - - - - State keeper that manages string-based states for chat sessions. - - - - - Gets the default state value, which is an empty string. - - - - - - - - Implementation of that provides bot information. @@ -7409,16 +6798,6 @@ The handler container. An awaiter builder for callback query updates. - - - Gets a state keeper instance for the specified types. - - The type of the state key. - The type of the state value. - The type of the state keeper. - The handler container (unused). - The state keeper instance. - Extensions methods for Awaiter Handler Builders @@ -7655,582 +7034,5 @@ - - - Adds a CallbackDataAttribute target filter to the handler builder. - - The builder type. - The handler builder. - The data. - The same builder instance. - - - - Adds a CallbackInlineIdAttribute target filter to the handler builder. - - The builder type. - The handler builder. - The inlineMessageId. - The same builder instance. - - - - Adds a CommandAlliasAttribute target filter to the handler builder. - - The builder type. - The handler builder. - The alliases. - The same builder instance. - - - - Adds a ArgumentCountAttribute target filter to the handler builder. - - The builder type. - The handler builder. - The count. - The same builder instance. - - - - Adds a ArgumentStartsWithAttribute target filter to the handler builder. - - The builder type. - The handler builder. - The content. - The comparison. - The index. - The same builder instance. - - - - Adds a ArgumentEndsWithAttribute target filter to the handler builder. - - The builder type. - The handler builder. - The content. - The comparison. - The index. - The same builder instance. - - - - Adds a ArgumentContainsAttribute target filter to the handler builder. - - The builder type. - The handler builder. - The content. - The comparison. - The index. - The same builder instance. - - - - Adds a ArgumentEqualsAttribute target filter to the handler builder. - - The builder type. - The handler builder. - The content. - The comparison. - The index. - The same builder instance. - - - - Adds a ArgumentRegexAttribute target filter to the handler builder. - - The builder type. - The handler builder. - The pattern. - The options. - The index. - The same builder instance. - - - - Adds a IsDebugEnvironmentAttribute target filter to the handler builder. - - The builder type. - The handler builder. - The same builder instance. - - - - Adds a IsReleaseEnvironmentAttribute target filter to the handler builder. - - The builder type. - The handler builder. - The same builder instance. - - - - Adds a EnvironmentVariableAttribute target filter to the handler builder. - - The builder type. - The handler builder. - The variable. - The value. - The comparison. - The same builder instance. - - - - Adds a EnvironmentVariableAttribute target filter to the handler builder. - - The builder type. - The handler builder. - The variable. - The value. - The same builder instance. - - - - Adds a EnvironmentVariableAttribute target filter to the handler builder. - - The builder type. - The handler builder. - The variable. - The same builder instance. - - - - Adds a EnvironmentVariableAttribute target filter to the handler builder. - - The builder type. - The handler builder. - The variable. - The comparison. - The same builder instance. - - - - Adds a MentionedAttribute target filter to the handler builder. - - The builder type. - The handler builder. - The same builder instance. - - - - Adds a MentionedAttribute target filter to the handler builder. - - The builder type. - The handler builder. - The offset. - The same builder instance. - - - - Adds a MentionedAttribute target filter to the handler builder. - - The builder type. - The handler builder. - The mention. - The same builder instance. - - - - Adds a MentionedAttribute target filter to the handler builder. - - The builder type. - The handler builder. - The mention. - The offset. - The same builder instance. - - - - Adds a ChatIsForumAttribute target filter to the handler builder. - - The builder type. - The handler builder. - The same builder instance. - - - - Adds a ChatIdAttribute target filter to the handler builder. - - The builder type. - The handler builder. - The id. - The same builder instance. - - - - Adds a ChatTypeAttribute target filter to the handler builder. - - The builder type. - The handler builder. - The type. - The same builder instance. - - - - Adds a ChatTypeAttribute target filter to the handler builder. - - The builder type. - The handler builder. - The flags. - The same builder instance. - - - - Adds a ChatTitleAttribute target filter to the handler builder. - - The builder type. - The handler builder. - The title. - The comparison. - The same builder instance. - - - - Adds a ChatTitleAttribute target filter to the handler builder. - - The builder type. - The handler builder. - The title. - The same builder instance. - - - - Adds a ChatUsernameAttribute target filter to the handler builder. - - The builder type. - The handler builder. - The userName. - The comparison. - The same builder instance. - - - - Adds a ChatUsernameAttribute target filter to the handler builder. - - The builder type. - The handler builder. - The userName. - The same builder instance. - - - - Adds a ChatNameAttribute target filter to the handler builder. - - The builder type. - The handler builder. - The firstName. - The lastName. - The comparison. - The same builder instance. - - - - Adds a ChatNameAttribute target filter to the handler builder. - - The builder type. - The handler builder. - The firstName. - The lastName. - The same builder instance. - - - - Adds a MessageRegexAttribute target filter to the handler builder. - - The builder type. - The handler builder. - The pattern. - The regexOptions. - The same builder instance. - - - - Adds a MessageRegexAttribute target filter to the handler builder. - - The builder type. - The handler builder. - The regex. - The same builder instance. - - - - Adds a DiceThrowedAttribute target filter to the handler builder. - - The builder type. - The handler builder. - The value. - The same builder instance. - - - - Adds a DiceThrowedAttribute target filter to the handler builder. - - The builder type. - The handler builder. - The diceType. - The value. - The same builder instance. - - - - Adds a IsAutomaticFormwardMessageAttribute target filter to the handler builder. - - The builder type. - The handler builder. - The same builder instance. - - - - Adds a IsFromOfflineMessageAttribute target filter to the handler builder. - - The builder type. - The handler builder. - The same builder instance. - - - - Adds a IsServiceMessageMessageAttribute target filter to the handler builder. - - The builder type. - The handler builder. - The same builder instance. - - - - Adds a IsTopicMessageMessageAttribute target filter to the handler builder. - - The builder type. - The handler builder. - The same builder instance. - - - - Adds a MessageHasEntityAttribute target filter to the handler builder. - - The builder type. - The handler builder. - The type. - The same builder instance. - - - - Adds a MessageHasEntityAttribute target filter to the handler builder. - - The builder type. - The handler builder. - The type. - The offset. - The length. - The same builder instance. - - - - Adds a MessageHasEntityAttribute target filter to the handler builder. - - The builder type. - The handler builder. - The type. - The content. - The stringComparison. - The same builder instance. - - - - Adds a MessageHasEntityAttribute target filter to the handler builder. - - The builder type. - The handler builder. - The type. - The offset. - The length. - The content. - The stringComparison. - The same builder instance. - - - - Adds a MeRepliedAttribute target filter to the handler builder. - - The builder type. - The handler builder. - The same builder instance. - - - - Adds a HasReplyAttribute target filter to the handler builder. - - The builder type. - The handler builder. - The replyDepth. - The same builder instance. - - - - Adds a FromReplyChainAttribute target filter to the handler builder. - - The builder type. - The handler builder. - The replyDepth. - The same builder instance. - - - - Adds a FromUsernameAttribute target filter to the handler builder. - - The builder type. - The handler builder. - The username. - The same builder instance. - - - - Adds a FromUsernameAttribute target filter to the handler builder. - - The builder type. - The handler builder. - The username. - The comparison. - The same builder instance. - - - - Adds a FromUserAttribute target filter to the handler builder. - - The builder type. - The handler builder. - The firstName. - The lastName. - The comparison. - The same builder instance. - - - - Adds a FromUserAttribute target filter to the handler builder. - - The builder type. - The handler builder. - The firstName. - The lastName. - The same builder instance. - - - - Adds a FromUserAttribute target filter to the handler builder. - - The builder type. - The handler builder. - The firstName. - The same builder instance. - - - - Adds a FromUserAttribute target filter to the handler builder. - - The builder type. - The handler builder. - The firstName. - The comparison. - The same builder instance. - - - - Adds a FromUserIdAttribute target filter to the handler builder. - - The builder type. - The handler builder. - The userId. - The same builder instance. - - - - Adds a NotFromBotAttribute target filter to the handler builder. - - The builder type. - The handler builder. - The same builder instance. - - - - Adds a FromBotAttribute target filter to the handler builder. - - The builder type. - The handler builder. - The same builder instance. - - - - Adds a FromPremiumUserAttribute target filter to the handler builder. - - The builder type. - The handler builder. - The same builder instance. - - - - Adds a TextStartsWithAttribute target filter to the handler builder. - - The builder type. - The handler builder. - The content. - The comparison. - The same builder instance. - - - - Adds a TextEndsWithAttribute target filter to the handler builder. - - The builder type. - The handler builder. - The content. - The comparison. - The same builder instance. - - - - Adds a TextContainsAttribute target filter to the handler builder. - - The builder type. - The handler builder. - The content. - The comparison. - The same builder instance. - - - - Adds a TextEqualsAttribute target filter to the handler builder. - - The builder type. - The handler builder. - The content. - The comparison. - The same builder instance. - - - - Adds a HasTextAttribute target filter to the handler builder. - - The builder type. - The handler builder. - The same builder instance. - - - - Adds a TextContainsWordAttribute target filter to the handler builder. - - The builder type. - The handler builder. - The word. - The comparison. - The startIndex. - The same builder instance. - - - - Adds a WelcomeAttribute target filter to the handler builder. - - The builder type. - The handler builder. - The onlyFirst. - The same builder instance. - diff --git a/src/Telegrator.Hosting.Web/Telegrator.Hosting.Web.csproj b/src/Telegrator.Hosting.Web/Telegrator.Hosting.Web.csproj index 946c1ef..7be0df7 100644 --- a/src/Telegrator.Hosting.Web/Telegrator.Hosting.Web.csproj +++ b/src/Telegrator.Hosting.Web/Telegrator.Hosting.Web.csproj @@ -15,7 +15,7 @@ True Telegrator.Hosting.Web - 1.16.3 + 1.16.4 Rikitav Tim4ik Rikitav Tim4ik https://github.com/Rikitav/Telegrator diff --git a/src/Telegrator.Hosting/Polling/HostUpdateRouter.cs b/src/Telegrator.Hosting/Polling/HostUpdateRouter.cs index 9258bf1..742a60f 100644 --- a/src/Telegrator.Hosting/Polling/HostUpdateRouter.cs +++ b/src/Telegrator.Hosting/Polling/HostUpdateRouter.cs @@ -4,6 +4,7 @@ using Telegram.Bot; using Telegram.Bot.Polling; using Telegram.Bot.Types; using Telegrator.Core; +using Telegrator.Core.States; using Telegrator.Mediation; namespace Telegrator.Polling @@ -20,9 +21,10 @@ namespace Telegrator.Polling public HostUpdateRouter( IHandlersProvider handlersProvider, IAwaitingProvider awaitingProvider, + IStateStorage stateStorage, IOptions options, ITelegramBotInfo botInfo, - ILogger logger) : base(handlersProvider, awaitingProvider, options.Value, botInfo) + ILogger logger) : base(handlersProvider, awaitingProvider, stateStorage, options.Value, botInfo) { Logger = logger; ExceptionHandler = new DefaultRouterExceptionHandler(HandleException); diff --git a/src/Telegrator.Hosting/Telegrator.Hosting.csproj b/src/Telegrator.Hosting/Telegrator.Hosting.csproj index 8ec7321..577613a 100644 --- a/src/Telegrator.Hosting/Telegrator.Hosting.csproj +++ b/src/Telegrator.Hosting/Telegrator.Hosting.csproj @@ -15,7 +15,7 @@ True Telegrator.Hosting - 1.16.3 + 1.16.4 Rikitav Tim4ik Rikitav Tim4ik https://github.com/Rikitav/Telegrator diff --git a/src/Telegrator/Annotations/StateAttribute.cs b/src/Telegrator/Annotations/StateAttribute.cs new file mode 100644 index 0000000..5adfdd1 --- /dev/null +++ b/src/Telegrator/Annotations/StateAttribute.cs @@ -0,0 +1,21 @@ +using Telegram.Bot.Types; +using Telegram.Bot.Types.Enums; +using Telegrator.Attributes; +using Telegrator.Core.States; +using Telegrator.Filters; + +namespace Telegrator.Annotations; + +public class StateAttribute(TValue? value) : UpdateFilterAttribute(new StateKeyFilter(value)) + where TKey : IStateKeyResolver, new() + where TValue : IEquatable +{ + public TValue? Value => value; + + public override UpdateType[] AllowedTypes => Update.AllTypes; + + public override Update? GetFilterringTarget(Update update) + { + return update; + } +} diff --git a/src/Telegrator/Annotations/StateKeeping/EnumStateAttribute.cs b/src/Telegrator/Annotations/StateKeeping/EnumStateAttribute.cs deleted file mode 100644 index e7bebed..0000000 --- a/src/Telegrator/Annotations/StateKeeping/EnumStateAttribute.cs +++ /dev/null @@ -1,44 +0,0 @@ -using Telegrator.StateKeeping; -using Telegrator.Attributes; -using Telegrator.Core.StateKeeping; - -namespace Telegrator.Annotations.StateKeeping -{ - /// - /// Attribute for managing enum-based states in Telegram bot handlers. - /// Provides a convenient way to associate enum values with state management functionality. - /// - /// The enum type to be used for state management. - public class EnumStateAttribute : StateKeeperAttribute> where TEnum : Enum - { - /// - /// Initializes a new instance of the EnumStateAttribute with a special state and custom key resolver. - /// - /// The special state to be managed. - /// The resolver for extracting keys from updates. - public EnumStateAttribute(SpecialState specialState, IStateKeyResolver keyResolver) - : base(specialState, keyResolver) { } - - /// - /// Initializes a new instance of the EnumStateAttribute with a specific enum state and custom key resolver. - /// - /// The specific enum state to be managed. - /// The resolver for extracting keys from updates. - public EnumStateAttribute(TEnum myState, IStateKeyResolver keyResolver) - : base(myState, keyResolver) { } - - /// - /// Initializes a new instance of the EnumStateAttribute with a special state and default sender ID resolver. - /// - /// The special state to be managed. - public EnumStateAttribute(SpecialState specialState) - : base(specialState, new SenderIdResolver()) { } - - /// - /// Initializes a new instance of the EnumStateAttribute with a specific enum state and default sender ID resolver. - /// - /// The specific enum state to be managed. - public EnumStateAttribute(TEnum myState) - : this(myState, new SenderIdResolver()) { } - } -} diff --git a/src/Telegrator/Annotations/StateKeeping/NumericStateAttribute.cs b/src/Telegrator/Annotations/StateKeeping/NumericStateAttribute.cs deleted file mode 100644 index 6ae2049..0000000 --- a/src/Telegrator/Annotations/StateKeeping/NumericStateAttribute.cs +++ /dev/null @@ -1,43 +0,0 @@ -using Telegrator.StateKeeping; -using Telegrator.Attributes; -using Telegrator.Core.StateKeeping; - -namespace Telegrator.Annotations.StateKeeping -{ - /// - /// Attribute for associating a handler or method with a numeric (integer) state keeper. - /// Provides constructors for flexible state and key resolver configuration. - /// - public class NumericStateAttribute : StateKeeperAttribute - { - /// - /// Initializes the attribute with a special state and a custom key resolver. - /// - /// The special state to associate - /// The key resolver for state keeping - public NumericStateAttribute(SpecialState specialState, IStateKeyResolver keyResolver) - : base(specialState, keyResolver) { } - - /// - /// Initializes the attribute with a specific numeric state and a custom key resolver. - /// - /// The integer state to associate - /// The key resolver for state keeping - public NumericStateAttribute(int myState, IStateKeyResolver keyResolver) - : base(myState, keyResolver) { } - - /// - /// Initializes the attribute with a special state and the default sender ID resolver. - /// - /// The special state to associate - public NumericStateAttribute(SpecialState specialState) - : base(specialState, new SenderIdResolver()) { } - - /// - /// Initializes the attribute with a specific numeric state and the default sender ID resolver. - /// - /// The integer state to associate - public NumericStateAttribute(int myState) - : this(myState, new SenderIdResolver()) { } - } -} diff --git a/src/Telegrator/Annotations/StateKeeping/SpecialState.cs b/src/Telegrator/Annotations/StateKeeping/SpecialState.cs deleted file mode 100644 index b599448..0000000 --- a/src/Telegrator/Annotations/StateKeeping/SpecialState.cs +++ /dev/null @@ -1,21 +0,0 @@ -namespace Telegrator.Annotations.StateKeeping -{ - /// - /// Represents special states for state keeping logic. - /// - public enum SpecialState - { - /// - /// No special state. - /// - None, - /// - /// Indicates that no state is present. - /// - NoState, - /// - /// Indicates that any state is acceptable. - /// - AnyState - } -} diff --git a/src/Telegrator/Annotations/StateKeeping/StringStateAttribute.cs b/src/Telegrator/Annotations/StateKeeping/StringStateAttribute.cs deleted file mode 100644 index 8d47b54..0000000 --- a/src/Telegrator/Annotations/StateKeeping/StringStateAttribute.cs +++ /dev/null @@ -1,43 +0,0 @@ -using Telegrator.StateKeeping; -using Telegrator.Attributes; -using Telegrator.Core.StateKeeping; - -namespace Telegrator.Annotations.StateKeeping -{ - /// - /// Attribute for associating a handler or method with a string-based state keeper. - /// Provides various constructors for flexible state and key resolver configuration. - /// - public class StringStateAttribute : StateKeeperAttribute - { - /// - /// Initializes the attribute with a special state and a custom key resolver. - /// - /// The special state to associate - /// The key resolver for state keeping - public StringStateAttribute(SpecialState specialState, IStateKeyResolver keyResolver) - : base(specialState, keyResolver) { } - - /// - /// Initializes the attribute with a specific state and a custom key resolver. - /// - /// The string state to associate - /// The key resolver for state keeping - public StringStateAttribute(string myState, IStateKeyResolver keyResolver) - : base(myState, keyResolver) { } - - /// - /// Initializes the attribute with a special state and the default sender ID resolver. - /// - /// The special state to associate - public StringStateAttribute(SpecialState specialState) - : base(specialState, new SenderIdResolver()) { } - - /// - /// Initializes the attribute with a specific state and the default sender ID resolver. - /// - /// The string state to associate - public StringStateAttribute(string myState) - : base(myState, new SenderIdResolver()) { } - } -} diff --git a/src/Telegrator/Annotations/Targetted/WelcomeAttribute.cs b/src/Telegrator/Annotations/WelcomeAttribute.cs similarity index 94% rename from src/Telegrator/Annotations/Targetted/WelcomeAttribute.cs rename to src/Telegrator/Annotations/WelcomeAttribute.cs index dece1b4..26f39fb 100644 --- a/src/Telegrator/Annotations/Targetted/WelcomeAttribute.cs +++ b/src/Telegrator/Annotations/WelcomeAttribute.cs @@ -2,7 +2,7 @@ using Telegram.Bot.Types.Enums; using Telegrator.Filters; -namespace Telegrator.Annotations.Targetted +namespace Telegrator.Annotations { /// /// Attribute for filtering message with command "start" in bot's private chats. diff --git a/src/Telegrator/Attributes/StateKeeperAttribute.cs b/src/Telegrator/Attributes/StateKeeperAttribute.cs deleted file mode 100644 index b284dd1..0000000 --- a/src/Telegrator/Attributes/StateKeeperAttribute.cs +++ /dev/null @@ -1,86 +0,0 @@ -using Telegram.Bot.Types; -using Telegrator.Annotations.StateKeeping; -using Telegrator.Core.Attributes; -using Telegrator.Core.Filters; -using Telegrator.Core.StateKeeping; - -namespace Telegrator.Attributes -{ - /// - /// Abstract attribute for associating a handler or method with a state keeper. - /// Provides logic for state-based filtering and state management. - /// - /// The type of the key used for state keeping (e.g., chat ID). - /// The type of the state value (e.g., string, int). - /// The type of the state keeper implementation. - public abstract class StateKeeperAttribute : StateKeeperAttributeBase where TKey : notnull where TState : notnull where TKeeper : StateKeeperBase, new() - { - /* - private static readonly TKeeper _shared = new TKeeper(); - private static readonly Dictionary _keyed = []; - */ - - /// - /// Gets or sets the singleton instance of the state keeper for this attribute type. - /// - public static TKeeper Shared { get; } = new TKeeper(); - - /// - /// Gets the default state value of this statekeeper. - /// - public static TState DefaultState => Shared.DefaultState; - - /// - /// Gets the state value associated with this attribute instance. - /// - public TState MyState { get; private set; } - - /// - /// Gets the special state mode for this attribute instance. - /// - public SpecialState SpecialState { get; private set; } - - /// - /// Initializes the attribute with a specific state and a custom key resolver. - /// - /// The state value to associate - /// The key resolver for state keeping - protected StateKeeperAttribute(TState myState, IStateKeyResolver keyResolver) : base(typeof(TKeeper)) - { - Shared.KeyResolver = keyResolver; - MyState = myState; - SpecialState = SpecialState.None; - } - - /// - /// Initializes the attribute with a special state and a custom key resolver. - /// - /// The special state mode - /// The key resolver for state keeping - protected StateKeeperAttribute(SpecialState specialState, IStateKeyResolver keyResolver) : base(typeof(TKeeper)) - { - Shared.KeyResolver = keyResolver; - MyState = Shared.DefaultState; - SpecialState = specialState; - } - - /// - /// Determines whether the current update context passes the state filter. - /// - /// The filter execution context - /// True if the state matches the filter; otherwise, false. - public override bool CanPass(FilterExecutionContext context) - { - if (SpecialState == SpecialState.AnyState) - return true; - - if (!Shared.TryGetState(context.Input, out TState? state)) - return SpecialState == SpecialState.NoState; - - if (state == null) - return false; - - return MyState.Equals(state); - } - } -} diff --git a/src/Telegrator/Core/Attributes/StateKeeperAttributeBase.cs b/src/Telegrator/Core/Attributes/StateKeeperAttributeBase.cs deleted file mode 100644 index c1dfac6..0000000 --- a/src/Telegrator/Core/Attributes/StateKeeperAttributeBase.cs +++ /dev/null @@ -1,35 +0,0 @@ -using Telegram.Bot.Types; -using Telegrator.Core.Filters; -using Telegrator.Core.Handlers; -using Telegrator.Core.StateKeeping; - -namespace Telegrator.Core.Attributes -{ - /// - /// Sets the state in which the can be executed - /// - [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)] - public abstract class StateKeeperAttributeBase : Attribute, IFilter - { - /// - public bool IsCollectible => GetType().HasPublicProperties(); - - /// - /// Creates a new instance - /// - /// - /// - protected StateKeeperAttributeBase(Type stateKeeperType) - { - if (!stateKeeperType.IsAssignableToGenericType(typeof(StateKeeperBase<,>))) - throw new ArgumentException(stateKeeperType + " is not a StateKeeperBase", nameof(stateKeeperType)); - } - - /// - /// Realizes a for validation of the current in the polling routing - /// - /// - /// - public abstract bool CanPass(FilterExecutionContext context); - } -} diff --git a/src/Telegrator/Core/Descriptors/DefaultCustomDescriptors.cs b/src/Telegrator/Core/Descriptors/DefaultCustomDescriptors.cs index 5c244b1..7e72539 100644 --- a/src/Telegrator/Core/Descriptors/DefaultCustomDescriptors.cs +++ b/src/Telegrator/Core/Descriptors/DefaultCustomDescriptors.cs @@ -24,7 +24,7 @@ namespace Telegrator.Core.Descriptors public MethodHandlerDescriptor(AbstractHandlerAction action) : base(DescriptorType.General, typeof(MethodHandler), true) { UpdateHandlerAttributeBase handlerAttribute = HandlerInspector.GetHandlerAttribute(action.Method); - StateKeeperAttributeBase? stateKeeperAttribute = HandlerInspector.GetStateKeeperAttribute(action.Method); + IFilter? stateKeeperAttribute = HandlerInspector.GetStateKeeperAttribute(action.Method); IFilter[] filters = HandlerInspector.GetFilterAttributes(action.Method, handlerAttribute.Type).ToArray(); UpdateType = handlerAttribute.Type; diff --git a/src/Telegrator/Core/Descriptors/DescribedHandlerDescriptor.cs b/src/Telegrator/Core/Descriptors/DescribedHandlerDescriptor.cs index 1079274..50b2565 100644 --- a/src/Telegrator/Core/Descriptors/DescribedHandlerDescriptor.cs +++ b/src/Telegrator/Core/Descriptors/DescribedHandlerDescriptor.cs @@ -2,6 +2,7 @@ using Telegram.Bot.Types; using Telegrator.Core.Filters; using Telegrator.Core.Handlers; +using Telegrator.Core.States; namespace Telegrator.Core.Descriptors { @@ -26,6 +27,11 @@ namespace Telegrator.Core.Descriptors /// The awaiting provider to fetch new updates inside handler /// public IAwaitingProvider AwaitingProvider { get; } + + /// + /// The state storage to handling state machines + /// + public IStateStorage StateStorage { get; } /// /// The Telegram bot client used for this handler. @@ -73,6 +79,7 @@ namespace Telegrator.Core.Descriptors /// The descriptor from which this handler was described. /// The update router. /// The awaiting provider. + /// The state storage. /// The Telegram bot client. /// The handler instance. /// The filter execution context. @@ -81,6 +88,7 @@ namespace Telegrator.Core.Descriptors HandlerDescriptor fromDescriptor, IUpdateRouter updateRouter, IAwaitingProvider awaitingProvider, + IStateStorage stateStorage, ITelegramBotClient client, UpdateHandlerBase handlerInstance, FilterExecutionContext filterContext, @@ -89,6 +97,7 @@ namespace Telegrator.Core.Descriptors From = fromDescriptor; UpdateRouter = updateRouter; AwaitingProvider = awaitingProvider; + StateStorage = stateStorage; Client = client; HandlerInstance = handlerInstance; ExtraData = filterContext.Data; diff --git a/src/Telegrator/Core/Descriptors/HandlerDescriptor.cs b/src/Telegrator/Core/Descriptors/HandlerDescriptor.cs index 14e870f..723e886 100644 --- a/src/Telegrator/Core/Descriptors/HandlerDescriptor.cs +++ b/src/Telegrator/Core/Descriptors/HandlerDescriptor.cs @@ -167,7 +167,7 @@ namespace Telegrator.Core.Descriptors if (handlerAttribute.ExpectingHandlerType != null && !handlerAttribute.ExpectingHandlerType.Contains(handlerType.BaseType)) throw new ArgumentException(string.Format("This handler attribute cannot be attached to this class. Attribute can be attached on next handlers : {0}", string.Join(", ", handlerAttribute.ExpectingHandlerType.AsEnumerable()))); - StateKeeperAttributeBase? stateKeeperAttribute = HandlerInspector.GetStateKeeperAttribute(handlerType); + IFilter? stateKeeperAttribute = HandlerInspector.GetStateKeeperAttribute(handlerType); IFilter[] filters = HandlerInspector.GetFilterAttributes(handlerType, handlerAttribute.Type).ToArray(); UpdateType = handlerAttribute.Type; diff --git a/src/Telegrator/Core/Descriptors/HandlerInspector.cs b/src/Telegrator/Core/Descriptors/HandlerInspector.cs index 155f845..5dbb21f 100644 --- a/src/Telegrator/Core/Descriptors/HandlerInspector.cs +++ b/src/Telegrator/Core/Descriptors/HandlerInspector.cs @@ -2,6 +2,7 @@ using System.Reflection; using Telegram.Bot.Types; using Telegram.Bot.Types.Enums; +using Telegrator.Annotations; using Telegrator.Aspects; using Telegrator.Core.Attributes; using Telegrator.Core.Filters; @@ -42,13 +43,13 @@ namespace Telegrator.Core.Descriptors /// /// The member info representing the handler type. /// The state keeper attribute, or null if not present. - public static StateKeeperAttributeBase? GetStateKeeperAttribute(MemberInfo handlerType) + public static IFilter? GetStateKeeperAttribute(MemberInfo handlerType) { // Getting polling handler attribute - IEnumerable handlerAttrs = handlerType.GetCustomAttributes(); + Attribute stateAttr = handlerType.GetCustomAttribute(typeof(StateAttribute<,>)); // - return handlerAttrs.Any() ? handlerAttrs.Single() : null; + return stateAttr as IFilter; } /// diff --git a/src/Telegrator/Core/Filters/FilterExecutionContext.cs b/src/Telegrator/Core/Filters/FilterExecutionContext.cs index 1d12539..1bfaab4 100644 --- a/src/Telegrator/Core/Filters/FilterExecutionContext.cs +++ b/src/Telegrator/Core/Filters/FilterExecutionContext.cs @@ -9,6 +9,11 @@ namespace Telegrator.Core.Filters /// The type of the input for the filter. public class FilterExecutionContext where T : class { + /// + /// Gets the for the current context. + /// + public IUpdateRouter UpdateRouter { get; } + /// /// Gets the for the current context. /// @@ -42,13 +47,15 @@ namespace Telegrator.Core.Filters /// /// Initializes a new instance of the class with all parameters. /// + /// The router, that invoked filter. /// The bot info. /// The update. /// The input object. /// The additional data dictionary. /// The list of completed filters. - public FilterExecutionContext(ITelegramBotInfo botInfo, Update update, T input, Dictionary data, CompletedFiltersList completedFilters) + public FilterExecutionContext(IUpdateRouter router, ITelegramBotInfo botInfo, Update update, T input, Dictionary data, CompletedFiltersList completedFilters) { + UpdateRouter = router; BotInfo = botInfo; Data = data; CompletedFilters = completedFilters; @@ -60,11 +67,12 @@ namespace Telegrator.Core.Filters /// /// Initializes a new instance of the class with default data and filters. /// + /// The router, that invoked filter. /// The bot info. /// The update. /// The input object. - public FilterExecutionContext(ITelegramBotInfo botInfo, Update update, T input) - : this(botInfo, update, input, [], []) { } + public FilterExecutionContext(IUpdateRouter router, ITelegramBotInfo botInfo, Update update, T input) + : this(router, botInfo, update, input, [], []) { } /// /// Creates a child context for a different input type, sharing the same data and completed filters. @@ -73,6 +81,6 @@ namespace Telegrator.Core.Filters /// The new input object. /// A new instance. public FilterExecutionContext CreateChild(C input) where C : class - => new FilterExecutionContext(BotInfo, Update, input, Data, CompletedFilters); + => new FilterExecutionContext(UpdateRouter, BotInfo, Update, input, Data, CompletedFilters); } } diff --git a/src/Telegrator/Core/Handlers/AbstractUpdateHandler.cs b/src/Telegrator/Core/Handlers/AbstractUpdateHandler.cs index 85eef46..9b21fd0 100644 --- a/src/Telegrator/Core/Handlers/AbstractUpdateHandler.cs +++ b/src/Telegrator/Core/Handlers/AbstractUpdateHandler.cs @@ -3,6 +3,7 @@ using Telegram.Bot.Types; using Telegram.Bot.Types.Enums; using Telegrator.Core.Descriptors; using Telegrator.Core.Filters; +using Telegrator.Core.States; using Telegrator.Handlers; namespace Telegrator.Core.Handlers @@ -47,6 +48,11 @@ namespace Telegrator.Core.Handlers /// protected IAwaitingProvider AwaitingProvider => Container.AwaitingProvider; + /// + /// Storage of bot states. + /// + protected IStateStorage StateStorage => Container.StateStorage; + /// /// Initializes a new instance and checks that the update type matches . /// diff --git a/src/Telegrator/Core/Handlers/Building/HandlerBuilderBase.cs b/src/Telegrator/Core/Handlers/Building/HandlerBuilderBase.cs index 561bd25..937b983 100644 --- a/src/Telegrator/Core/Handlers/Building/HandlerBuilderBase.cs +++ b/src/Telegrator/Core/Handlers/Building/HandlerBuilderBase.cs @@ -1,9 +1,9 @@ using Telegram.Bot.Types; using Telegram.Bot.Types.Enums; -using Telegrator.Annotations.StateKeeping; using Telegrator.Core.Descriptors; using Telegrator.Core.Filters; -using Telegrator.Core.StateKeeping; +using Telegrator.Core.States; +using Telegrator.Filters; namespace Telegrator.Core.Handlers.Building { @@ -140,35 +140,15 @@ namespace Telegrator.Core.Handlers.Building /// /// Sets a state keeper for the handler using a specific state and key resolver. /// - /// The type of the key. - /// The type of the state. - /// The type of the state keeper. - /// The state value. - /// The key resolver. + /// The key resolver. + /// The state value. + /// The state value. /// The builder instance. - public void SetStateKeeper(TState myState, IStateKeyResolver keyResolver) - where TKey : notnull - where TState : IEquatable - where TKeeper : StateKeeperBase, new() + public void SetState(TValue? state) + where TKey : IStateKeyResolver, new() + where TValue : IEquatable { - StateKeeper = new StateKeepFilter(myState, keyResolver); - } - - /// - /// Sets a state keeper for the handler using a special state and key resolver. - /// - /// The type of the key. - /// The type of the state. - /// The type of the state keeper. - /// The special state value. - /// The key resolver. - /// The builder instance. - public void SetStateKeeper(SpecialState specialState, IStateKeyResolver keyResolver) - where TKey : notnull - where TState : IEquatable - where TKeeper : StateKeeperBase, new() - { - StateKeeper = new StateKeepFilter(specialState, keyResolver); + StateKeeper = new StateKeyFilter(state); } /// diff --git a/src/Telegrator/Core/Handlers/Building/IAwaiterHandlerBuilder.cs b/src/Telegrator/Core/Handlers/Building/IAwaiterHandlerBuilder.cs index 78819b1..954c51e 100644 --- a/src/Telegrator/Core/Handlers/Building/IAwaiterHandlerBuilder.cs +++ b/src/Telegrator/Core/Handlers/Building/IAwaiterHandlerBuilder.cs @@ -1,4 +1,4 @@ -using Telegrator.Core.StateKeeping; +using Telegrator.Core.States; namespace Telegrator.Core.Handlers.Building { @@ -11,9 +11,9 @@ namespace Telegrator.Core.Handlers.Building /// /// Awaits an update using the specified key resolver and cancellation token. /// - /// The to resolve the key. + /// The to resolve the key. /// The cancellation token. /// A representing the awaited update. - public Task Await(IStateKeyResolver keyResolver, CancellationToken cancellationToken = default); + public Task Await(IStateKeyResolver keyResolver, CancellationToken cancellationToken = default); } } diff --git a/src/Telegrator/Core/Handlers/Building/IHandlerBuilder.cs b/src/Telegrator/Core/Handlers/Building/IHandlerBuilder.cs index d2a40d3..821909d 100644 --- a/src/Telegrator/Core/Handlers/Building/IHandlerBuilder.cs +++ b/src/Telegrator/Core/Handlers/Building/IHandlerBuilder.cs @@ -1,7 +1,6 @@ using Telegram.Bot.Types; -using Telegrator.Annotations.StateKeeping; using Telegrator.Core.Filters; -using Telegrator.Core.StateKeeping; +using Telegrator.Core.States; namespace Telegrator.Core.Handlers.Building { @@ -56,30 +55,13 @@ namespace Telegrator.Core.Handlers.Building /// /// Sets a state keeper for the handler using a specific state and key resolver. /// - /// The type of the key. - /// The type of the state. - /// The type of the state keeper. - /// The state value. - /// The key resolver. + /// The key resolver. + /// The state value. + /// The state value. /// The builder instance. - public void SetStateKeeper(TState myState, IStateKeyResolver keyResolver) - where TKey : notnull - where TState : IEquatable - where TKeeper : StateKeeperBase, new(); - - /// - /// Sets a state keeper for the handler using a special state and key resolver. - /// - /// The type of the key. - /// The type of the state. - /// The type of the state keeper. - /// The special state value. - /// The key resolver. - /// The builder instance. - public void SetStateKeeper(SpecialState specialState, IStateKeyResolver keyResolver) - where TKey : notnull - where TState : IEquatable - where TKeeper : StateKeeperBase, new(); + public void SetState(TValue? state) + where TKey : IStateKeyResolver, new() + where TValue : IEquatable; /// /// Adds a targeted filter for a specific filter target type. diff --git a/src/Telegrator/Core/Handlers/Building/StateKeepFilter.cs b/src/Telegrator/Core/Handlers/Building/StateKeepFilter.cs deleted file mode 100644 index d0b0bf5..0000000 --- a/src/Telegrator/Core/Handlers/Building/StateKeepFilter.cs +++ /dev/null @@ -1,80 +0,0 @@ -using Telegram.Bot.Types; -using Telegrator.Annotations.StateKeeping; -using Telegrator.Core.Filters; -using Telegrator.Core.StateKeeping; -using Telegrator.Filters; - -namespace Telegrator.Core.Handlers.Building -{ - /// - /// Filter for state keeping logic, allowing filtering based on state and special state conditions. - /// - /// The type of the key for state resolution. - /// The type of the state. - /// The type of the state keeper. - public class StateKeepFilter : Filter - where TKey : notnull - where TState : IEquatable - where TKeeper : StateKeeperBase, new() - { - /// - /// Gets or sets the state keeper instance. - /// - public static TKeeper StateKeeper { get; internal set; } = null!; - - /// - /// Gets the state value for this filter. - /// - public TState MyState { get; private set; } - - /// - /// Gets the special state value for this filter. - /// - public SpecialState SpecialState { get; private set; } - - /// - /// Initializes a new instance of the class with a specific state. - /// - /// The state value. - /// The key resolver. - public StateKeepFilter(TState myState, IStateKeyResolver keyResolver) - { - StateKeeper ??= new TKeeper(); - StateKeeper.KeyResolver = keyResolver; - MyState = myState; - SpecialState = SpecialState.None; - } - - /// - /// Initializes a new instance of the class with a special state. - /// - /// The special state value. - /// The key resolver. - public StateKeepFilter(SpecialState specialState, IStateKeyResolver keyResolver) - { - StateKeeper ??= new TKeeper(); - StateKeeper.KeyResolver = keyResolver; - MyState = StateKeeper.DefaultState; - SpecialState = specialState; - } - - /// - /// Determines whether the filter can pass for the given context based on state logic. - /// - /// The filter execution context. - /// True if the filter passes; otherwise, false. - public override bool CanPass(FilterExecutionContext context) - { - if (SpecialState == SpecialState.AnyState) - return true; - - if (!StateKeeper.TryGetState(context.Input, out TState? state)) - return SpecialState == SpecialState.NoState; - - if (state == null) - return false; - - return MyState.Equals(state); - } - } -} diff --git a/src/Telegrator/Core/Handlers/EmptyHandlerContainer.cs b/src/Telegrator/Core/Handlers/EmptyHandlerContainer.cs index 4004140..20f9880 100644 --- a/src/Telegrator/Core/Handlers/EmptyHandlerContainer.cs +++ b/src/Telegrator/Core/Handlers/EmptyHandlerContainer.cs @@ -1,6 +1,7 @@ using Telegram.Bot; using Telegram.Bot.Types; using Telegrator.Core.Filters; +using Telegrator.Core.States; namespace Telegrator.Core.Handlers { @@ -23,5 +24,8 @@ namespace Telegrator.Core.Handlers /// public IAwaitingProvider AwaitingProvider => throw new NotImplementedException(); + + /// + public IStateStorage StateStorage => throw new NotImplementedException(); } } diff --git a/src/Telegrator/Core/Handlers/IHandlerContainer.cs b/src/Telegrator/Core/Handlers/IHandlerContainer.cs index 2a47e48..a2ce418 100644 --- a/src/Telegrator/Core/Handlers/IHandlerContainer.cs +++ b/src/Telegrator/Core/Handlers/IHandlerContainer.cs @@ -1,6 +1,7 @@ using Telegram.Bot; using Telegram.Bot.Types; using Telegrator.Core.Filters; +using Telegrator.Core.States; namespace Telegrator.Core.Handlers { @@ -34,5 +35,10 @@ namespace Telegrator.Core.Handlers /// Gets the for awaiting operations. /// public IAwaitingProvider AwaitingProvider { get; } + + /// + /// Gets the for state managment. + /// + public IStateStorage StateStorage { get; } } } diff --git a/src/Telegrator/Core/Handlers/UpdateHandlerBase.cs b/src/Telegrator/Core/Handlers/UpdateHandlerBase.cs index 2ac2f78..070e74c 100644 --- a/src/Telegrator/Core/Handlers/UpdateHandlerBase.cs +++ b/src/Telegrator/Core/Handlers/UpdateHandlerBase.cs @@ -125,7 +125,7 @@ namespace Telegrator.Core.Handlers } } - internal IHandlerContainer GetContainer(DescribedHandlerDescriptor handlerInfo) + private IHandlerContainer GetContainer(DescribedHandlerDescriptor handlerInfo) { if (this is IHandlerContainerFactory handlerDefainedContainerFactory) return handlerDefainedContainerFactory.CreateContainer(handlerInfo); diff --git a/src/Telegrator/Core/IPollingProvider.cs b/src/Telegrator/Core/IPollingProvider.cs deleted file mode 100644 index 75ddcc7..0000000 --- a/src/Telegrator/Core/IPollingProvider.cs +++ /dev/null @@ -1,19 +0,0 @@ -namespace Telegrator.Core -{ - /// - /// Interface for polling providers that manage both regular and awaiting handlers. - /// Provides access to handlers for different types of update processing during polling operations. - /// - public interface IPollingProvider - { - /// - /// Gets the that manages handlers for polling. - /// - public IHandlersProvider HandlersProvider { get; } - - /// - /// Gets the that manages awaiting handlers for polling. - /// - public IAwaitingProvider AwaitingProvider { get; } - } -} diff --git a/src/Telegrator/Core/IUpdateRouter.cs b/src/Telegrator/Core/IUpdateRouter.cs index 82252b0..f6748c7 100644 --- a/src/Telegrator/Core/IUpdateRouter.cs +++ b/src/Telegrator/Core/IUpdateRouter.cs @@ -1,5 +1,6 @@ using Telegram.Bot.Polling; using Telegrator.Core.Handlers; +using Telegrator.Core.States; namespace Telegrator.Core { @@ -7,7 +8,7 @@ namespace Telegrator.Core /// Interface for update routers that handle incoming updates and manage handler execution. /// Combines update handling capabilities with polling provider functionality and exception handling. /// - public interface IUpdateRouter : IUpdateHandler, IPollingProvider + public interface IUpdateRouter : IUpdateHandler { /// /// Gets the for the router. @@ -19,6 +20,21 @@ namespace Telegrator.Core /// public IUpdateHandlersPool HandlersPool { get; } + /// + /// Gets the that manages handlers for polling. + /// + public IHandlersProvider HandlersProvider { get; } + + /// + /// Gets the that manages awaiting handlers for polling. + /// + public IAwaitingProvider AwaitingProvider { get; } + + /// + /// Gets the that manages storing of handlers state. + /// + public IStateStorage StateStorage { get; } + /// /// Gets or sets the for handling exceptions. /// diff --git a/src/Telegrator/Core/StateKeeping/IStateKeyResolver.cs b/src/Telegrator/Core/StateKeeping/IStateKeyResolver.cs deleted file mode 100644 index f8d3404..0000000 --- a/src/Telegrator/Core/StateKeeping/IStateKeyResolver.cs +++ /dev/null @@ -1,18 +0,0 @@ -using Telegram.Bot.Types; - -namespace Telegrator.Core.StateKeeping -{ - /// - /// Defines a resolver for extracting a key from an update for state keeping purposes. - /// - /// The type of the key. - public interface IStateKeyResolver where TKey : notnull - { - /// - /// Resolves a key from the specified . - /// - /// The update to resolve the key from. - /// The resolved key. - public TKey ResolveKey(Update keySource); - } -} diff --git a/src/Telegrator/Core/StateKeeping/StateKeeperBase.cs b/src/Telegrator/Core/StateKeeping/StateKeeperBase.cs deleted file mode 100644 index 98c51d3..0000000 --- a/src/Telegrator/Core/StateKeeping/StateKeeperBase.cs +++ /dev/null @@ -1,150 +0,0 @@ -using Telegram.Bot.Types; - -namespace Telegrator.Core.StateKeeping -{ - /// - /// Base class for managing state associated with updates and keys. - /// - /// The type of the key used for state resolution. - /// The type of the state. - public abstract class StateKeeperBase where TState : notnull where TKey : notnull - { - private readonly Dictionary States = []; - - /// - /// Gets or sets the key resolver used to resolve keys from updates. - /// - public IStateKeyResolver KeyResolver { get; set; } = null!; - - /// - /// Gets the default state value. - /// - public abstract TState DefaultState { get; } - - /// - /// Sets the state for the specified update. - /// - /// The update to use as a key source. - /// The new state value. - public virtual void SetState(Update keySource, TState newState) - { - TKey key = KeyResolver.ResolveKey(keySource); - States.Set(key, newState, DefaultState); - } - - /// - /// Gets the state for the specified update. - /// - /// The update to use as a key source. - /// The state value. - public virtual TState GetState(Update keySource) - { - TKey key = KeyResolver.ResolveKey(keySource); - return States[key]; - } - - /// - /// Tries to get the state for the specified update. - /// - /// The update to use as a key source. - /// When this method returns, contains the state value if found; otherwise, the default value. - /// True if the state was found; otherwise, false. - public virtual bool TryGetState(Update keySource, out TState? state) - { - TKey key = KeyResolver.ResolveKey(keySource); - return States.TryGetValue(key, out state); - } - - /// - /// Determines whether a state exists for the specified update. - /// - /// The update to use as a key source. - /// True if the state exists; otherwise, false. - public virtual bool HasState(Update keySource) - { - TKey key = KeyResolver.ResolveKey(keySource); - return States.ContainsKey(key); - } - - /// - /// Creates a state for the specified update using the default state value. - /// - /// The update to use as a key source. - public virtual void CreateState(Update keySource) - { - TKey key = KeyResolver.ResolveKey(keySource); - States.Set(key, DefaultState); - } - - /// - /// Deletes the state for the specified update. - /// - /// The update to use as a key source. - public virtual void DeleteState(Update keySource) - { - TKey key = KeyResolver.ResolveKey(keySource); - States.Remove(key); - } - - /// - /// Moves the state forward for the specified update. - /// - /// The update to use as a key source. - public virtual void MoveForward(Update keySource) - { - TKey key = KeyResolver.ResolveKey(keySource); - if (!States.TryGetValue(key, out TState currentState)) - { - States.Set(key, DefaultState); - currentState = DefaultState; - } - - TState newState = MoveForward(currentState, key); - States[key] = newState; - } - - /// - /// Moves the state backward for the specified update. - /// - /// The update to use as a key source. - public virtual void MoveBackward(Update keySource) - { - TKey key = KeyResolver.ResolveKey(keySource); - if (!States.TryGetValue(key, out TState currentState)) - { - States.Set(key, DefaultState); - return; - } - - TState newState = MoveBackward(currentState, key); - States[key] = newState; - } - - /* - /// - /// Gets the state keeper for the specified key. - /// - /// The type of the state keeper. - /// The key. - /// The state keeper instance. - protected virtual TStateKeeper GetKeeper(TKey key) where TStateKeeper : StateKeeperBase - => States[key] as TStateKeeper ?? throw new InvalidCastException(); - */ - - /// - /// Moves the state forward for the specified current state and key. - /// - /// The current state value. - /// The key. - /// The new state value. - protected abstract TState MoveForward(TState currentState, TKey currentKey); - - /// - /// Moves the state backward for the specified current state and key. - /// - /// The current state value. - /// The key. - /// The new state value. - protected abstract TState MoveBackward(TState currentState, TKey currentKey); - } -} diff --git a/src/Telegrator/Core/States/IStateKeyResolver.cs b/src/Telegrator/Core/States/IStateKeyResolver.cs new file mode 100644 index 0000000..a01eb15 --- /dev/null +++ b/src/Telegrator/Core/States/IStateKeyResolver.cs @@ -0,0 +1,16 @@ +using Telegram.Bot.Types; + +namespace Telegrator.Core.States; + +/// +/// Defines a resolver for extracting a key from an update for state keeping purposes. +/// +public interface IStateKeyResolver +{ + /// + /// Resolves a key from the specified . + /// + /// The update to resolve the key from. + /// The resolved key. + public string? ResolveKey(Update keySource); +} diff --git a/src/Telegrator/Core/States/IStateMachine.cs b/src/Telegrator/Core/States/IStateMachine.cs new file mode 100644 index 0000000..5506c2b --- /dev/null +++ b/src/Telegrator/Core/States/IStateMachine.cs @@ -0,0 +1,9 @@ +namespace Telegrator.Core.States; + +public interface IStateMachine where TState : IEquatable +{ + Task Current(IStateStorage storage, string updateKey, CancellationToken cancellationToken = default); + Task Advance(IStateStorage storage, string updateKey, CancellationToken cancellationToken = default); + Task Retreat(IStateStorage storage, string updateKey, CancellationToken cancellationToken = default); + Task Reset(IStateStorage storage, string updateKey, CancellationToken cancellationToken = default); +} diff --git a/src/Telegrator/Core/States/IStateStorage.cs b/src/Telegrator/Core/States/IStateStorage.cs new file mode 100644 index 0000000..c48cb2e --- /dev/null +++ b/src/Telegrator/Core/States/IStateStorage.cs @@ -0,0 +1,8 @@ +namespace Telegrator.Core.States; + +public interface IStateStorage +{ + Task SetAsync(string key, T state, CancellationToken cancellationToken = default); + Task GetAsync(string key, CancellationToken cancellationToken = default); + Task DeleteAsync(string key, CancellationToken cancellationToken = default); +} diff --git a/src/Telegrator/Filters/StateKeyFilter.cs b/src/Telegrator/Filters/StateKeyFilter.cs index 8e9fc4a..3594cfd 100644 --- a/src/Telegrator/Filters/StateKeyFilter.cs +++ b/src/Telegrator/Filters/StateKeyFilter.cs @@ -1,31 +1,44 @@ using Telegram.Bot.Types; using Telegrator.Core.Filters; -using Telegrator.Core.StateKeeping; +using Telegrator.Core.States; namespace Telegrator.Filters { /// /// Filters updates by comparing a resolved state key with a target key. /// - /// The type of the key used for state resolution. - public class StateKeyFilter : Filter where TKey : IEquatable + /// The type of the key resolver used to get state key. + /// The type of the key used for state resolution. + public class StateKeyFilter : Filter + where TKey : IStateKeyResolver, new() + where TValue : IEquatable { - private readonly IStateKeyResolver KeyResolver; - private readonly TKey TargetKey; + private readonly TValue? TargetKey; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// - /// The key resolver to extract the key from the update. /// The target key to compare with. - public StateKeyFilter(IStateKeyResolver keyResolver, TKey targetKey) + public StateKeyFilter(TValue? targetKey) { - KeyResolver = keyResolver; TargetKey = targetKey; } /// public override bool CanPass(FilterExecutionContext context) - => KeyResolver.ResolveKey(context.Input).Equals(TargetKey); + { + string? key = new TKey().ResolveKey(context.Input); + if (key is null) + return TargetKey is null; + + TValue? value = context.UpdateRouter.StateStorage.GetAsync(key).Result; + if (value is null) + return TargetKey is null; + + if (TargetKey is null) + return false; + + return TargetKey.Equals(value); + } } } diff --git a/src/Telegrator/Handlers/Building/AwaiterHandlerBuilder.cs b/src/Telegrator/Handlers/Building/AwaiterHandlerBuilder.cs index c21b94f..2b87b73 100644 --- a/src/Telegrator/Handlers/Building/AwaiterHandlerBuilder.cs +++ b/src/Telegrator/Handlers/Building/AwaiterHandlerBuilder.cs @@ -1,11 +1,11 @@ using Telegram.Bot.Types; using Telegram.Bot.Types.Enums; -using Telegrator.Filters; -using Telegrator.StateKeeping; using Telegrator.Core; -using Telegrator.Core.Handlers.Building; using Telegrator.Core.Descriptors; -using Telegrator.Core.StateKeeping; +using Telegrator.Core.Handlers.Building; +using Telegrator.Core.States; +using Telegrator.Filters; +using Telegrator.States; namespace Telegrator.Handlers.Building { @@ -56,9 +56,21 @@ namespace Telegrator.Handlers.Building /// The state key resolver to use for filtering updates. /// The cancellation token to cancel the wait operation. /// The awaited update of type TUpdate. - public async Task Await(IStateKeyResolver keyResolver, CancellationToken cancellationToken = default) + public async Task Await(IStateKeyResolver keyResolver, CancellationToken cancellationToken = default) { - Filters.Add(new StateKeyFilter(keyResolver, keyResolver.ResolveKey(HandlingUpdate))); + string? handlingKey = keyResolver.ResolveKey(HandlingUpdate); + if (handlingKey is null) + throw new InvalidOperationException("Cannot await update with resolved key as NULL"); + + Filters.Add(Filter.If(ctx => + { + string? key = keyResolver.ResolveKey(ctx.Update); + if (key is null) + return false; + + return key == handlingKey; + })); + AwaiterHandler handlerInstance = new AwaiterHandler(UpdateType); HandlerDescriptor descriptor = BuildImplicitDescriptor(handlerInstance); diff --git a/src/Telegrator/Handlers/Building/TypesExtensions.cs b/src/Telegrator/Handlers/Building/TypesExtensions.cs index 74573d1..476a992 100644 --- a/src/Telegrator/Handlers/Building/TypesExtensions.cs +++ b/src/Telegrator/Handlers/Building/TypesExtensions.cs @@ -1,9 +1,7 @@ using Telegram.Bot.Types; -using Telegrator.Annotations.StateKeeping; using Telegrator.Core.Filters; using Telegrator.Core.Handlers.Building; -using Telegrator.Core.StateKeeping; -using Telegrator.StateKeeping; +using Telegrator.Core.States; namespace Telegrator.Handlers.Building { @@ -61,25 +59,13 @@ namespace Telegrator.Handlers.Building return handlerBuilder; } - /// - public static TBuilder SetStateKeeper(this TBuilder handlerBuilder, TState myState, IStateKeyResolver keyResolver) + /// + public static TBuilder SetState(this TBuilder handlerBuilder, TValue? myState) where TBuilder : HandlerBuilderBase - where TKey : notnull - where TState : IEquatable - where TKeeper : StateKeeperBase, new() + where TKey : IStateKeyResolver, new() + where TValue : IEquatable { - handlerBuilder.SetStateKeeper(myState, keyResolver); - return handlerBuilder; - } - - /// - public static TBuilder SetStateKeeper(this TBuilder handlerBuilder, SpecialState specialState, IStateKeyResolver keyResolver) - where TBuilder : HandlerBuilderBase - where TKey : notnull - where TState : IEquatable - where TKeeper : StateKeeperBase, new() - { - handlerBuilder.SetStateKeeper(specialState, keyResolver); + handlerBuilder.SetState(myState); return handlerBuilder; } @@ -116,129 +102,5 @@ namespace Telegrator.Handlers.Building handlerBuilder.AddTargetedFilters(getFilterringTarget, filters); return handlerBuilder; } - - /// - /// Sets a numeric state keeper with a custom key resolver. - /// - /// The type of the handler builder. - /// The handler builder. - /// The numeric state value. - /// The key resolver for the state. - /// The handler builder for method chaining. - public static TBuilder SetNumericState(this TBuilder handlerBuilder, int myState, IStateKeyResolver keyResolver) - where TBuilder : HandlerBuilderBase - { - handlerBuilder.SetStateKeeper(myState, keyResolver); - return handlerBuilder; - } - - /// - /// Sets a numeric state keeper with a special state and custom key resolver. - /// - /// The type of the handler builder. - /// The handler builder. - /// The special state value. - /// The key resolver for the state. - /// The handler builder for method chaining. - public static TBuilder SetNumericState(this TBuilder handlerBuilder, SpecialState specialState, IStateKeyResolver keyResolver) - where TBuilder : HandlerBuilderBase - { - handlerBuilder.SetStateKeeper(specialState, keyResolver); - return handlerBuilder; - } - - /// - /// Sets a numeric state keeper with the default sender ID resolver. - /// - /// The type of the handler builder. - /// The handler builder. - /// The numeric state value. - /// The handler builder for method chaining. - public static TBuilder SetNumericState(this TBuilder handlerBuilder, int myState) - where TBuilder : HandlerBuilderBase - { - handlerBuilder.SetStateKeeper(myState, new SenderIdResolver()); - return handlerBuilder; - } - - /// - /// Sets a numeric state keeper with a special state and the default sender ID resolver. - /// - /// The type of the handler builder. - /// The handler builder. - /// The special state value. - /// The handler builder for method chaining. - public static TBuilder SetNumericState(this TBuilder handlerBuilder, SpecialState specialState) - where TBuilder : HandlerBuilderBase - { - handlerBuilder.SetStateKeeper(specialState, new SenderIdResolver()); - return handlerBuilder; - } - - /// - /// Sets an enum state keeper with a custom key resolver. - /// - /// The type of the handler builder. - /// The type of the enum state. - /// The handler builder. - /// The enum state value. - /// The key resolver for the state. - /// The handler builder for method chaining. - public static TBuilder SetEnumState(this TBuilder handlerBuilder, TEnum myState, IStateKeyResolver keyResolver) - where TBuilder : HandlerBuilderBase - where TEnum : Enum, IEquatable - { - handlerBuilder.SetStateKeeper>(myState, keyResolver); - return handlerBuilder; - } - - /// - /// Sets an enum state keeper with a special state and custom key resolver. - /// - /// The type of the handler builder. - /// The type of the enum state. - /// The handler builder. - /// The special state value. - /// The key resolver for the state. - /// The handler builder for method chaining. - public static TBuilder SetEnumState(this TBuilder handlerBuilder, SpecialState specialState, IStateKeyResolver keyResolver) - where TBuilder : HandlerBuilderBase - where TEnum : Enum, IEquatable - { - handlerBuilder.SetStateKeeper>(specialState, keyResolver); - return handlerBuilder; - } - - /// - /// Sets an enum state keeper with the default sender ID resolver. - /// - /// The type of the handler builder. - /// The type of the enum state. - /// The handler builder. - /// The enum state value. - /// The handler builder for method chaining. - public static TBuilder SetEnumState(this TBuilder handlerBuilder, TEnum myState) - where TBuilder : HandlerBuilderBase - where TEnum : Enum, IEquatable - { - handlerBuilder.SetStateKeeper>(myState, new SenderIdResolver()); - return handlerBuilder; - } - - /// - /// Sets an enum state keeper with a special state and the default sender ID resolver. - /// - /// The type of the handler builder. - /// The type of the enum state. - /// The handler builder. - /// The special state value. - /// The handler builder for method chaining. - public static TBuilder SetEnumState(this TBuilder handlerBuilder, SpecialState specialState) - where TBuilder : HandlerBuilderBase - where TEnum : Enum, IEquatable - { - handlerBuilder.SetStateKeeper>(specialState, new SenderIdResolver()); - return handlerBuilder; - } } } diff --git a/src/Telegrator/Handlers/CommandHandler.cs b/src/Telegrator/Handlers/CommandHandler.cs index d9d1708..26c0b28 100644 --- a/src/Telegrator/Handlers/CommandHandler.cs +++ b/src/Telegrator/Handlers/CommandHandler.cs @@ -43,6 +43,9 @@ namespace Telegrator.Handlers { string[] split = ReceivedCommand.Split('@'); ReceivedCommand = split[0]; + + if (!split.ElementAtOrDefault(1).Equals(context.BotInfo.User.Username)) + return false; } return true; diff --git a/src/Telegrator/Handlers/HandlerContainer.cs b/src/Telegrator/Handlers/HandlerContainer.cs index 0f0d011..d489b58 100644 --- a/src/Telegrator/Handlers/HandlerContainer.cs +++ b/src/Telegrator/Handlers/HandlerContainer.cs @@ -3,6 +3,7 @@ using Telegram.Bot.Types; using Telegrator.Core; using Telegrator.Core.Descriptors; using Telegrator.Core.Filters; +using Telegrator.Core.States; namespace Telegrator.Handlers { @@ -33,6 +34,9 @@ namespace Telegrator.Handlers /// public IAwaitingProvider AwaitingProvider { get; } + /// + public IStateStorage StateStorage { get; } + /// /// Initializes new instance of /// @@ -45,6 +49,7 @@ namespace Telegrator.Handlers ExtraData = handlerInfo.ExtraData; CompletedFilters = handlerInfo.CompletedFilters; AwaitingProvider = handlerInfo.AwaitingProvider; + StateStorage = handlerInfo.StateStorage; } /// @@ -56,7 +61,7 @@ namespace Telegrator.Handlers /// /// /// - public HandlerContainer(TUpdate actualUpdate, Update handlingUpdate, ITelegramBotClient client, Dictionary extraData, CompletedFiltersList filters, IAwaitingProvider awaitingProvider) + public HandlerContainer(TUpdate actualUpdate, Update handlingUpdate, ITelegramBotClient client, Dictionary extraData, CompletedFiltersList filters, IAwaitingProvider awaitingProvider, IStateStorage stateStorage) { ActualUpdate = actualUpdate; HandlingUpdate = handlingUpdate; @@ -64,6 +69,7 @@ namespace Telegrator.Handlers ExtraData = extraData; CompletedFilters = filters; AwaitingProvider = awaitingProvider; + StateStorage = stateStorage; } /// @@ -75,8 +81,8 @@ namespace Telegrator.Handlers { return new HandlerContainer( HandlingUpdate.GetActualUpdateObject(), - HandlingUpdate, Client, ExtraData, - CompletedFilters, AwaitingProvider); + HandlingUpdate, Client, ExtraData, CompletedFilters, + AwaitingProvider, StateStorage); } /// @@ -89,8 +95,8 @@ namespace Telegrator.Handlers { return new HandlerContainer( other.HandlingUpdate.GetActualUpdateObject(), - other.HandlingUpdate, other.Client, other.ExtraData, - other.CompletedFilters, other.AwaitingProvider); + other.HandlingUpdate, other.Client, other.ExtraData, other.CompletedFilters, + other.AwaitingProvider, other.StateStorage); } } } diff --git a/src/Telegrator/Mediation/DefaultRouterExceptionHandler.cs b/src/Telegrator/Mediation/DefaultRouterExceptionHandler.cs index ef0c581..4d1d488 100644 --- a/src/Telegrator/Mediation/DefaultRouterExceptionHandler.cs +++ b/src/Telegrator/Mediation/DefaultRouterExceptionHandler.cs @@ -28,7 +28,7 @@ namespace Telegrator.Mediation { _handler.Invoke(botClient, exception, source, cancellationToken); } - finally + catch { _ = 0xBAD + 0xC0DE; } diff --git a/src/Telegrator/Mediation/UpdateRouter.cs b/src/Telegrator/Mediation/UpdateRouter.cs index d14b545..981694f 100644 --- a/src/Telegrator/Mediation/UpdateRouter.cs +++ b/src/Telegrator/Mediation/UpdateRouter.cs @@ -7,6 +7,7 @@ using Telegrator.Core; using Telegrator.Core.Descriptors; using Telegrator.Core.Filters; using Telegrator.Core.Handlers; +using Telegrator.Core.States; using Telegrator.Handlers.Diagnostics; using Telegrator.Logging; @@ -21,6 +22,7 @@ namespace Telegrator.Mediation private readonly TelegratorOptions _options; private readonly IHandlersProvider _handlersProvider; private readonly IAwaitingProvider _awaitingProvider; + private readonly IStateStorage _stateStorage; private readonly IUpdateHandlersPool _HandlersPool; private readonly ITelegramBotInfo _botInfo; @@ -30,6 +32,9 @@ namespace Telegrator.Mediation /// public IAwaitingProvider AwaitingProvider => _awaitingProvider; + /// + public IStateStorage StateStorage => _stateStorage; + /// public TelegratorOptions Options => _options; @@ -47,13 +52,15 @@ namespace Telegrator.Mediation /// /// The provider for regular handlers. /// The provider for awaiting handlers. + /// The state storage. /// The bot configuration options. /// - public UpdateRouter(IHandlersProvider handlersProvider, IAwaitingProvider awaitingProvider, TelegratorOptions options, ITelegramBotInfo botInfo) + public UpdateRouter(IHandlersProvider handlersProvider, IAwaitingProvider awaitingProvider, IStateStorage stateStorage, TelegratorOptions options, ITelegramBotInfo botInfo) { _options = options; _handlersProvider = handlersProvider; _awaitingProvider = awaitingProvider; + _stateStorage = stateStorage; _HandlersPool = new UpdateHandlersPool(this, _options, _options.GlobalCancellationToken); _botInfo = botInfo; } @@ -235,7 +242,7 @@ namespace Telegrator.Mediation }; UpdateHandlerBase handlerInstance = provider.GetHandlerInstance(descriptor, cancellationToken); - FilterExecutionContext filterContext = new FilterExecutionContext(_botInfo, update, update, data, []); + FilterExecutionContext filterContext = new FilterExecutionContext(this, _botInfo, update, update, data, []); if (descriptor.Filters != null) { @@ -254,7 +261,7 @@ namespace Telegrator.Mediation } } - return new DescribedHandlerDescriptor(descriptor, this, AwaitingProvider, client, handlerInstance, filterContext, descriptor.DisplayString); + return new DescribedHandlerDescriptor(descriptor, this, AwaitingProvider, StateStorage, client, handlerInstance, filterContext, descriptor.DisplayString); } /// diff --git a/src/Telegrator/StateKeeping/ArrayStateKeeper.cs b/src/Telegrator/StateKeeping/ArrayStateKeeper.cs deleted file mode 100644 index 2294e86..0000000 --- a/src/Telegrator/StateKeeping/ArrayStateKeeper.cs +++ /dev/null @@ -1,59 +0,0 @@ -using Telegrator.Core.StateKeeping; - -namespace Telegrator.StateKeeping -{ - /// - /// Abstract base class for state keepers that manage state transitions using an array of predefined states. - /// Provides forward and backward navigation through a fixed sequence of states. - /// - /// The type of the key used to identify state contexts. - /// The type of the state values. Must be non-null. - /// The array of states that define the allowed state sequence. - public abstract class ArrayStateKeeper(params TState[] states) : StateKeeperBase where TState : notnull where TKey : notnull - { - /// - /// The array of states that defines the allowed state sequence for navigation. - /// - protected readonly TState[] ArrayStates = states; - - /// - /// Moves to the previous state in the array sequence. - /// - /// The current state to move backward from. - /// The key parameter (unused in this implementation). - /// The previous state in the array sequence. - /// Thrown when the current state is not found in the array. - /// Thrown when trying to move backward from the first state. - protected override TState MoveBackward(TState currentState, TKey _) - { - int index = Array.IndexOf(ArrayStates, currentState); - if (index == -1) - throw new ArgumentException("Cannot resolve current state"); - - if (index == 0) - throw new IndexOutOfRangeException("This state cannot be moved backward"); - - return ArrayStates[index - 1]; - } - - /// - /// Moves to the next state in the array sequence. - /// - /// The current state to move forward from. - /// The key parameter (unused in this implementation). - /// The next state in the array sequence. - /// Thrown when the current state is not found in the array. - /// Thrown when trying to move forward from the last state. - protected override TState MoveForward(TState currentState, TKey _) - { - int index = Array.IndexOf(ArrayStates, currentState); - if (index == -1) - throw new ArgumentException("Cannot resolve current state"); - - if (index == ArrayStates.Length - 1) - throw new IndexOutOfRangeException("This state cannot be moved forward"); - - return ArrayStates[index + 1]; - } - } -} diff --git a/src/Telegrator/StateKeeping/EnumStateKeeper.cs b/src/Telegrator/StateKeeping/EnumStateKeeper.cs deleted file mode 100644 index 68c0579..0000000 --- a/src/Telegrator/StateKeeping/EnumStateKeeper.cs +++ /dev/null @@ -1,75 +0,0 @@ -using Telegrator.Annotations.StateKeeping; -using Telegrator.Core.Handlers; - -namespace Telegrator.StateKeeping -{ - /// - /// State keeper implementation for enum-based states. - /// Automatically creates an array of all enum values for state navigation. - /// - /// The enum type to be used for state management. - public class EnumStateKeeper() : ArrayStateKeeper(Enum.GetValues(typeof(TEnum)).Cast().ToArray()) where TEnum : Enum - { - /// - /// Gets the default state, which is the first value in the enum. - /// - public override TEnum DefaultState => ArrayStates.ElementAt(0); - } - - /// - /// Extension methods for working with enum-based states in handler containers. - /// Provides convenient methods for state management operations. - /// - public static partial class StateHandlerContainerExtensions - { - /// - /// Gets the enum state keeper for the specified enum type. - /// - /// The enum type to get the state keeper for. - /// The handler container (unused parameter for extension method syntax). - /// The enum state keeper instance. - public static EnumStateKeeper EnumStateKeeper(this IHandlerContainer _) where TEnum : Enum - => EnumStateAttribute.Shared; - - /// - /// Creates a new enum state for the current update. - /// - /// The enum type for state management. - /// The handler container. - public static void CreateEnumState(this IHandlerContainer container) where TEnum : Enum - => container.EnumStateKeeper().CreateState(container.HandlingUpdate); - - /// - /// Deletes the enum state for the current update. - /// - /// The enum type for state management. - /// The handler container. - public static void DeleteEnumState(this IHandlerContainer container) where TEnum : Enum - => container.EnumStateKeeper().DeleteState(container.HandlingUpdate); - - /// - /// Sets the enum state to a specific value for the current update. - /// - /// The enum type for state management. - /// The handler container. - /// The new state value. If null, uses the default state. - public static void SetEnumState(this IHandlerContainer container, TEnum? newState) where TEnum : Enum - => container.EnumStateKeeper().SetState(container.HandlingUpdate, newState ?? EnumStateAttribute.DefaultState); - - /// - /// Moves the enum state forward to the next value in the enum sequence. - /// - /// The enum type for state management. - /// The handler container. - public static void ForwardEnumState(this IHandlerContainer container) where TEnum : Enum - => container.EnumStateKeeper().MoveForward(container.HandlingUpdate); - - /// - /// Moves the enum state backward to the previous value in the enum sequence. - /// - /// The enum type for state management. - /// The handler container. - public static void BackwardEnumState(this IHandlerContainer container) where TEnum : Enum - => container.EnumStateKeeper().MoveBackward(container.HandlingUpdate); - } -} diff --git a/src/Telegrator/StateKeeping/NumericStateKeeper.cs b/src/Telegrator/StateKeeping/NumericStateKeeper.cs deleted file mode 100644 index 3c54557..0000000 --- a/src/Telegrator/StateKeeping/NumericStateKeeper.cs +++ /dev/null @@ -1,92 +0,0 @@ -using Telegrator.Annotations.StateKeeping; -using Telegrator.Core.Handlers; -using Telegrator.Core.StateKeeping; - -namespace Telegrator.StateKeeping -{ - /// - /// State keeper that manages numeric (integer) states for chat sessions. - /// Inherits from with long keys and int states. - /// Provides automatic increment/decrement functionality for state transitions. - /// - public class NumericStateKeeper : StateKeeperBase - { - /// - /// Gets the default state value, which is 1. - /// - public override int DefaultState => 1; - - /// - /// Moves the numeric state backward by decrementing the current state value. - /// - /// The current numeric state value - /// The chat ID (unused in this implementation) - /// The decremented state value - protected override int MoveBackward(int currentState, long _) - { - return currentState - 1; - } - - /// - /// Moves the numeric state forward by incrementing the current state value. - /// - /// The current numeric state value - /// The chat ID (unused in this implementation) - /// The incremented state value - protected override int MoveForward(int currentState, long _) - { - return currentState + 1; - } - } - - /// - /// Provides extension methods for managing numeric states in handler containers. - /// - public static partial class StateHandlerContainerExtensions - { - /// - /// Gets the numeric state keeper instance associated with the handler container. - /// - /// The handler container instance - /// The instance - public static NumericStateKeeper NumericStateKeeper(this IHandlerContainer _) - => NumericStateAttribute.Shared; - - /// - /// Creates a new numeric state for the current update being handled. - /// - /// The handler container instance - public static void CreateNumericState(this IHandlerContainer container) - => container.NumericStateKeeper().CreateState(container.HandlingUpdate); - - /// - /// Deletes the numeric state for the current update being handled. - /// - /// The handler container instance - public static void DeleteNumericState(this IHandlerContainer container) - => container.NumericStateKeeper().DeleteState(container.HandlingUpdate); - - /// - /// Sets the numeric state for the current update being handled. - /// If the new state is null, uses the default state from the state keeper. - /// - /// The handler container instance - /// The new numeric state to set, or null to use default - public static void SetNumericState(this IHandlerContainer container, int? newState) - => container.NumericStateKeeper().SetState(container.HandlingUpdate, newState ?? NumericStateAttribute.DefaultState); - - /// - /// Moves the numeric state forward by incrementing the current value. - /// - /// The handler container instance - public static void ForwardNumericState(this IHandlerContainer container) - => container.NumericStateKeeper().MoveForward(container.HandlingUpdate); - - /// - /// Moves the numeric state backward by decrementing the current value. - /// - /// The handler container instance - public static void BackwardNumericState(this IHandlerContainer container) - => container.NumericStateKeeper().MoveBackward(container.HandlingUpdate); - } -} diff --git a/src/Telegrator/StateKeeping/SenderIdResolver.cs b/src/Telegrator/StateKeeping/SenderIdResolver.cs deleted file mode 100644 index af53c82..0000000 --- a/src/Telegrator/StateKeeping/SenderIdResolver.cs +++ /dev/null @@ -1,21 +0,0 @@ -using Telegram.Bot.Types; -using Telegrator.Core.StateKeeping; - -namespace Telegrator.StateKeeping -{ - /// - /// Resolves sender ID from Telegram updates for state management purposes. - /// Extracts the sender identifier from various types of updates to provide a consistent key for state operations. - /// - public class SenderIdResolver : IStateKeyResolver - { - /// - /// Resolves the sender ID from a Telegram update. - /// - /// The Telegram update to extract the sender ID from. - /// The sender ID as a long value. - /// Thrown when the update does not contain a valid sender ID. - public long ResolveKey(Update keySource) - => keySource.GetSenderId() ?? throw new ArgumentException("Cannot resolve SenderID for this Update"); - } -} diff --git a/src/Telegrator/StateKeeping/StringStateKeeper.cs b/src/Telegrator/StateKeeping/StringStateKeeper.cs deleted file mode 100644 index 13832ed..0000000 --- a/src/Telegrator/StateKeeping/StringStateKeeper.cs +++ /dev/null @@ -1,87 +0,0 @@ -using Telegrator.Annotations.StateKeeping; -using Telegrator.Core.Handlers; -using Telegrator.Core.StateKeeping; - -namespace Telegrator.StateKeeping -{ - /// - /// State keeper that manages string-based states for chat sessions. - /// - public class StringStateKeeper() : StateKeeperBase() - { - /// - /// Gets the default state value, which is an empty string. - /// - public override string DefaultState => string.Empty; - - /// - protected override string MoveBackward(string currentState, long currentKey) - { - throw new NotImplementedException(); - } - - /// - protected override string MoveForward(string currentState, long currentKey) - { - throw new NotImplementedException(); - } - } - - /// - /// Provides extension methods for managing string states in handler containers. - /// - public static partial class StateHandlerContainerExtensions - { - /// - /// Gets the string state keeper instance associated with the handler container. - /// - /// The handler container instance - /// The instance - public static StringStateKeeper StringStateKeeper(this IHandlerContainer _) - => StringStateAttribute.Shared; - - /// - /// Creates a new string state for the current update being handled. - /// - /// The handler container instance - public static void CreateStringState(this IHandlerContainer container) - => container.StringStateKeeper().CreateState(container.HandlingUpdate); - - /// - /// Deletes the string state for the current update being handled. - /// - /// The handler container instance - public static void DeleteStringState(this IHandlerContainer container) - => container.StringStateKeeper().DeleteState(container.HandlingUpdate); - - /// - /// Sets the string state for the current update being handled. - /// If the new state is null, uses the default state from the state keeper. - /// - /// The handler container instance - /// The new string state to set, or null to use default - public static void SetStringState(this IHandlerContainer container, string? newState) - => container.StringStateKeeper().SetState(container.HandlingUpdate, newState ?? StringStateAttribute.DefaultState); - - /* - public static string GetStringState(this IHandlerContainer container, string key) - => container.StringStateKeeper().GetState() - */ - - /* - /// - /// Moves the string state forward to the next state in the sequence. - /// - /// The handler container instance - public static void ForwardStringState(this IHandlerContainer container) - => container.StringStateKeeper().MoveForward(container.HandlingUpdate); - - /// - /// Moves the string state backward to the previous state in the sequence. - /// - /// The handler container instance - public static void BackwardStringState(this IHandlerContainer container) - => container.StringStateKeeper().MoveBackward(container.HandlingUpdate); - */ - } -} diff --git a/src/Telegrator/StateKeeping/ChatIdResolver.cs b/src/Telegrator/States/ChatIdResolver.cs similarity index 69% rename from src/Telegrator/StateKeeping/ChatIdResolver.cs rename to src/Telegrator/States/ChatIdResolver.cs index d8d34bd..2159510 100644 --- a/src/Telegrator/StateKeeping/ChatIdResolver.cs +++ b/src/Telegrator/States/ChatIdResolver.cs @@ -1,13 +1,13 @@ using Telegram.Bot.Types; -using Telegrator.Core.StateKeeping; +using Telegrator.Core.States; -namespace Telegrator.StateKeeping +namespace Telegrator.States { /// /// Resolves chat ID from Telegram updates for state management purposes. /// Extracts the chat identifier from various types of updates to provide a consistent key for state operations. /// - public class ChatIdResolver : IStateKeyResolver + public class ChatIdResolver : IStateKeyResolver { /// /// Resolves the chat ID from a Telegram update. @@ -15,7 +15,7 @@ namespace Telegrator.StateKeeping /// The Telegram update to extract the chat ID from. /// The chat ID as a long value. /// Thrown when the update does not contain a valid chat ID. - public long ResolveKey(Update keySource) - => keySource.GetChatId() ?? throw new ArgumentException("Cannot resolve ChatID for this Update"); + public string? ResolveKey(Update keySource) + => keySource.GetChatId()?.ToString() ?? throw new ArgumentException("Cannot resolve ChatID for this Update"); } } diff --git a/src/Telegrator/States/DefaultStateStorage.cs b/src/Telegrator/States/DefaultStateStorage.cs new file mode 100644 index 0000000..1cdcd30 --- /dev/null +++ b/src/Telegrator/States/DefaultStateStorage.cs @@ -0,0 +1,43 @@ +using System.Collections.Concurrent; +using Telegrator.Core.States; + +namespace Telegrator.States; + +public class DefaultStateStorage : IStateStorage +{ + private readonly ConcurrentDictionary storage = []; + + public Task DeleteAsync(string key, CancellationToken cancellationToken = default) + { + if (key is null) + throw new ArgumentNullException(nameof(key)); + + if (!storage.TryRemove(key, out object value)) + throw new Exception("Failed to remove key '" + key + "' from storage."); + + return Task.CompletedTask; + } + + public Task GetAsync(string key, CancellationToken ccancellationTokent = default) + { + if (key is null) + throw new ArgumentNullException(nameof(key)); + + if (storage.TryGetValue(key, out object value) && value is T) + return Task.FromResult((T?)value); + + return Task.FromResult(default(T)); + } + + public Task SetAsync(string key, T state, CancellationToken cancellationToken = default) + { + if (key is null) + throw new ArgumentNullException(nameof(key)); + + if (state is null) + throw new ArgumentNullException(nameof(state)); + + storage.Set(key, state); + return Task.CompletedTask; + } +} diff --git a/src/Telegrator/States/EnumStateMachine.cs b/src/Telegrator/States/EnumStateMachine.cs new file mode 100644 index 0000000..af0aa66 --- /dev/null +++ b/src/Telegrator/States/EnumStateMachine.cs @@ -0,0 +1,62 @@ +using Telegrator.Core.States; + +namespace Telegrator.States; + +/// +/// State machine implementation for enum-based states. +/// Automatically creates an array of all enum values for state navigation. +/// +/// The enum type to be used for state management. +public class EnumStateMachine : IStateMachine where TEnum : struct, Enum, IEquatable +{ + private readonly TEnum[] _states = Enum.GetValues(typeof(TEnum)).Cast().ToArray(); + private TEnum _defaultState => _states.FirstOrDefault(); + + /// + public async Task Current(IStateStorage storage, string updateKey, CancellationToken cancellationToken = default) + { + string key = FormatKey(updateKey); + TEnum state = await storage.GetAsync(key, cancellationToken); + + return EqualityComparer.Default.Equals(state, default) + ? _defaultState : state; + } + + /// + public async Task Advance(IStateStorage storage, string updateKey, CancellationToken cancellationToken = default) + { + string key = FormatKey(updateKey); + TEnum currentState = await storage.GetAsync(key, cancellationToken); + + int currentIndex = Array.IndexOf(_states, currentState); + if (currentIndex < _states.Length - 1) + { + var nextState = _states[currentIndex + 1]; + await storage.SetAsync(key, nextState, cancellationToken); + } + } + + /// + public async Task Retreat(IStateStorage storage, string updateKey, CancellationToken cancellationToken = default) + { + string key = FormatKey(updateKey); + TEnum currentState = await storage.GetAsync(key, cancellationToken); + + int currentIndex = Array.IndexOf(_states, currentState); + if (currentIndex > 0) + { + var nextState = _states[currentIndex - 1]; + await storage.SetAsync(key, nextState, cancellationToken); + } + } + + /// + public async Task Reset(IStateStorage storage, string updateKey, CancellationToken cancellationToken = default) + { + string key = FormatKey(updateKey); + await storage.SetAsync(key, _defaultState, cancellationToken); + } + + private static string FormatKey(string updateKey) + => typeof(TEnum).Name + ":" + updateKey; +} diff --git a/src/Telegrator/States/SenderIdResolver.cs b/src/Telegrator/States/SenderIdResolver.cs new file mode 100644 index 0000000..528134b --- /dev/null +++ b/src/Telegrator/States/SenderIdResolver.cs @@ -0,0 +1,20 @@ +using Telegram.Bot.Types; +using Telegrator.Core.States; + +namespace Telegrator.States; + +/// +/// Resolves sender ID from Telegram updates for state management purposes. +/// Extracts the sender identifier from various types of updates to provide a consistent key for state operations. +/// +public class SenderIdResolver : IStateKeyResolver +{ + /// + /// Resolves the sender ID from a Telegram update. + /// + /// The Telegram update to extract the sender ID from. + /// The sender ID as a long value. + /// Thrown when the update does not contain a valid sender ID. + public string ResolveKey(Update keySource) + => keySource.GetSenderId()?.ToString() ?? throw new ArgumentException("Cannot resolve SenderID for this Update"); +} diff --git a/src/Telegrator/States/StateMachine.cs b/src/Telegrator/States/StateMachine.cs new file mode 100644 index 0000000..aa2ef72 --- /dev/null +++ b/src/Telegrator/States/StateMachine.cs @@ -0,0 +1,63 @@ +using Telegram.Bot.Types; +using Telegrator.Core.States; + +namespace Telegrator.States; + +public class StateMachine(IStateStorage stateStorage, Update handlingUpdate) + where TMachine : IStateMachine, new() + where TState : IEquatable +{ + private readonly IStateStorage _stateStorage = stateStorage; + private readonly Update _handlingUpdate = handlingUpdate; + private readonly IStateMachine _stateMachine = new TMachine(); + + public IStateKeyResolver? KeyResolver; + + public async Task Advance(CancellationToken cancellationToken = default) + { + if (KeyResolver is null) + throw new InvalidOperationException("KeyResolver is not set."); + + string? key = KeyResolver.ResolveKey(_handlingUpdate); + if (key is null) + throw new InvalidOperationException("Failed to resolve Update key"); + + await _stateMachine.Advance(_stateStorage, key, cancellationToken); + } + + public async Task Current(CancellationToken cancellationToken = default) + { + if (KeyResolver is null) + throw new InvalidOperationException("KeyResolver is not set."); + + string? key = KeyResolver.ResolveKey(_handlingUpdate); + if (key is null) + throw new InvalidOperationException("Failed to resolve Update key"); + + return await _stateMachine.Current(_stateStorage, key, cancellationToken); + } + + public async Task Reset(CancellationToken cancellationToken = default) + { + if (KeyResolver is null) + throw new InvalidOperationException("KeyResolver is not set."); + + string? key = KeyResolver.ResolveKey(_handlingUpdate); + if (key is null) + throw new InvalidOperationException("Failed to resolve Update key"); + + await _stateMachine.Reset(_stateStorage, key, cancellationToken); + } + + public async Task Retreat(CancellationToken cancellationToken = default) + { + if (KeyResolver is null) + throw new InvalidOperationException("KeyResolver is not set."); + + string? key = KeyResolver.ResolveKey(_handlingUpdate); + if (key is null) + throw new InvalidOperationException("Failed to resolve Update key"); + + await _stateMachine.Retreat(_stateStorage, key, cancellationToken); + } +} diff --git a/src/Telegrator/Telegrator.csproj b/src/Telegrator/Telegrator.csproj index 8fcee53..9ad50dc 100644 --- a/src/Telegrator/Telegrator.csproj +++ b/src/Telegrator/Telegrator.csproj @@ -14,7 +14,7 @@ True Telegrator : Telegram.Bot mediator framework - 1.16.3 + 1.16.4 Rikitav Tim4ik Rikitav Tim4ik https://github.com/Rikitav/Telegrator @@ -44,4 +44,8 @@ + + + + diff --git a/src/Telegrator/TelegratorClient.cs b/src/Telegrator/TelegratorClient.cs index fbe2140..b64c0da 100644 --- a/src/Telegrator/TelegratorClient.cs +++ b/src/Telegrator/TelegratorClient.cs @@ -4,6 +4,7 @@ using Telegrator.Core; using Telegrator.Logging; using Telegrator.Mediation; using Telegrator.Providers; +using Telegrator.States; namespace Telegrator { @@ -75,8 +76,9 @@ namespace Telegrator HandlersProvider handlerProvider = new HandlersProvider(Handlers, Options); AwaitingProvider awaitingProvider = new AwaitingProvider(Options); + DefaultStateStorage stateStorage = new DefaultStateStorage(); - updateRouter = new UpdateRouter(handlerProvider, awaitingProvider, Options, BotInfo); + updateRouter = new UpdateRouter(handlerProvider, awaitingProvider, stateStorage, Options, BotInfo); // Log startup TelegratorLogging.LogInformation($"Telegrator bot starting up - BotId: {BotInfo.User.Id}, Username: {BotInfo.User.Username}, MaxParallelHandlers: {Options.MaximumParallelWorkingHandlers ?? -1}"); diff --git a/src/Telegrator/TypesExtensions.cs b/src/Telegrator/TypesExtensions.cs index 56efd1f..d1f6a3a 100644 --- a/src/Telegrator/TypesExtensions.cs +++ b/src/Telegrator/TypesExtensions.cs @@ -3,14 +3,13 @@ using Telegram.Bot.Types; using Telegram.Bot.Types.Enums; using Telegram.Bot.Types.Payments; using Telegrator.Annotations; -using Telegrator.Attributes; using Telegrator.Core; using Telegrator.Core.Descriptors; using Telegrator.Core.Handlers; using Telegrator.Core.Handlers.Building; -using Telegrator.Core.StateKeeping; +using Telegrator.Core.States; using Telegrator.Handlers.Building; -using Telegrator.StateKeeping; +using Telegrator.States; namespace Telegrator { @@ -187,17 +186,6 @@ namespace Telegrator /// An awaiter builder for callback query updates. public static IAwaiterHandlerBuilder AwaitCallbackQuery(this IHandlerContainer container) => container.AwaitUpdate(UpdateType.CallbackQuery); - - /// - /// Gets a state keeper instance for the specified types. - /// - /// The type of the state key. - /// The type of the state value. - /// The type of the state keeper. - /// The handler container (unused). - /// The state keeper instance. - public static TKeeper GetStateKeeper(this IHandlerContainer _) where TKey : notnull where TState : IEquatable where TKeeper : StateKeeperBase, new() - => StateKeeperAttribute.Shared; } /// @@ -296,6 +284,47 @@ namespace Telegrator } } + public static class StateStorageExtensions + { + public static StateMachine, TState> GetStateMachine(this IStateStorage stateStorage, Update handlingUpdate) + where TState : struct, Enum, IEquatable + => new StateMachine, TState>(stateStorage, handlingUpdate); + + public static StateMachine GetStateMachine(this IStateStorage stateStorage, Update handlingUpdate) + where TMachine : IStateMachine, new() + where TState : IEquatable + => new StateMachine(stateStorage, handlingUpdate); + + public static StateMachine ByChatId(this IStateStorage stateStorage, Update handlingUpdate) + where TMachine : IStateMachine, new() + where TState : IEquatable + => new StateMachine(stateStorage, handlingUpdate).ByChatId(); + + public static StateMachine BySenderId(this IStateStorage stateStorage, Update handlingUpdate) + where TMachine : IStateMachine, new() + where TState : IEquatable + => new StateMachine(stateStorage, handlingUpdate).BySenderId(); + } + + public static class StateMachineExtensions + { + public static StateMachine ByChatId(this StateMachine stateMachine) + where TMachine : IStateMachine, new() + where TState : IEquatable + { + stateMachine.KeyResolver = new ChatIdResolver(); + return stateMachine; + } + + public static StateMachine BySenderId(this StateMachine stateMachine) + where TMachine : IStateMachine, new() + where TState : IEquatable + { + stateMachine.KeyResolver = new SenderIdResolver(); + return stateMachine; + } + } + /// /// Extension methods for handlers collections. /// Provides convenient methods for creating implicit handlers. diff --git a/tests/Telegrator.Tests/Filters/FilterTests.cs b/tests/Telegrator.Tests/Filters/FilterTests.cs index 860386b..4b5b2e7 100644 --- a/tests/Telegrator.Tests/Filters/FilterTests.cs +++ b/tests/Telegrator.Tests/Filters/FilterTests.cs @@ -29,7 +29,7 @@ public class FilterTests { // Arrange (Given) - подготовка тестовых данных var anyFilter = Filter.Any(); - var context = new FilterExecutionContext(new TelegramBotInfo(null), new Update(), new Update(), new Dictionary(), new CompletedFiltersList()); + var context = new FilterExecutionContext(null, new TelegramBotInfo(null), new Update(), new Update(), new Dictionary(), new CompletedFiltersList()); // Act (When) - выполнение тестируемого действия var result = anyFilter.CanPass(context); @@ -49,7 +49,7 @@ public class FilterTests // Arrange var alwaysTrueFilter = Filter.Any(); var reverseFilter = alwaysTrueFilter.Not(); - var context = new FilterExecutionContext(new TelegramBotInfo(null), new Update(), new Update(), new Dictionary(), new CompletedFiltersList()); + var context = new FilterExecutionContext(null, new TelegramBotInfo(null), new Update(), new Update(), new Dictionary(), new CompletedFiltersList()); // Act var result = reverseFilter.CanPass(context); @@ -74,7 +74,7 @@ public class FilterTests var firstFilter = Filter.If(_ => firstResult); var secondFilter = Filter.If(_ => secondResult); var andFilter = firstFilter.And(secondFilter); - var context = new FilterExecutionContext(new TelegramBotInfo(null), new Update(), new Update(), new Dictionary(), new CompletedFiltersList()); + var context = new FilterExecutionContext(null, new TelegramBotInfo(null), new Update(), new Update(), new Dictionary(), new CompletedFiltersList()); // Act var result = andFilter.CanPass(context); @@ -99,7 +99,7 @@ public class FilterTests var firstFilter = Filter.If(_ => firstResult); var secondFilter = Filter.If(_ => secondResult); var orFilter = firstFilter.Or(secondFilter); - var context = new FilterExecutionContext(new TelegramBotInfo(null), new Update(), new Update(), new Dictionary(), new CompletedFiltersList()); + var context = new FilterExecutionContext(null, new TelegramBotInfo(null), new Update(), new Update(), new Dictionary(), new CompletedFiltersList()); // Act var result = orFilter.CanPass(context); @@ -122,7 +122,7 @@ public class FilterTests var filter3 = Filter.If(_ => false); var compiledFilter = new CompiledFilter(filter1, filter2, filter3); - var context = new FilterExecutionContext(new TelegramBotInfo(null), new Update(), new Update(), new Dictionary(), new CompletedFiltersList()); + var context = new FilterExecutionContext(null, new TelegramBotInfo(null), new Update(), new Update(), new Dictionary(), new CompletedFiltersList()); // Act var result = compiledFilter.CanPass(context); @@ -164,7 +164,7 @@ public class FilterTests wasCalled = true; return true; }); - var context = new FilterExecutionContext(new TelegramBotInfo(null), new Update(), new Update(), new Dictionary(), new CompletedFiltersList()); + var context = new FilterExecutionContext(null, new TelegramBotInfo(null), new Update(), new Update(), new Dictionary(), new CompletedFiltersList()); // Act var result = functionFilter.CanPass(context);