* 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
This commit is contained in:
@@ -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<MethodDeclarationSyntax>().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;
|
||||
|
||||
@@ -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<T>(this T statememnt, int times) where T : StatementSyntax => statememnt
|
||||
.WithoutTrivia().WithLeadingTrivia(TabulationTrivia.Repeat(times)).WithTrailingTrivia(NewLineTrivia);
|
||||
|
||||
public static T DecorateMember<T>(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<T>(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));
|
||||
}
|
||||
}
|
||||
@@ -203,7 +203,7 @@
|
||||
<see cref="T:Microsoft.Extensions.Logging.ILogger"/> of this router
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:Telegrator.Polling.HostUpdateRouter.#ctor(Telegrator.Core.IHandlersProvider,Telegrator.Core.IAwaitingProvider,Microsoft.Extensions.Options.IOptions{Telegrator.TelegratorOptions},Telegrator.Core.ITelegramBotInfo,Microsoft.Extensions.Logging.ILogger{Telegrator.Polling.HostUpdateRouter})">
|
||||
<member name="M:Telegrator.Polling.HostUpdateRouter.#ctor(Telegrator.Core.IHandlersProvider,Telegrator.Core.IAwaitingProvider,Telegrator.Core.States.IStateStorage,Microsoft.Extensions.Options.IOptions{Telegrator.TelegratorOptions},Telegrator.Core.ITelegramBotInfo,Microsoft.Extensions.Logging.ILogger{Telegrator.Polling.HostUpdateRouter})">
|
||||
<inheritdoc/>
|
||||
</member>
|
||||
<member name="M:Telegrator.Polling.HostUpdateRouter.HandleUpdateAsync(Telegram.Bot.ITelegramBotClient,Telegram.Bot.Types.Update,System.Threading.CancellationToken)">
|
||||
|
||||
+91
-1289
File diff suppressed because it is too large
Load Diff
@@ -15,7 +15,7 @@
|
||||
<EnforceCodeStyleInBuild>True</EnforceCodeStyleInBuild>
|
||||
|
||||
<Title>Telegrator.Hosting.Web</Title>
|
||||
<Version>1.16.3</Version>
|
||||
<Version>1.16.4</Version>
|
||||
<Authors>Rikitav Tim4ik</Authors>
|
||||
<Company>Rikitav Tim4ik</Company>
|
||||
<RepositoryUrl>https://github.com/Rikitav/Telegrator</RepositoryUrl>
|
||||
|
||||
@@ -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<TelegratorOptions> options,
|
||||
ITelegramBotInfo botInfo,
|
||||
ILogger<HostUpdateRouter> logger) : base(handlersProvider, awaitingProvider, options.Value, botInfo)
|
||||
ILogger<HostUpdateRouter> logger) : base(handlersProvider, awaitingProvider, stateStorage, options.Value, botInfo)
|
||||
{
|
||||
Logger = logger;
|
||||
ExceptionHandler = new DefaultRouterExceptionHandler(HandleException);
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
<EnforceCodeStyleInBuild>True</EnforceCodeStyleInBuild>
|
||||
|
||||
<Title>Telegrator.Hosting</Title>
|
||||
<Version>1.16.3</Version>
|
||||
<Version>1.16.4</Version>
|
||||
<Authors>Rikitav Tim4ik</Authors>
|
||||
<Company>Rikitav Tim4ik</Company>
|
||||
<RepositoryUrl>https://github.com/Rikitav/Telegrator</RepositoryUrl>
|
||||
|
||||
@@ -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<TKey, TValue>(TValue? value) : UpdateFilterAttribute<Update>(new StateKeyFilter<TKey, TValue>(value))
|
||||
where TKey : IStateKeyResolver, new()
|
||||
where TValue : IEquatable<TValue>
|
||||
{
|
||||
public TValue? Value => value;
|
||||
|
||||
public override UpdateType[] AllowedTypes => Update.AllTypes;
|
||||
|
||||
public override Update? GetFilterringTarget(Update update)
|
||||
{
|
||||
return update;
|
||||
}
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
using Telegrator.StateKeeping;
|
||||
using Telegrator.Attributes;
|
||||
using Telegrator.Core.StateKeeping;
|
||||
|
||||
namespace Telegrator.Annotations.StateKeeping
|
||||
{
|
||||
/// <summary>
|
||||
/// Attribute for managing enum-based states in Telegram bot handlers.
|
||||
/// Provides a convenient way to associate enum values with state management functionality.
|
||||
/// </summary>
|
||||
/// <typeparam name="TEnum">The enum type to be used for state management.</typeparam>
|
||||
public class EnumStateAttribute<TEnum> : StateKeeperAttribute<long, TEnum, EnumStateKeeper<TEnum>> where TEnum : Enum
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the EnumStateAttribute with a special state and custom key resolver.
|
||||
/// </summary>
|
||||
/// <param name="specialState">The special state to be managed.</param>
|
||||
/// <param name="keyResolver">The resolver for extracting keys from updates.</param>
|
||||
public EnumStateAttribute(SpecialState specialState, IStateKeyResolver<long> keyResolver)
|
||||
: base(specialState, keyResolver) { }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the EnumStateAttribute with a specific enum state and custom key resolver.
|
||||
/// </summary>
|
||||
/// <param name="myState">The specific enum state to be managed.</param>
|
||||
/// <param name="keyResolver">The resolver for extracting keys from updates.</param>
|
||||
public EnumStateAttribute(TEnum myState, IStateKeyResolver<long> keyResolver)
|
||||
: base(myState, keyResolver) { }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the EnumStateAttribute with a special state and default sender ID resolver.
|
||||
/// </summary>
|
||||
/// <param name="specialState">The special state to be managed.</param>
|
||||
public EnumStateAttribute(SpecialState specialState)
|
||||
: base(specialState, new SenderIdResolver()) { }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the EnumStateAttribute with a specific enum state and default sender ID resolver.
|
||||
/// </summary>
|
||||
/// <param name="myState">The specific enum state to be managed.</param>
|
||||
public EnumStateAttribute(TEnum myState)
|
||||
: this(myState, new SenderIdResolver()) { }
|
||||
}
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
using Telegrator.StateKeeping;
|
||||
using Telegrator.Attributes;
|
||||
using Telegrator.Core.StateKeeping;
|
||||
|
||||
namespace Telegrator.Annotations.StateKeeping
|
||||
{
|
||||
/// <summary>
|
||||
/// Attribute for associating a handler or method with a numeric (integer) state keeper.
|
||||
/// Provides constructors for flexible state and key resolver configuration.
|
||||
/// </summary>
|
||||
public class NumericStateAttribute : StateKeeperAttribute<long, int, NumericStateKeeper>
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes the attribute with a special state and a custom key resolver.
|
||||
/// </summary>
|
||||
/// <param name="specialState">The special state to associate</param>
|
||||
/// <param name="keyResolver">The key resolver for state keeping</param>
|
||||
public NumericStateAttribute(SpecialState specialState, IStateKeyResolver<long> keyResolver)
|
||||
: base(specialState, keyResolver) { }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the attribute with a specific numeric state and a custom key resolver.
|
||||
/// </summary>
|
||||
/// <param name="myState">The integer state to associate</param>
|
||||
/// <param name="keyResolver">The key resolver for state keeping</param>
|
||||
public NumericStateAttribute(int myState, IStateKeyResolver<long> keyResolver)
|
||||
: base(myState, keyResolver) { }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the attribute with a special state and the default sender ID resolver.
|
||||
/// </summary>
|
||||
/// <param name="specialState">The special state to associate</param>
|
||||
public NumericStateAttribute(SpecialState specialState)
|
||||
: base(specialState, new SenderIdResolver()) { }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the attribute with a specific numeric state and the default sender ID resolver.
|
||||
/// </summary>
|
||||
/// <param name="myState">The integer state to associate</param>
|
||||
public NumericStateAttribute(int myState)
|
||||
: this(myState, new SenderIdResolver()) { }
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
namespace Telegrator.Annotations.StateKeeping
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents special states for state keeping logic.
|
||||
/// </summary>
|
||||
public enum SpecialState
|
||||
{
|
||||
/// <summary>
|
||||
/// No special state.
|
||||
/// </summary>
|
||||
None,
|
||||
/// <summary>
|
||||
/// Indicates that no state is present.
|
||||
/// </summary>
|
||||
NoState,
|
||||
/// <summary>
|
||||
/// Indicates that any state is acceptable.
|
||||
/// </summary>
|
||||
AnyState
|
||||
}
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
using Telegrator.StateKeeping;
|
||||
using Telegrator.Attributes;
|
||||
using Telegrator.Core.StateKeeping;
|
||||
|
||||
namespace Telegrator.Annotations.StateKeeping
|
||||
{
|
||||
/// <summary>
|
||||
/// Attribute for associating a handler or method with a string-based state keeper.
|
||||
/// Provides various constructors for flexible state and key resolver configuration.
|
||||
/// </summary>
|
||||
public class StringStateAttribute : StateKeeperAttribute<long, string, StringStateKeeper>
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes the attribute with a special state and a custom key resolver.
|
||||
/// </summary>
|
||||
/// <param name="specialState">The special state to associate</param>
|
||||
/// <param name="keyResolver">The key resolver for state keeping</param>
|
||||
public StringStateAttribute(SpecialState specialState, IStateKeyResolver<long> keyResolver)
|
||||
: base(specialState, keyResolver) { }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the attribute with a specific state and a custom key resolver.
|
||||
/// </summary>
|
||||
/// <param name="myState">The string state to associate</param>
|
||||
/// <param name="keyResolver">The key resolver for state keeping</param>
|
||||
public StringStateAttribute(string myState, IStateKeyResolver<long> keyResolver)
|
||||
: base(myState, keyResolver) { }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the attribute with a special state and the default sender ID resolver.
|
||||
/// </summary>
|
||||
/// <param name="specialState">The special state to associate</param>
|
||||
public StringStateAttribute(SpecialState specialState)
|
||||
: base(specialState, new SenderIdResolver()) { }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the attribute with a specific state and the default sender ID resolver.
|
||||
/// </summary>
|
||||
/// <param name="myState">The string state to associate</param>
|
||||
public StringStateAttribute(string myState)
|
||||
: base(myState, new SenderIdResolver()) { }
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
using Telegram.Bot.Types.Enums;
|
||||
using Telegrator.Filters;
|
||||
|
||||
namespace Telegrator.Annotations.Targetted
|
||||
namespace Telegrator.Annotations
|
||||
{
|
||||
/// <summary>
|
||||
/// Attribute for filtering message with command "start" in bot's private chats.
|
||||
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// Abstract attribute for associating a handler or method with a state keeper.
|
||||
/// Provides logic for state-based filtering and state management.
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">The type of the key used for state keeping (e.g., chat ID).</typeparam>
|
||||
/// <typeparam name="TState">The type of the state value (e.g., string, int).</typeparam>
|
||||
/// <typeparam name="TKeeper">The type of the state keeper implementation.</typeparam>
|
||||
public abstract class StateKeeperAttribute<TKey, TState, TKeeper> : StateKeeperAttributeBase where TKey : notnull where TState : notnull where TKeeper : StateKeeperBase<TKey, TState>, new()
|
||||
{
|
||||
/*
|
||||
private static readonly TKeeper _shared = new TKeeper();
|
||||
private static readonly Dictionary<TKey, TKeeper> _keyed = [];
|
||||
*/
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the singleton instance of the state keeper for this attribute type.
|
||||
/// </summary>
|
||||
public static TKeeper Shared { get; } = new TKeeper();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the default state value of this statekeeper.
|
||||
/// </summary>
|
||||
public static TState DefaultState => Shared.DefaultState;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the state value associated with this attribute instance.
|
||||
/// </summary>
|
||||
public TState MyState { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the special state mode for this attribute instance.
|
||||
/// </summary>
|
||||
public SpecialState SpecialState { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the attribute with a specific state and a custom key resolver.
|
||||
/// </summary>
|
||||
/// <param name="myState">The state value to associate</param>
|
||||
/// <param name="keyResolver">The key resolver for state keeping</param>
|
||||
protected StateKeeperAttribute(TState myState, IStateKeyResolver<TKey> keyResolver) : base(typeof(TKeeper))
|
||||
{
|
||||
Shared.KeyResolver = keyResolver;
|
||||
MyState = myState;
|
||||
SpecialState = SpecialState.None;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the attribute with a special state and a custom key resolver.
|
||||
/// </summary>
|
||||
/// <param name="specialState">The special state mode</param>
|
||||
/// <param name="keyResolver">The key resolver for state keeping</param>
|
||||
protected StateKeeperAttribute(SpecialState specialState, IStateKeyResolver<TKey> keyResolver) : base(typeof(TKeeper))
|
||||
{
|
||||
Shared.KeyResolver = keyResolver;
|
||||
MyState = Shared.DefaultState;
|
||||
SpecialState = specialState;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the current update context passes the state filter.
|
||||
/// </summary>
|
||||
/// <param name="context">The filter execution context</param>
|
||||
/// <returns>True if the state matches the filter; otherwise, false.</returns>
|
||||
public override bool CanPass(FilterExecutionContext<Update> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
using Telegram.Bot.Types;
|
||||
using Telegrator.Core.Filters;
|
||||
using Telegrator.Core.Handlers;
|
||||
using Telegrator.Core.StateKeeping;
|
||||
|
||||
namespace Telegrator.Core.Attributes
|
||||
{
|
||||
/// <summary>
|
||||
/// Sets the state in which the <see cref="UpdateHandlerBase"/> can be executed
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
|
||||
public abstract class StateKeeperAttributeBase : Attribute, IFilter<Update>
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public bool IsCollectible => GetType().HasPublicProperties();
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance <see cref="StateKeeperBase{TKey, TState}"/>
|
||||
/// </summary>
|
||||
/// <param name="stateKeeperType"></param>
|
||||
/// <exception cref="ArgumentException"></exception>
|
||||
protected StateKeeperAttributeBase(Type stateKeeperType)
|
||||
{
|
||||
if (!stateKeeperType.IsAssignableToGenericType(typeof(StateKeeperBase<,>)))
|
||||
throw new ArgumentException(stateKeeperType + " is not a StateKeeperBase", nameof(stateKeeperType));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Realizes a <see cref="IFilter{T}"/> for validation of the current <see cref="StateKeeperBase{TKey, TState}"/> in the polling routing
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
/// <returns></returns>
|
||||
public abstract bool CanPass(FilterExecutionContext<Update> context);
|
||||
}
|
||||
}
|
||||
@@ -24,7 +24,7 @@ namespace Telegrator.Core.Descriptors
|
||||
public MethodHandlerDescriptor(AbstractHandlerAction<TUpdate> action) : base(DescriptorType.General, typeof(MethodHandler), true)
|
||||
{
|
||||
UpdateHandlerAttributeBase handlerAttribute = HandlerInspector.GetHandlerAttribute(action.Method);
|
||||
StateKeeperAttributeBase? stateKeeperAttribute = HandlerInspector.GetStateKeeperAttribute(action.Method);
|
||||
IFilter<Update>? stateKeeperAttribute = HandlerInspector.GetStateKeeperAttribute(action.Method);
|
||||
IFilter<Update>[] filters = HandlerInspector.GetFilterAttributes(action.Method, handlerAttribute.Type).ToArray();
|
||||
|
||||
UpdateType = handlerAttribute.Type;
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using Telegram.Bot.Types;
|
||||
using Telegrator.Core.Filters;
|
||||
using Telegrator.Core.Handlers;
|
||||
using Telegrator.Core.States;
|
||||
|
||||
namespace Telegrator.Core.Descriptors
|
||||
{
|
||||
@@ -27,6 +28,11 @@ namespace Telegrator.Core.Descriptors
|
||||
/// </summary>
|
||||
public IAwaitingProvider AwaitingProvider { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The state storage to handling state machines
|
||||
/// </summary>
|
||||
public IStateStorage StateStorage { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The Telegram bot client used for this handler.
|
||||
/// </summary>
|
||||
@@ -73,6 +79,7 @@ namespace Telegrator.Core.Descriptors
|
||||
/// <param name="fromDescriptor">The descriptor from which this handler was described.</param>
|
||||
/// <param name="updateRouter">The update router.</param>
|
||||
/// <param name="awaitingProvider">The awaiting provider.</param>
|
||||
/// <param name="stateStorage">The state storage.</param>
|
||||
/// <param name="client">The Telegram bot client.</param>
|
||||
/// <param name="handlerInstance">The handler instance.</param>
|
||||
/// <param name="filterContext">The filter execution context.</param>
|
||||
@@ -81,6 +88,7 @@ namespace Telegrator.Core.Descriptors
|
||||
HandlerDescriptor fromDescriptor,
|
||||
IUpdateRouter updateRouter,
|
||||
IAwaitingProvider awaitingProvider,
|
||||
IStateStorage stateStorage,
|
||||
ITelegramBotClient client,
|
||||
UpdateHandlerBase handlerInstance,
|
||||
FilterExecutionContext<Update> filterContext,
|
||||
@@ -89,6 +97,7 @@ namespace Telegrator.Core.Descriptors
|
||||
From = fromDescriptor;
|
||||
UpdateRouter = updateRouter;
|
||||
AwaitingProvider = awaitingProvider;
|
||||
StateStorage = stateStorage;
|
||||
Client = client;
|
||||
HandlerInstance = handlerInstance;
|
||||
ExtraData = filterContext.Data;
|
||||
|
||||
@@ -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<Update>? stateKeeperAttribute = HandlerInspector.GetStateKeeperAttribute(handlerType);
|
||||
IFilter<Update>[] filters = HandlerInspector.GetFilterAttributes(handlerType, handlerAttribute.Type).ToArray();
|
||||
|
||||
UpdateType = handlerAttribute.Type;
|
||||
|
||||
@@ -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
|
||||
/// </summary>
|
||||
/// <param name="handlerType">The member info representing the handler type.</param>
|
||||
/// <returns>The state keeper attribute, or null if not present.</returns>
|
||||
public static StateKeeperAttributeBase? GetStateKeeperAttribute(MemberInfo handlerType)
|
||||
public static IFilter<Update>? GetStateKeeperAttribute(MemberInfo handlerType)
|
||||
{
|
||||
// Getting polling handler attribute
|
||||
IEnumerable<StateKeeperAttributeBase> handlerAttrs = handlerType.GetCustomAttributes<StateKeeperAttributeBase>();
|
||||
Attribute stateAttr = handlerType.GetCustomAttribute(typeof(StateAttribute<,>));
|
||||
|
||||
//
|
||||
return handlerAttrs.Any() ? handlerAttrs.Single() : null;
|
||||
return stateAttr as IFilter<Update>;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -9,6 +9,11 @@ namespace Telegrator.Core.Filters
|
||||
/// <typeparam name="T">The type of the input for the filter.</typeparam>
|
||||
public class FilterExecutionContext<T> where T : class
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the <see cref="ITelegramBotInfo"/> for the current context.
|
||||
/// </summary>
|
||||
public IUpdateRouter UpdateRouter { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="ITelegramBotInfo"/> for the current context.
|
||||
/// </summary>
|
||||
@@ -42,13 +47,15 @@ namespace Telegrator.Core.Filters
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="FilterExecutionContext{T}"/> class with all parameters.
|
||||
/// </summary>
|
||||
/// <param name="router">The router, that invoked filter.</param>
|
||||
/// <param name="botInfo">The bot info.</param>
|
||||
/// <param name="update">The update.</param>
|
||||
/// <param name="input">The input object.</param>
|
||||
/// <param name="data">The additional data dictionary.</param>
|
||||
/// <param name="completedFilters">The list of completed filters.</param>
|
||||
public FilterExecutionContext(ITelegramBotInfo botInfo, Update update, T input, Dictionary<string, object> data, CompletedFiltersList completedFilters)
|
||||
public FilterExecutionContext(IUpdateRouter router, ITelegramBotInfo botInfo, Update update, T input, Dictionary<string, object> data, CompletedFiltersList completedFilters)
|
||||
{
|
||||
UpdateRouter = router;
|
||||
BotInfo = botInfo;
|
||||
Data = data;
|
||||
CompletedFilters = completedFilters;
|
||||
@@ -60,11 +67,12 @@ namespace Telegrator.Core.Filters
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="FilterExecutionContext{T}"/> class with default data and filters.
|
||||
/// </summary>
|
||||
/// <param name="router">The router, that invoked filter.</param>
|
||||
/// <param name="botInfo">The bot info.</param>
|
||||
/// <param name="update">The update.</param>
|
||||
/// <param name="input">The input object.</param>
|
||||
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, [], []) { }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a child context for a different input type, sharing the same data and completed filters.
|
||||
@@ -73,6 +81,6 @@ namespace Telegrator.Core.Filters
|
||||
/// <param name="input">The new input object.</param>
|
||||
/// <returns>A new <see cref="FilterExecutionContext{C}"/> instance.</returns>
|
||||
public FilterExecutionContext<C> CreateChild<C>(C input) where C : class
|
||||
=> new FilterExecutionContext<C>(BotInfo, Update, input, Data, CompletedFilters);
|
||||
=> new FilterExecutionContext<C>(UpdateRouter, BotInfo, Update, input, Data, CompletedFilters);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
/// </summary>
|
||||
protected IAwaitingProvider AwaitingProvider => Container.AwaitingProvider;
|
||||
|
||||
/// <summary>
|
||||
/// Storage of bot states.
|
||||
/// </summary>
|
||||
protected IStateStorage StateStorage => Container.StateStorage;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance and checks that the update type matches <typeparamref name="TUpdate"/>.
|
||||
/// </summary>
|
||||
|
||||
@@ -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
|
||||
/// <summary>
|
||||
/// Sets a state keeper for the handler using a specific state and key resolver.
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">The type of the key.</typeparam>
|
||||
/// <typeparam name="TState">The type of the state.</typeparam>
|
||||
/// <typeparam name="TKeeper">The type of the state keeper.</typeparam>
|
||||
/// <param name="myState">The state value.</param>
|
||||
/// <param name="keyResolver">The key resolver.</param>
|
||||
/// <typeparam name="TKey">The key resolver.</typeparam>
|
||||
/// <typeparam name="TValue">The state value.</typeparam>
|
||||
/// <param name="state">The state value.</param>
|
||||
/// <returns>The builder instance.</returns>
|
||||
public void SetStateKeeper<TKey, TState, TKeeper>(TState myState, IStateKeyResolver<TKey> keyResolver)
|
||||
where TKey : notnull
|
||||
where TState : IEquatable<TState>
|
||||
where TKeeper : StateKeeperBase<TKey, TState>, new()
|
||||
public void SetState<TKey, TValue>(TValue? state)
|
||||
where TKey : IStateKeyResolver, new()
|
||||
where TValue : IEquatable<TValue>
|
||||
{
|
||||
StateKeeper = new StateKeepFilter<TKey, TState, TKeeper>(myState, keyResolver);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets a state keeper for the handler using a special state and key resolver.
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">The type of the key.</typeparam>
|
||||
/// <typeparam name="TState">The type of the state.</typeparam>
|
||||
/// <typeparam name="TKeeper">The type of the state keeper.</typeparam>
|
||||
/// <param name="specialState">The special state value.</param>
|
||||
/// <param name="keyResolver">The key resolver.</param>
|
||||
/// <returns>The builder instance.</returns>
|
||||
public void SetStateKeeper<TKey, TState, TKeeper>(SpecialState specialState, IStateKeyResolver<TKey> keyResolver)
|
||||
where TKey : notnull
|
||||
where TState : IEquatable<TState>
|
||||
where TKeeper : StateKeeperBase<TKey, TState>, new()
|
||||
{
|
||||
StateKeeper = new StateKeepFilter<TKey, TState, TKeeper>(specialState, keyResolver);
|
||||
StateKeeper = new StateKeyFilter<TKey, TValue>(state);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -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
|
||||
/// <summary>
|
||||
/// Awaits an update using the specified key resolver and cancellation token.
|
||||
/// </summary>
|
||||
/// <param name="keyResolver">The <see cref="IStateKeyResolver{TKey}"/> to resolve the key.</param>
|
||||
/// <param name="keyResolver">The <see cref="IStateKeyResolver"/> to resolve the key.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>A <see cref="Task{TUpdate}"/> representing the awaited update.</returns>
|
||||
public Task<TUpdate> Await(IStateKeyResolver<long> keyResolver, CancellationToken cancellationToken = default);
|
||||
public Task<TUpdate> Await(IStateKeyResolver keyResolver, CancellationToken cancellationToken = default);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
/// <summary>
|
||||
/// Sets a state keeper for the handler using a specific state and key resolver.
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">The type of the key.</typeparam>
|
||||
/// <typeparam name="TState">The type of the state.</typeparam>
|
||||
/// <typeparam name="TKeeper">The type of the state keeper.</typeparam>
|
||||
/// <param name="myState">The state value.</param>
|
||||
/// <param name="keyResolver">The key resolver.</param>
|
||||
/// <typeparam name="TKey">The key resolver.</typeparam>
|
||||
/// <typeparam name="TValue">The state value.</typeparam>
|
||||
/// <param name="state">The state value.</param>
|
||||
/// <returns>The builder instance.</returns>
|
||||
public void SetStateKeeper<TKey, TState, TKeeper>(TState myState, IStateKeyResolver<TKey> keyResolver)
|
||||
where TKey : notnull
|
||||
where TState : IEquatable<TState>
|
||||
where TKeeper : StateKeeperBase<TKey, TState>, new();
|
||||
|
||||
/// <summary>
|
||||
/// Sets a state keeper for the handler using a special state and key resolver.
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">The type of the key.</typeparam>
|
||||
/// <typeparam name="TState">The type of the state.</typeparam>
|
||||
/// <typeparam name="TKeeper">The type of the state keeper.</typeparam>
|
||||
/// <param name="specialState">The special state value.</param>
|
||||
/// <param name="keyResolver">The key resolver.</param>
|
||||
/// <returns>The builder instance.</returns>
|
||||
public void SetStateKeeper<TKey, TState, TKeeper>(SpecialState specialState, IStateKeyResolver<TKey> keyResolver)
|
||||
where TKey : notnull
|
||||
where TState : IEquatable<TState>
|
||||
where TKeeper : StateKeeperBase<TKey, TState>, new();
|
||||
public void SetState<TKey, TValue>(TValue? state)
|
||||
where TKey : IStateKeyResolver, new()
|
||||
where TValue : IEquatable<TValue>;
|
||||
|
||||
/// <summary>
|
||||
/// Adds a targeted filter for a specific filter target type.
|
||||
|
||||
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// Filter for state keeping logic, allowing filtering based on state and special state conditions.
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">The type of the key for state resolution.</typeparam>
|
||||
/// <typeparam name="TState">The type of the state.</typeparam>
|
||||
/// <typeparam name="TKeeper">The type of the state keeper.</typeparam>
|
||||
public class StateKeepFilter<TKey, TState, TKeeper> : Filter<Update>
|
||||
where TKey : notnull
|
||||
where TState : IEquatable<TState>
|
||||
where TKeeper : StateKeeperBase<TKey, TState>, new()
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the state keeper instance.
|
||||
/// </summary>
|
||||
public static TKeeper StateKeeper { get; internal set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the state value for this filter.
|
||||
/// </summary>
|
||||
public TState MyState { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the special state value for this filter.
|
||||
/// </summary>
|
||||
public SpecialState SpecialState { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="StateKeepFilter{TKey, TState, TKeeper}"/> class with a specific state.
|
||||
/// </summary>
|
||||
/// <param name="myState">The state value.</param>
|
||||
/// <param name="keyResolver">The key resolver.</param>
|
||||
public StateKeepFilter(TState myState, IStateKeyResolver<TKey> keyResolver)
|
||||
{
|
||||
StateKeeper ??= new TKeeper();
|
||||
StateKeeper.KeyResolver = keyResolver;
|
||||
MyState = myState;
|
||||
SpecialState = SpecialState.None;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="StateKeepFilter{TKey, TState, TKeeper}"/> class with a special state.
|
||||
/// </summary>
|
||||
/// <param name="specialState">The special state value.</param>
|
||||
/// <param name="keyResolver">The key resolver.</param>
|
||||
public StateKeepFilter(SpecialState specialState, IStateKeyResolver<TKey> keyResolver)
|
||||
{
|
||||
StateKeeper ??= new TKeeper();
|
||||
StateKeeper.KeyResolver = keyResolver;
|
||||
MyState = StateKeeper.DefaultState;
|
||||
SpecialState = specialState;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the filter can pass for the given context based on state logic.
|
||||
/// </summary>
|
||||
/// <param name="context">The filter execution context.</param>
|
||||
/// <returns>True if the filter passes; otherwise, false.</returns>
|
||||
public override bool CanPass(FilterExecutionContext<Update> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IAwaitingProvider AwaitingProvider => throw new NotImplementedException();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IStateStorage StateStorage => throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 <see cref="IAwaitingProvider"/> for awaiting operations.
|
||||
/// </summary>
|
||||
public IAwaitingProvider AwaitingProvider { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="IStateStorage"/> for state managment.
|
||||
/// </summary>
|
||||
public IStateStorage StateStorage { get; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
namespace Telegrator.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// Interface for polling providers that manage both regular and awaiting handlers.
|
||||
/// Provides access to handlers for different types of update processing during polling operations.
|
||||
/// </summary>
|
||||
public interface IPollingProvider
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the <see cref="IHandlersProvider"/> that manages handlers for polling.
|
||||
/// </summary>
|
||||
public IHandlersProvider HandlersProvider { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="IAwaitingProvider"/> that manages awaiting handlers for polling.
|
||||
/// </summary>
|
||||
public IAwaitingProvider AwaitingProvider { get; }
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
/// </summary>
|
||||
public interface IUpdateRouter : IUpdateHandler, IPollingProvider
|
||||
public interface IUpdateRouter : IUpdateHandler
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the <see cref="TelegratorOptions"/> for the router.
|
||||
@@ -19,6 +20,21 @@ namespace Telegrator.Core
|
||||
/// </summary>
|
||||
public IUpdateHandlersPool HandlersPool { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="IHandlersProvider"/> that manages handlers for polling.
|
||||
/// </summary>
|
||||
public IHandlersProvider HandlersProvider { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="IAwaitingProvider"/> that manages awaiting handlers for polling.
|
||||
/// </summary>
|
||||
public IAwaitingProvider AwaitingProvider { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="IStateStorage"/> that manages storing of handlers state.
|
||||
/// </summary>
|
||||
public IStateStorage StateStorage { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the <see cref="IRouterExceptionHandler"/> for handling exceptions.
|
||||
/// </summary>
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
using Telegram.Bot.Types;
|
||||
|
||||
namespace Telegrator.Core.StateKeeping
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines a resolver for extracting a key from an update for state keeping purposes.
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">The type of the key.</typeparam>
|
||||
public interface IStateKeyResolver<TKey> where TKey : notnull
|
||||
{
|
||||
/// <summary>
|
||||
/// Resolves a key from the specified <see cref="Update"/>.
|
||||
/// </summary>
|
||||
/// <param name="keySource">The update to resolve the key from.</param>
|
||||
/// <returns>The resolved key.</returns>
|
||||
public TKey ResolveKey(Update keySource);
|
||||
}
|
||||
}
|
||||
@@ -1,150 +0,0 @@
|
||||
using Telegram.Bot.Types;
|
||||
|
||||
namespace Telegrator.Core.StateKeeping
|
||||
{
|
||||
/// <summary>
|
||||
/// Base class for managing state associated with updates and keys.
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">The type of the key used for state resolution.</typeparam>
|
||||
/// <typeparam name="TState">The type of the state.</typeparam>
|
||||
public abstract class StateKeeperBase<TKey, TState> where TState : notnull where TKey : notnull
|
||||
{
|
||||
private readonly Dictionary<TKey, TState> States = [];
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the key resolver used to resolve keys from updates.
|
||||
/// </summary>
|
||||
public IStateKeyResolver<TKey> KeyResolver { get; set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the default state value.
|
||||
/// </summary>
|
||||
public abstract TState DefaultState { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Sets the state for the specified update.
|
||||
/// </summary>
|
||||
/// <param name="keySource">The update to use as a key source.</param>
|
||||
/// <param name="newState">The new state value.</param>
|
||||
public virtual void SetState(Update keySource, TState newState)
|
||||
{
|
||||
TKey key = KeyResolver.ResolveKey(keySource);
|
||||
States.Set(key, newState, DefaultState);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the state for the specified update.
|
||||
/// </summary>
|
||||
/// <param name="keySource">The update to use as a key source.</param>
|
||||
/// <returns>The state value.</returns>
|
||||
public virtual TState GetState(Update keySource)
|
||||
{
|
||||
TKey key = KeyResolver.ResolveKey(keySource);
|
||||
return States[key];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to get the state for the specified update.
|
||||
/// </summary>
|
||||
/// <param name="keySource">The update to use as a key source.</param>
|
||||
/// <param name="state">When this method returns, contains the state value if found; otherwise, the default value.</param>
|
||||
/// <returns>True if the state was found; otherwise, false.</returns>
|
||||
public virtual bool TryGetState(Update keySource, out TState? state)
|
||||
{
|
||||
TKey key = KeyResolver.ResolveKey(keySource);
|
||||
return States.TryGetValue(key, out state);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether a state exists for the specified update.
|
||||
/// </summary>
|
||||
/// <param name="keySource">The update to use as a key source.</param>
|
||||
/// <returns>True if the state exists; otherwise, false.</returns>
|
||||
public virtual bool HasState(Update keySource)
|
||||
{
|
||||
TKey key = KeyResolver.ResolveKey(keySource);
|
||||
return States.ContainsKey(key);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a state for the specified update using the default state value.
|
||||
/// </summary>
|
||||
/// <param name="keySource">The update to use as a key source.</param>
|
||||
public virtual void CreateState(Update keySource)
|
||||
{
|
||||
TKey key = KeyResolver.ResolveKey(keySource);
|
||||
States.Set(key, DefaultState);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deletes the state for the specified update.
|
||||
/// </summary>
|
||||
/// <param name="keySource">The update to use as a key source.</param>
|
||||
public virtual void DeleteState(Update keySource)
|
||||
{
|
||||
TKey key = KeyResolver.ResolveKey(keySource);
|
||||
States.Remove(key);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Moves the state forward for the specified update.
|
||||
/// </summary>
|
||||
/// <param name="keySource">The update to use as a key source.</param>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Moves the state backward for the specified update.
|
||||
/// </summary>
|
||||
/// <param name="keySource">The update to use as a key source.</param>
|
||||
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;
|
||||
}
|
||||
|
||||
/*
|
||||
/// <summary>
|
||||
/// Gets the state keeper for the specified key.
|
||||
/// </summary>
|
||||
/// <typeparam name="TStateKeeper">The type of the state keeper.</typeparam>
|
||||
/// <param name="key">The key.</param>
|
||||
/// <returns>The state keeper instance.</returns>
|
||||
protected virtual TStateKeeper GetKeeper<TStateKeeper>(TKey key) where TStateKeeper : StateKeeperBase<TKey, TState>
|
||||
=> States[key] as TStateKeeper ?? throw new InvalidCastException();
|
||||
*/
|
||||
|
||||
/// <summary>
|
||||
/// Moves the state forward for the specified current state and key.
|
||||
/// </summary>
|
||||
/// <param name="currentState">The current state value.</param>
|
||||
/// <param name="currentKey">The key.</param>
|
||||
/// <returns>The new state value.</returns>
|
||||
protected abstract TState MoveForward(TState currentState, TKey currentKey);
|
||||
|
||||
/// <summary>
|
||||
/// Moves the state backward for the specified current state and key.
|
||||
/// </summary>
|
||||
/// <param name="currentState">The current state value.</param>
|
||||
/// <param name="currentKey">The key.</param>
|
||||
/// <returns>The new state value.</returns>
|
||||
protected abstract TState MoveBackward(TState currentState, TKey currentKey);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
using Telegram.Bot.Types;
|
||||
|
||||
namespace Telegrator.Core.States;
|
||||
|
||||
/// <summary>
|
||||
/// Defines a resolver for extracting a key from an update for state keeping purposes.
|
||||
/// </summary>
|
||||
public interface IStateKeyResolver
|
||||
{
|
||||
/// <summary>
|
||||
/// Resolves a key from the specified <see cref="Update"/>.
|
||||
/// </summary>
|
||||
/// <param name="keySource">The update to resolve the key from.</param>
|
||||
/// <returns>The resolved key.</returns>
|
||||
public string? ResolveKey(Update keySource);
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace Telegrator.Core.States;
|
||||
|
||||
public interface IStateMachine<TState> where TState : IEquatable<TState>
|
||||
{
|
||||
Task<TState?> 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);
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace Telegrator.Core.States;
|
||||
|
||||
public interface IStateStorage
|
||||
{
|
||||
Task SetAsync<T>(string key, T state, CancellationToken cancellationToken = default);
|
||||
Task<T?> GetAsync<T>(string key, CancellationToken cancellationToken = default);
|
||||
Task DeleteAsync(string key, CancellationToken cancellationToken = default);
|
||||
}
|
||||
@@ -1,31 +1,44 @@
|
||||
using Telegram.Bot.Types;
|
||||
using Telegrator.Core.Filters;
|
||||
using Telegrator.Core.StateKeeping;
|
||||
using Telegrator.Core.States;
|
||||
|
||||
namespace Telegrator.Filters
|
||||
{
|
||||
/// <summary>
|
||||
/// Filters updates by comparing a resolved state key with a target key.
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">The type of the key used for state resolution.</typeparam>
|
||||
public class StateKeyFilter<TKey> : Filter<Update> where TKey : IEquatable<TKey>
|
||||
/// <typeparam name="TKey">The type of the key resolver used to get state key.</typeparam>
|
||||
/// <typeparam name="TValue">The type of the key used for state resolution.</typeparam>
|
||||
public class StateKeyFilter<TKey, TValue> : Filter<Update>
|
||||
where TKey : IStateKeyResolver, new()
|
||||
where TValue : IEquatable<TValue>
|
||||
{
|
||||
private readonly IStateKeyResolver<TKey> KeyResolver;
|
||||
private readonly TKey TargetKey;
|
||||
private readonly TValue? TargetKey;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="StateKeyFilter{TKey}"/> class.
|
||||
/// Initializes a new instance of the <see cref="StateKeyFilter{TKey, TValue}"/> class.
|
||||
/// </summary>
|
||||
/// <param name="keyResolver">The key resolver to extract the key from the update.</param>
|
||||
/// <param name="targetKey">The target key to compare with.</param>
|
||||
public StateKeyFilter(IStateKeyResolver<TKey> keyResolver, TKey targetKey)
|
||||
public StateKeyFilter(TValue? targetKey)
|
||||
{
|
||||
KeyResolver = keyResolver;
|
||||
TargetKey = targetKey;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool CanPass(FilterExecutionContext<Update> 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<TValue>(key).Result;
|
||||
if (value is null)
|
||||
return TargetKey is null;
|
||||
|
||||
if (TargetKey is null)
|
||||
return false;
|
||||
|
||||
return TargetKey.Equals(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
/// <param name="keyResolver">The state key resolver to use for filtering updates.</param>
|
||||
/// <param name="cancellationToken">The cancellation token to cancel the wait operation.</param>
|
||||
/// <returns>The awaited update of type TUpdate.</returns>
|
||||
public async Task<TUpdate> Await(IStateKeyResolver<long> keyResolver, CancellationToken cancellationToken = default)
|
||||
public async Task<TUpdate> Await(IStateKeyResolver keyResolver, CancellationToken cancellationToken = default)
|
||||
{
|
||||
Filters.Add(new StateKeyFilter<long>(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<Update>.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);
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="HandlerBuilderBase.SetStateKeeper{TKey, TState, TKeeper}(TState, IStateKeyResolver{TKey})"/>
|
||||
public static TBuilder SetStateKeeper<TBuilder, TKey, TState, TKeeper>(this TBuilder handlerBuilder, TState myState, IStateKeyResolver<TKey> keyResolver)
|
||||
/// <inheritdoc cref="HandlerBuilderBase.SetState{TKey, TValue}(TValue?)"/>
|
||||
public static TBuilder SetState<TBuilder, TKey, TValue>(this TBuilder handlerBuilder, TValue? myState)
|
||||
where TBuilder : HandlerBuilderBase
|
||||
where TKey : notnull
|
||||
where TState : IEquatable<TState>
|
||||
where TKeeper : StateKeeperBase<TKey, TState>, new()
|
||||
where TKey : IStateKeyResolver, new()
|
||||
where TValue : IEquatable<TValue>
|
||||
{
|
||||
handlerBuilder.SetStateKeeper<TKey, TState, TKeeper>(myState, keyResolver);
|
||||
return handlerBuilder;
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="HandlerBuilderBase.SetStateKeeper{TKey, TState, TKeeper}(SpecialState, IStateKeyResolver{TKey})"/>
|
||||
public static TBuilder SetStateKeeper<TBuilder, TKey, TState, TKeeper>(this TBuilder handlerBuilder, SpecialState specialState, IStateKeyResolver<TKey> keyResolver)
|
||||
where TBuilder : HandlerBuilderBase
|
||||
where TKey : notnull
|
||||
where TState : IEquatable<TState>
|
||||
where TKeeper : StateKeeperBase<TKey, TState>, new()
|
||||
{
|
||||
handlerBuilder.SetStateKeeper<TKey, TState, TKeeper>(specialState, keyResolver);
|
||||
handlerBuilder.SetState<TKey, TValue>(myState);
|
||||
return handlerBuilder;
|
||||
}
|
||||
|
||||
@@ -116,129 +102,5 @@ namespace Telegrator.Handlers.Building
|
||||
handlerBuilder.AddTargetedFilters(getFilterringTarget, filters);
|
||||
return handlerBuilder;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets a numeric state keeper with a custom key resolver.
|
||||
/// </summary>
|
||||
/// <typeparam name="TBuilder">The type of the handler builder.</typeparam>
|
||||
/// <param name="handlerBuilder">The handler builder.</param>
|
||||
/// <param name="myState">The numeric state value.</param>
|
||||
/// <param name="keyResolver">The key resolver for the state.</param>
|
||||
/// <returns>The handler builder for method chaining.</returns>
|
||||
public static TBuilder SetNumericState<TBuilder>(this TBuilder handlerBuilder, int myState, IStateKeyResolver<long> keyResolver)
|
||||
where TBuilder : HandlerBuilderBase
|
||||
{
|
||||
handlerBuilder.SetStateKeeper<long, int, NumericStateKeeper>(myState, keyResolver);
|
||||
return handlerBuilder;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets a numeric state keeper with a special state and custom key resolver.
|
||||
/// </summary>
|
||||
/// <typeparam name="TBuilder">The type of the handler builder.</typeparam>
|
||||
/// <param name="handlerBuilder">The handler builder.</param>
|
||||
/// <param name="specialState">The special state value.</param>
|
||||
/// <param name="keyResolver">The key resolver for the state.</param>
|
||||
/// <returns>The handler builder for method chaining.</returns>
|
||||
public static TBuilder SetNumericState<TBuilder>(this TBuilder handlerBuilder, SpecialState specialState, IStateKeyResolver<long> keyResolver)
|
||||
where TBuilder : HandlerBuilderBase
|
||||
{
|
||||
handlerBuilder.SetStateKeeper<long, int, NumericStateKeeper>(specialState, keyResolver);
|
||||
return handlerBuilder;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets a numeric state keeper with the default sender ID resolver.
|
||||
/// </summary>
|
||||
/// <typeparam name="TBuilder">The type of the handler builder.</typeparam>
|
||||
/// <param name="handlerBuilder">The handler builder.</param>
|
||||
/// <param name="myState">The numeric state value.</param>
|
||||
/// <returns>The handler builder for method chaining.</returns>
|
||||
public static TBuilder SetNumericState<TBuilder>(this TBuilder handlerBuilder, int myState)
|
||||
where TBuilder : HandlerBuilderBase
|
||||
{
|
||||
handlerBuilder.SetStateKeeper<long, int, NumericStateKeeper>(myState, new SenderIdResolver());
|
||||
return handlerBuilder;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets a numeric state keeper with a special state and the default sender ID resolver.
|
||||
/// </summary>
|
||||
/// <typeparam name="TBuilder">The type of the handler builder.</typeparam>
|
||||
/// <param name="handlerBuilder">The handler builder.</param>
|
||||
/// <param name="specialState">The special state value.</param>
|
||||
/// <returns>The handler builder for method chaining.</returns>
|
||||
public static TBuilder SetNumericState<TBuilder>(this TBuilder handlerBuilder, SpecialState specialState)
|
||||
where TBuilder : HandlerBuilderBase
|
||||
{
|
||||
handlerBuilder.SetStateKeeper<long, int, NumericStateKeeper>(specialState, new SenderIdResolver());
|
||||
return handlerBuilder;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets an enum state keeper with a custom key resolver.
|
||||
/// </summary>
|
||||
/// <typeparam name="TBuilder">The type of the handler builder.</typeparam>
|
||||
/// <typeparam name="TEnum">The type of the enum state.</typeparam>
|
||||
/// <param name="handlerBuilder">The handler builder.</param>
|
||||
/// <param name="myState">The enum state value.</param>
|
||||
/// <param name="keyResolver">The key resolver for the state.</param>
|
||||
/// <returns>The handler builder for method chaining.</returns>
|
||||
public static TBuilder SetEnumState<TBuilder, TEnum>(this TBuilder handlerBuilder, TEnum myState, IStateKeyResolver<long> keyResolver)
|
||||
where TBuilder : HandlerBuilderBase
|
||||
where TEnum : Enum, IEquatable<TEnum>
|
||||
{
|
||||
handlerBuilder.SetStateKeeper<long, TEnum, EnumStateKeeper<TEnum>>(myState, keyResolver);
|
||||
return handlerBuilder;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets an enum state keeper with a special state and custom key resolver.
|
||||
/// </summary>
|
||||
/// <typeparam name="TBuilder">The type of the handler builder.</typeparam>
|
||||
/// <typeparam name="TEnum">The type of the enum state.</typeparam>
|
||||
/// <param name="handlerBuilder">The handler builder.</param>
|
||||
/// <param name="specialState">The special state value.</param>
|
||||
/// <param name="keyResolver">The key resolver for the state.</param>
|
||||
/// <returns>The handler builder for method chaining.</returns>
|
||||
public static TBuilder SetEnumState<TBuilder, TEnum>(this TBuilder handlerBuilder, SpecialState specialState, IStateKeyResolver<long> keyResolver)
|
||||
where TBuilder : HandlerBuilderBase
|
||||
where TEnum : Enum, IEquatable<TEnum>
|
||||
{
|
||||
handlerBuilder.SetStateKeeper<long, TEnum, EnumStateKeeper<TEnum>>(specialState, keyResolver);
|
||||
return handlerBuilder;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets an enum state keeper with the default sender ID resolver.
|
||||
/// </summary>
|
||||
/// <typeparam name="TBuilder">The type of the handler builder.</typeparam>
|
||||
/// <typeparam name="TEnum">The type of the enum state.</typeparam>
|
||||
/// <param name="handlerBuilder">The handler builder.</param>
|
||||
/// <param name="myState">The enum state value.</param>
|
||||
/// <returns>The handler builder for method chaining.</returns>
|
||||
public static TBuilder SetEnumState<TBuilder, TEnum>(this TBuilder handlerBuilder, TEnum myState)
|
||||
where TBuilder : HandlerBuilderBase
|
||||
where TEnum : Enum, IEquatable<TEnum>
|
||||
{
|
||||
handlerBuilder.SetStateKeeper<long, TEnum, EnumStateKeeper<TEnum>>(myState, new SenderIdResolver());
|
||||
return handlerBuilder;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets an enum state keeper with a special state and the default sender ID resolver.
|
||||
/// </summary>
|
||||
/// <typeparam name="TBuilder">The type of the handler builder.</typeparam>
|
||||
/// <typeparam name="TEnum">The type of the enum state.</typeparam>
|
||||
/// <param name="handlerBuilder">The handler builder.</param>
|
||||
/// <param name="specialState">The special state value.</param>
|
||||
/// <returns>The handler builder for method chaining.</returns>
|
||||
public static TBuilder SetEnumState<TBuilder, TEnum>(this TBuilder handlerBuilder, SpecialState specialState)
|
||||
where TBuilder : HandlerBuilderBase
|
||||
where TEnum : Enum, IEquatable<TEnum>
|
||||
{
|
||||
handlerBuilder.SetStateKeeper<long, TEnum, EnumStateKeeper<TEnum>>(specialState, new SenderIdResolver());
|
||||
return handlerBuilder;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
/// <inheritdoc/>
|
||||
public IAwaitingProvider AwaitingProvider { get; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IStateStorage StateStorage { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes new instance of <see cref="HandlerContainer{TUpdate}"/>
|
||||
/// </summary>
|
||||
@@ -45,6 +49,7 @@ namespace Telegrator.Handlers
|
||||
ExtraData = handlerInfo.ExtraData;
|
||||
CompletedFilters = handlerInfo.CompletedFilters;
|
||||
AwaitingProvider = handlerInfo.AwaitingProvider;
|
||||
StateStorage = handlerInfo.StateStorage;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -56,7 +61,7 @@ namespace Telegrator.Handlers
|
||||
/// <param name="extraData"></param>
|
||||
/// <param name="filters"></param>
|
||||
/// <param name="awaitingProvider"></param>
|
||||
public HandlerContainer(TUpdate actualUpdate, Update handlingUpdate, ITelegramBotClient client, Dictionary<string, object> extraData, CompletedFiltersList filters, IAwaitingProvider awaitingProvider)
|
||||
public HandlerContainer(TUpdate actualUpdate, Update handlingUpdate, ITelegramBotClient client, Dictionary<string, object> 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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -75,8 +81,8 @@ namespace Telegrator.Handlers
|
||||
{
|
||||
return new HandlerContainer<QUpdate>(
|
||||
HandlingUpdate.GetActualUpdateObject<QUpdate>(),
|
||||
HandlingUpdate, Client, ExtraData,
|
||||
CompletedFilters, AwaitingProvider);
|
||||
HandlingUpdate, Client, ExtraData, CompletedFilters,
|
||||
AwaitingProvider, StateStorage);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -89,8 +95,8 @@ namespace Telegrator.Handlers
|
||||
{
|
||||
return new HandlerContainer<TUpdate>(
|
||||
other.HandlingUpdate.GetActualUpdateObject<TUpdate>(),
|
||||
other.HandlingUpdate, other.Client, other.ExtraData,
|
||||
other.CompletedFilters, other.AwaitingProvider);
|
||||
other.HandlingUpdate, other.Client, other.ExtraData, other.CompletedFilters,
|
||||
other.AwaitingProvider, other.StateStorage);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ namespace Telegrator.Mediation
|
||||
{
|
||||
_handler.Invoke(botClient, exception, source, cancellationToken);
|
||||
}
|
||||
finally
|
||||
catch
|
||||
{
|
||||
_ = 0xBAD + 0xC0DE;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
/// <inheritdoc/>
|
||||
public IAwaitingProvider AwaitingProvider => _awaitingProvider;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IStateStorage StateStorage => _stateStorage;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public TelegratorOptions Options => _options;
|
||||
|
||||
@@ -47,13 +52,15 @@ namespace Telegrator.Mediation
|
||||
/// </summary>
|
||||
/// <param name="handlersProvider">The provider for regular handlers.</param>
|
||||
/// <param name="awaitingProvider">The provider for awaiting handlers.</param>
|
||||
/// <param name="stateStorage">The state storage.</param>
|
||||
/// <param name="options">The bot configuration options.</param>
|
||||
/// <param name="botInfo"></param>
|
||||
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<Update> filterContext = new FilterExecutionContext<Update>(_botInfo, update, update, data, []);
|
||||
FilterExecutionContext<Update> filterContext = new FilterExecutionContext<Update>(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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -1,59 +0,0 @@
|
||||
using Telegrator.Core.StateKeeping;
|
||||
|
||||
namespace Telegrator.StateKeeping
|
||||
{
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">The type of the key used to identify state contexts.</typeparam>
|
||||
/// <typeparam name="TState">The type of the state values. Must be non-null.</typeparam>
|
||||
/// <param name="states">The array of states that define the allowed state sequence.</param>
|
||||
public abstract class ArrayStateKeeper<TKey, TState>(params TState[] states) : StateKeeperBase<TKey, TState> where TState : notnull where TKey : notnull
|
||||
{
|
||||
/// <summary>
|
||||
/// The array of states that defines the allowed state sequence for navigation.
|
||||
/// </summary>
|
||||
protected readonly TState[] ArrayStates = states;
|
||||
|
||||
/// <summary>
|
||||
/// Moves to the previous state in the array sequence.
|
||||
/// </summary>
|
||||
/// <param name="currentState">The current state to move backward from.</param>
|
||||
/// <param name="_">The key parameter (unused in this implementation).</param>
|
||||
/// <returns>The previous state in the array sequence.</returns>
|
||||
/// <exception cref="ArgumentException">Thrown when the current state is not found in the array.</exception>
|
||||
/// <exception cref="IndexOutOfRangeException">Thrown when trying to move backward from the first state.</exception>
|
||||
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];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Moves to the next state in the array sequence.
|
||||
/// </summary>
|
||||
/// <param name="currentState">The current state to move forward from.</param>
|
||||
/// <param name="_">The key parameter (unused in this implementation).</param>
|
||||
/// <returns>The next state in the array sequence.</returns>
|
||||
/// <exception cref="ArgumentException">Thrown when the current state is not found in the array.</exception>
|
||||
/// <exception cref="IndexOutOfRangeException">Thrown when trying to move forward from the last state.</exception>
|
||||
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];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,75 +0,0 @@
|
||||
using Telegrator.Annotations.StateKeeping;
|
||||
using Telegrator.Core.Handlers;
|
||||
|
||||
namespace Telegrator.StateKeeping
|
||||
{
|
||||
/// <summary>
|
||||
/// State keeper implementation for enum-based states.
|
||||
/// Automatically creates an array of all enum values for state navigation.
|
||||
/// </summary>
|
||||
/// <typeparam name="TEnum">The enum type to be used for state management.</typeparam>
|
||||
public class EnumStateKeeper<TEnum>() : ArrayStateKeeper<long, TEnum>(Enum.GetValues(typeof(TEnum)).Cast<TEnum>().ToArray()) where TEnum : Enum
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the default state, which is the first value in the enum.
|
||||
/// </summary>
|
||||
public override TEnum DefaultState => ArrayStates.ElementAt(0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extension methods for working with enum-based states in handler containers.
|
||||
/// Provides convenient methods for state management operations.
|
||||
/// </summary>
|
||||
public static partial class StateHandlerContainerExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the enum state keeper for the specified enum type.
|
||||
/// </summary>
|
||||
/// <typeparam name="TEnum">The enum type to get the state keeper for.</typeparam>
|
||||
/// <param name="_">The handler container (unused parameter for extension method syntax).</param>
|
||||
/// <returns>The enum state keeper instance.</returns>
|
||||
public static EnumStateKeeper<TEnum> EnumStateKeeper<TEnum>(this IHandlerContainer _) where TEnum : Enum
|
||||
=> EnumStateAttribute<TEnum>.Shared;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new enum state for the current update.
|
||||
/// </summary>
|
||||
/// <typeparam name="TEnum">The enum type for state management.</typeparam>
|
||||
/// <param name="container">The handler container.</param>
|
||||
public static void CreateEnumState<TEnum>(this IHandlerContainer container) where TEnum : Enum
|
||||
=> container.EnumStateKeeper<TEnum>().CreateState(container.HandlingUpdate);
|
||||
|
||||
/// <summary>
|
||||
/// Deletes the enum state for the current update.
|
||||
/// </summary>
|
||||
/// <typeparam name="TEnum">The enum type for state management.</typeparam>
|
||||
/// <param name="container">The handler container.</param>
|
||||
public static void DeleteEnumState<TEnum>(this IHandlerContainer container) where TEnum : Enum
|
||||
=> container.EnumStateKeeper<TEnum>().DeleteState(container.HandlingUpdate);
|
||||
|
||||
/// <summary>
|
||||
/// Sets the enum state to a specific value for the current update.
|
||||
/// </summary>
|
||||
/// <typeparam name="TEnum">The enum type for state management.</typeparam>
|
||||
/// <param name="container">The handler container.</param>
|
||||
/// <param name="newState">The new state value. If null, uses the default state.</param>
|
||||
public static void SetEnumState<TEnum>(this IHandlerContainer container, TEnum? newState) where TEnum : Enum
|
||||
=> container.EnumStateKeeper<TEnum>().SetState(container.HandlingUpdate, newState ?? EnumStateAttribute<TEnum>.DefaultState);
|
||||
|
||||
/// <summary>
|
||||
/// Moves the enum state forward to the next value in the enum sequence.
|
||||
/// </summary>
|
||||
/// <typeparam name="TEnum">The enum type for state management.</typeparam>
|
||||
/// <param name="container">The handler container.</param>
|
||||
public static void ForwardEnumState<TEnum>(this IHandlerContainer container) where TEnum : Enum
|
||||
=> container.EnumStateKeeper<TEnum>().MoveForward(container.HandlingUpdate);
|
||||
|
||||
/// <summary>
|
||||
/// Moves the enum state backward to the previous value in the enum sequence.
|
||||
/// </summary>
|
||||
/// <typeparam name="TEnum">The enum type for state management.</typeparam>
|
||||
/// <param name="container">The handler container.</param>
|
||||
public static void BackwardEnumState<TEnum>(this IHandlerContainer container) where TEnum : Enum
|
||||
=> container.EnumStateKeeper<TEnum>().MoveBackward(container.HandlingUpdate);
|
||||
}
|
||||
}
|
||||
@@ -1,92 +0,0 @@
|
||||
using Telegrator.Annotations.StateKeeping;
|
||||
using Telegrator.Core.Handlers;
|
||||
using Telegrator.Core.StateKeeping;
|
||||
|
||||
namespace Telegrator.StateKeeping
|
||||
{
|
||||
/// <summary>
|
||||
/// State keeper that manages numeric (integer) states for chat sessions.
|
||||
/// Inherits from <see cref="StateKeeperBase{TKey, TState}"/> with long keys and int states.
|
||||
/// Provides automatic increment/decrement functionality for state transitions.
|
||||
/// </summary>
|
||||
public class NumericStateKeeper : StateKeeperBase<long, int>
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the default state value, which is 1.
|
||||
/// </summary>
|
||||
public override int DefaultState => 1;
|
||||
|
||||
/// <summary>
|
||||
/// Moves the numeric state backward by decrementing the current state value.
|
||||
/// </summary>
|
||||
/// <param name="currentState">The current numeric state value</param>
|
||||
/// <param name="_">The chat ID (unused in this implementation)</param>
|
||||
/// <returns>The decremented state value</returns>
|
||||
protected override int MoveBackward(int currentState, long _)
|
||||
{
|
||||
return currentState - 1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Moves the numeric state forward by incrementing the current state value.
|
||||
/// </summary>
|
||||
/// <param name="currentState">The current numeric state value</param>
|
||||
/// <param name="_">The chat ID (unused in this implementation)</param>
|
||||
/// <returns>The incremented state value</returns>
|
||||
protected override int MoveForward(int currentState, long _)
|
||||
{
|
||||
return currentState + 1;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Provides extension methods for managing numeric states in handler containers.
|
||||
/// </summary>
|
||||
public static partial class StateHandlerContainerExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the numeric state keeper instance associated with the handler container.
|
||||
/// </summary>
|
||||
/// <param name="_">The handler container instance</param>
|
||||
/// <returns>The <see cref="NumericStateKeeper"/> instance</returns>
|
||||
public static NumericStateKeeper NumericStateKeeper(this IHandlerContainer _)
|
||||
=> NumericStateAttribute.Shared;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new numeric state for the current update being handled.
|
||||
/// </summary>
|
||||
/// <param name="container">The handler container instance</param>
|
||||
public static void CreateNumericState(this IHandlerContainer container)
|
||||
=> container.NumericStateKeeper().CreateState(container.HandlingUpdate);
|
||||
|
||||
/// <summary>
|
||||
/// Deletes the numeric state for the current update being handled.
|
||||
/// </summary>
|
||||
/// <param name="container">The handler container instance</param>
|
||||
public static void DeleteNumericState(this IHandlerContainer container)
|
||||
=> container.NumericStateKeeper().DeleteState(container.HandlingUpdate);
|
||||
|
||||
/// <summary>
|
||||
/// Sets the numeric state for the current update being handled.
|
||||
/// If the new state is null, uses the default state from the state keeper.
|
||||
/// </summary>
|
||||
/// <param name="container">The handler container instance</param>
|
||||
/// <param name="newState">The new numeric state to set, or null to use default</param>
|
||||
public static void SetNumericState(this IHandlerContainer container, int? newState)
|
||||
=> container.NumericStateKeeper().SetState(container.HandlingUpdate, newState ?? NumericStateAttribute.DefaultState);
|
||||
|
||||
/// <summary>
|
||||
/// Moves the numeric state forward by incrementing the current value.
|
||||
/// </summary>
|
||||
/// <param name="container">The handler container instance</param>
|
||||
public static void ForwardNumericState(this IHandlerContainer container)
|
||||
=> container.NumericStateKeeper().MoveForward(container.HandlingUpdate);
|
||||
|
||||
/// <summary>
|
||||
/// Moves the numeric state backward by decrementing the current value.
|
||||
/// </summary>
|
||||
/// <param name="container">The handler container instance</param>
|
||||
public static void BackwardNumericState(this IHandlerContainer container)
|
||||
=> container.NumericStateKeeper().MoveBackward(container.HandlingUpdate);
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
using Telegram.Bot.Types;
|
||||
using Telegrator.Core.StateKeeping;
|
||||
|
||||
namespace Telegrator.StateKeeping
|
||||
{
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
public class SenderIdResolver : IStateKeyResolver<long>
|
||||
{
|
||||
/// <summary>
|
||||
/// Resolves the sender ID from a Telegram update.
|
||||
/// </summary>
|
||||
/// <param name="keySource">The Telegram update to extract the sender ID from.</param>
|
||||
/// <returns>The sender ID as a long value.</returns>
|
||||
/// <exception cref="ArgumentException">Thrown when the update does not contain a valid sender ID.</exception>
|
||||
public long ResolveKey(Update keySource)
|
||||
=> keySource.GetSenderId() ?? throw new ArgumentException("Cannot resolve SenderID for this Update");
|
||||
}
|
||||
}
|
||||
@@ -1,87 +0,0 @@
|
||||
using Telegrator.Annotations.StateKeeping;
|
||||
using Telegrator.Core.Handlers;
|
||||
using Telegrator.Core.StateKeeping;
|
||||
|
||||
namespace Telegrator.StateKeeping
|
||||
{
|
||||
/// <summary>
|
||||
/// State keeper that manages string-based states for chat sessions.
|
||||
/// </summary>
|
||||
public class StringStateKeeper() : StateKeeperBase<long, string>()
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the default state value, which is an empty string.
|
||||
/// </summary>
|
||||
public override string DefaultState => string.Empty;
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override string MoveBackward(string currentState, long currentKey)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override string MoveForward(string currentState, long currentKey)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Provides extension methods for managing string states in handler containers.
|
||||
/// </summary>
|
||||
public static partial class StateHandlerContainerExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the string state keeper instance associated with the handler container.
|
||||
/// </summary>
|
||||
/// <param name="_">The handler container instance</param>
|
||||
/// <returns>The <see cref="StringStateKeeper"/> instance</returns>
|
||||
public static StringStateKeeper StringStateKeeper(this IHandlerContainer _)
|
||||
=> StringStateAttribute.Shared;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new string state for the current update being handled.
|
||||
/// </summary>
|
||||
/// <param name="container">The handler container instance</param>
|
||||
public static void CreateStringState(this IHandlerContainer container)
|
||||
=> container.StringStateKeeper().CreateState(container.HandlingUpdate);
|
||||
|
||||
/// <summary>
|
||||
/// Deletes the string state for the current update being handled.
|
||||
/// </summary>
|
||||
/// <param name="container">The handler container instance</param>
|
||||
public static void DeleteStringState(this IHandlerContainer container)
|
||||
=> container.StringStateKeeper().DeleteState(container.HandlingUpdate);
|
||||
|
||||
/// <summary>
|
||||
/// Sets the string state for the current update being handled.
|
||||
/// If the new state is null, uses the default state from the state keeper.
|
||||
/// </summary>
|
||||
/// <param name="container">The handler container instance</param>
|
||||
/// <param name="newState">The new string state to set, or null to use default</param>
|
||||
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()
|
||||
*/
|
||||
|
||||
/*
|
||||
/// <summary>
|
||||
/// Moves the string state forward to the next state in the sequence.
|
||||
/// </summary>
|
||||
/// <param name="container">The handler container instance</param>
|
||||
public static void ForwardStringState(this IHandlerContainer container)
|
||||
=> container.StringStateKeeper().MoveForward(container.HandlingUpdate);
|
||||
|
||||
/// <summary>
|
||||
/// Moves the string state backward to the previous state in the sequence.
|
||||
/// </summary>
|
||||
/// <param name="container">The handler container instance</param>
|
||||
public static void BackwardStringState(this IHandlerContainer container)
|
||||
=> container.StringStateKeeper().MoveBackward(container.HandlingUpdate);
|
||||
*/
|
||||
}
|
||||
}
|
||||
+5
-5
@@ -1,13 +1,13 @@
|
||||
using Telegram.Bot.Types;
|
||||
using Telegrator.Core.StateKeeping;
|
||||
using Telegrator.Core.States;
|
||||
|
||||
namespace Telegrator.StateKeeping
|
||||
namespace Telegrator.States
|
||||
{
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
public class ChatIdResolver : IStateKeyResolver<long>
|
||||
public class ChatIdResolver : IStateKeyResolver
|
||||
{
|
||||
/// <summary>
|
||||
/// Resolves the chat ID from a Telegram update.
|
||||
@@ -15,7 +15,7 @@ namespace Telegrator.StateKeeping
|
||||
/// <param name="keySource">The Telegram update to extract the chat ID from.</param>
|
||||
/// <returns>The chat ID as a long value.</returns>
|
||||
/// <exception cref="ArgumentException">Thrown when the update does not contain a valid chat ID.</exception>
|
||||
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");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
using System.Collections.Concurrent;
|
||||
using Telegrator.Core.States;
|
||||
|
||||
namespace Telegrator.States;
|
||||
|
||||
public class DefaultStateStorage : IStateStorage
|
||||
{
|
||||
private readonly ConcurrentDictionary<string, object> 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<T?> GetAsync<T>(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<T>(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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
using Telegrator.Core.States;
|
||||
|
||||
namespace Telegrator.States;
|
||||
|
||||
/// <summary>
|
||||
/// State machine implementation for enum-based states.
|
||||
/// Automatically creates an array of all enum values for state navigation.
|
||||
/// </summary>
|
||||
/// <typeparam name="TEnum">The enum type to be used for state management.</typeparam>
|
||||
public class EnumStateMachine<TEnum> : IStateMachine<TEnum> where TEnum : struct, Enum, IEquatable<TEnum>
|
||||
{
|
||||
private readonly TEnum[] _states = Enum.GetValues(typeof(TEnum)).Cast<TEnum>().ToArray();
|
||||
private TEnum _defaultState => _states.FirstOrDefault();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<TEnum> Current(IStateStorage storage, string updateKey, CancellationToken cancellationToken = default)
|
||||
{
|
||||
string key = FormatKey(updateKey);
|
||||
TEnum state = await storage.GetAsync<TEnum>(key, cancellationToken);
|
||||
|
||||
return EqualityComparer<TEnum>.Default.Equals(state, default)
|
||||
? _defaultState : state;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task Advance(IStateStorage storage, string updateKey, CancellationToken cancellationToken = default)
|
||||
{
|
||||
string key = FormatKey(updateKey);
|
||||
TEnum currentState = await storage.GetAsync<TEnum>(key, cancellationToken);
|
||||
|
||||
int currentIndex = Array.IndexOf(_states, currentState);
|
||||
if (currentIndex < _states.Length - 1)
|
||||
{
|
||||
var nextState = _states[currentIndex + 1];
|
||||
await storage.SetAsync(key, nextState, cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task Retreat(IStateStorage storage, string updateKey, CancellationToken cancellationToken = default)
|
||||
{
|
||||
string key = FormatKey(updateKey);
|
||||
TEnum currentState = await storage.GetAsync<TEnum>(key, cancellationToken);
|
||||
|
||||
int currentIndex = Array.IndexOf(_states, currentState);
|
||||
if (currentIndex > 0)
|
||||
{
|
||||
var nextState = _states[currentIndex - 1];
|
||||
await storage.SetAsync(key, nextState, cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
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;
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
using Telegram.Bot.Types;
|
||||
using Telegrator.Core.States;
|
||||
|
||||
namespace Telegrator.States;
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
public class SenderIdResolver : IStateKeyResolver
|
||||
{
|
||||
/// <summary>
|
||||
/// Resolves the sender ID from a Telegram update.
|
||||
/// </summary>
|
||||
/// <param name="keySource">The Telegram update to extract the sender ID from.</param>
|
||||
/// <returns>The sender ID as a long value.</returns>
|
||||
/// <exception cref="ArgumentException">Thrown when the update does not contain a valid sender ID.</exception>
|
||||
public string ResolveKey(Update keySource)
|
||||
=> keySource.GetSenderId()?.ToString() ?? throw new ArgumentException("Cannot resolve SenderID for this Update");
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
using Telegram.Bot.Types;
|
||||
using Telegrator.Core.States;
|
||||
|
||||
namespace Telegrator.States;
|
||||
|
||||
public class StateMachine<TMachine, TState>(IStateStorage stateStorage, Update handlingUpdate)
|
||||
where TMachine : IStateMachine<TState>, new()
|
||||
where TState : IEquatable<TState>
|
||||
{
|
||||
private readonly IStateStorage _stateStorage = stateStorage;
|
||||
private readonly Update _handlingUpdate = handlingUpdate;
|
||||
private readonly IStateMachine<TState> _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<TState?> 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);
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,7 @@
|
||||
<EnforceCodeStyleInBuild>True</EnforceCodeStyleInBuild>
|
||||
|
||||
<Title>Telegrator : Telegram.Bot mediator framework</Title>
|
||||
<Version>1.16.3</Version>
|
||||
<Version>1.16.4</Version>
|
||||
<Authors>Rikitav Tim4ik</Authors>
|
||||
<Company>Rikitav Tim4ik</Company>
|
||||
<RepositoryUrl>https://github.com/Rikitav/Telegrator</RepositoryUrl>
|
||||
@@ -44,4 +44,8 @@
|
||||
<ProjectReference Include="..\..\dev\Telegrator.RoslynGenerators\Telegrator.RoslynGenerators.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Annotations\Targetted\" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -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}");
|
||||
|
||||
@@ -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
|
||||
/// <returns>An awaiter builder for callback query updates.</returns>
|
||||
public static IAwaiterHandlerBuilder<CallbackQuery> AwaitCallbackQuery(this IHandlerContainer container)
|
||||
=> container.AwaitUpdate<CallbackQuery>(UpdateType.CallbackQuery);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a state keeper instance for the specified types.
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">The type of the state key.</typeparam>
|
||||
/// <typeparam name="TState">The type of the state value.</typeparam>
|
||||
/// <typeparam name="TKeeper">The type of the state keeper.</typeparam>
|
||||
/// <param name="_">The handler container (unused).</param>
|
||||
/// <returns>The state keeper instance.</returns>
|
||||
public static TKeeper GetStateKeeper<TKey, TState, TKeeper>(this IHandlerContainer _) where TKey : notnull where TState : IEquatable<TState> where TKeeper : StateKeeperBase<TKey, TState>, new()
|
||||
=> StateKeeperAttribute<TKey, TState, TKeeper>.Shared;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -296,6 +284,47 @@ namespace Telegrator
|
||||
}
|
||||
}
|
||||
|
||||
public static class StateStorageExtensions
|
||||
{
|
||||
public static StateMachine<EnumStateMachine<TState>, TState> GetStateMachine<TState>(this IStateStorage stateStorage, Update handlingUpdate)
|
||||
where TState : struct, Enum, IEquatable<TState>
|
||||
=> new StateMachine<EnumStateMachine<TState>, TState>(stateStorage, handlingUpdate);
|
||||
|
||||
public static StateMachine<TMachine, TState> GetStateMachine<TMachine, TState>(this IStateStorage stateStorage, Update handlingUpdate)
|
||||
where TMachine : IStateMachine<TState>, new()
|
||||
where TState : IEquatable<TState>
|
||||
=> new StateMachine<TMachine, TState>(stateStorage, handlingUpdate);
|
||||
|
||||
public static StateMachine<TMachine, TState> ByChatId<TMachine, TState>(this IStateStorage stateStorage, Update handlingUpdate)
|
||||
where TMachine : IStateMachine<TState>, new()
|
||||
where TState : IEquatable<TState>
|
||||
=> new StateMachine<TMachine, TState>(stateStorage, handlingUpdate).ByChatId();
|
||||
|
||||
public static StateMachine<TMachine, TState> BySenderId<TMachine, TState>(this IStateStorage stateStorage, Update handlingUpdate)
|
||||
where TMachine : IStateMachine<TState>, new()
|
||||
where TState : IEquatable<TState>
|
||||
=> new StateMachine<TMachine, TState>(stateStorage, handlingUpdate).BySenderId();
|
||||
}
|
||||
|
||||
public static class StateMachineExtensions
|
||||
{
|
||||
public static StateMachine<TMachine, TState> ByChatId<TMachine, TState>(this StateMachine<TMachine, TState> stateMachine)
|
||||
where TMachine : IStateMachine<TState>, new()
|
||||
where TState : IEquatable<TState>
|
||||
{
|
||||
stateMachine.KeyResolver = new ChatIdResolver();
|
||||
return stateMachine;
|
||||
}
|
||||
|
||||
public static StateMachine<TMachine, TState> BySenderId<TMachine, TState>(this StateMachine<TMachine, TState> stateMachine)
|
||||
where TMachine : IStateMachine<TState>, new()
|
||||
where TState : IEquatable<TState>
|
||||
{
|
||||
stateMachine.KeyResolver = new SenderIdResolver();
|
||||
return stateMachine;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extension methods for handlers collections.
|
||||
/// Provides convenient methods for creating implicit handlers.
|
||||
|
||||
@@ -29,7 +29,7 @@ public class FilterTests
|
||||
{
|
||||
// Arrange (Given) - подготовка тестовых данных
|
||||
var anyFilter = Filter<Update>.Any();
|
||||
var context = new FilterExecutionContext<Update>(new TelegramBotInfo(null), new Update(), new Update(), new Dictionary<string, object>(), new CompletedFiltersList());
|
||||
var context = new FilterExecutionContext<Update>(null, new TelegramBotInfo(null), new Update(), new Update(), new Dictionary<string, object>(), new CompletedFiltersList());
|
||||
|
||||
// Act (When) - выполнение тестируемого действия
|
||||
var result = anyFilter.CanPass(context);
|
||||
@@ -49,7 +49,7 @@ public class FilterTests
|
||||
// Arrange
|
||||
var alwaysTrueFilter = Filter<Update>.Any();
|
||||
var reverseFilter = alwaysTrueFilter.Not();
|
||||
var context = new FilterExecutionContext<Update>(new TelegramBotInfo(null), new Update(), new Update(), new Dictionary<string, object>(), new CompletedFiltersList());
|
||||
var context = new FilterExecutionContext<Update>(null, new TelegramBotInfo(null), new Update(), new Update(), new Dictionary<string, object>(), new CompletedFiltersList());
|
||||
|
||||
// Act
|
||||
var result = reverseFilter.CanPass(context);
|
||||
@@ -74,7 +74,7 @@ public class FilterTests
|
||||
var firstFilter = Filter<Update>.If(_ => firstResult);
|
||||
var secondFilter = Filter<Update>.If(_ => secondResult);
|
||||
var andFilter = firstFilter.And(secondFilter);
|
||||
var context = new FilterExecutionContext<Update>(new TelegramBotInfo(null), new Update(), new Update(), new Dictionary<string, object>(), new CompletedFiltersList());
|
||||
var context = new FilterExecutionContext<Update>(null, new TelegramBotInfo(null), new Update(), new Update(), new Dictionary<string, object>(), new CompletedFiltersList());
|
||||
|
||||
// Act
|
||||
var result = andFilter.CanPass(context);
|
||||
@@ -99,7 +99,7 @@ public class FilterTests
|
||||
var firstFilter = Filter<Update>.If(_ => firstResult);
|
||||
var secondFilter = Filter<Update>.If(_ => secondResult);
|
||||
var orFilter = firstFilter.Or(secondFilter);
|
||||
var context = new FilterExecutionContext<Update>(new TelegramBotInfo(null), new Update(), new Update(), new Dictionary<string, object>(), new CompletedFiltersList());
|
||||
var context = new FilterExecutionContext<Update>(null, new TelegramBotInfo(null), new Update(), new Update(), new Dictionary<string, object>(), new CompletedFiltersList());
|
||||
|
||||
// Act
|
||||
var result = orFilter.CanPass(context);
|
||||
@@ -122,7 +122,7 @@ public class FilterTests
|
||||
var filter3 = Filter<Update>.If(_ => false);
|
||||
|
||||
var compiledFilter = new CompiledFilter<Update>(filter1, filter2, filter3);
|
||||
var context = new FilterExecutionContext<Update>(new TelegramBotInfo(null), new Update(), new Update(), new Dictionary<string, object>(), new CompletedFiltersList());
|
||||
var context = new FilterExecutionContext<Update>(null, new TelegramBotInfo(null), new Update(), new Update(), new Dictionary<string, object>(), new CompletedFiltersList());
|
||||
|
||||
// Act
|
||||
var result = compiledFilter.CanPass(context);
|
||||
@@ -164,7 +164,7 @@ public class FilterTests
|
||||
wasCalled = true;
|
||||
return true;
|
||||
});
|
||||
var context = new FilterExecutionContext<Update>(new TelegramBotInfo(null), new Update(), new Update(), new Dictionary<string, object>(), new CompletedFiltersList());
|
||||
var context = new FilterExecutionContext<Update>(null, new TelegramBotInfo(null), new Update(), new Update(), new Dictionary<string, object>(), new CompletedFiltersList());
|
||||
|
||||
// Act
|
||||
var result = functionFilter.CanPass(context);
|
||||
|
||||
Reference in New Issue
Block a user