* 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:
2026-03-09 03:22:23 +04:00
parent e28cc2b8da
commit 162d4a1d05
56 changed files with 563 additions and 2481 deletions
@@ -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()) { }
}
}
@@ -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
{
@@ -26,6 +27,11 @@ namespace Telegrator.Core.Descriptors
/// The awaiting provider to fetch new updates inside handler
/// </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.
@@ -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);
-19
View File
@@ -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; }
}
}
+17 -1
View File
@@ -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);
}
+23 -10
View File
@@ -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;
+11 -5
View File
@@ -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;
}
+10 -3
View File
@@ -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);
*/
}
}
@@ -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;
}
}
+62
View File
@@ -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;
}
+20
View File
@@ -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");
}
+63
View File
@@ -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);
}
}
+5 -1
View File
@@ -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>
+3 -1
View File
@@ -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}");
+43 -14
View File
@@ -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.