* Little renaming in StateKeeping namespace

* Changed "FilterAnnotation{T}" to work withou type specific sub-classes
* Changed interfaces "IUpdateRouter" and "IHandlersProvider" to match their names more correctly. Before filter validating was on providers, now its router works AS ITS SHOULD BE
This commit is contained in:
2025-07-27 14:19:40 +04:00
parent b86699a65e
commit b01d576564
18 changed files with 265 additions and 306 deletions
@@ -39,39 +39,5 @@ namespace Telegrator.Annotations.StateKeeping
/// <param name="myState">The string state to associate</param>
public StringStateAttribute(string myState)
: base(myState, new SenderIdResolver()) { }
/// <summary>
/// Initializes the attribute with a specific state, a custom key resolver, and a set of possible states.
/// </summary>
/// <param name="myState">The string state to associate</param>
/// <param name="keyResolver">The key resolver for state keeping</param>
/// <param name="states">The set of possible string states</param>
public StringStateAttribute(string myState, IStateKeyResolver<long> keyResolver, params string[] states)
: base(new StringStateKeeper(states), myState, keyResolver) { }
/// <summary>
/// Initializes the attribute with a special state, a custom key resolver, and a set of possible states.
/// </summary>
/// <param name="specialState">The special state to associate</param>
/// <param name="keyResolver">The key resolver for state keeping</param>
/// <param name="states">The set of possible string states</param>
public StringStateAttribute(SpecialState specialState, IStateKeyResolver<long> keyResolver, params string[] states)
: base(new StringStateKeeper(states), specialState, keyResolver) { }
/// <summary>
/// Initializes the attribute with a specific state, the default sender ID resolver, and a set of possible states.
/// </summary>
/// <param name="myState">The string state to associate</param>
/// <param name="states">The set of possible string states</param>
public StringStateAttribute(string myState, params string[] states)
: base(new StringStateKeeper(states), myState, new SenderIdResolver()) { }
/// <summary>
/// Initializes the attribute with a special state, the default sender ID resolver, and a set of possible states.
/// </summary>
/// <param name="specialState">The special state to associate</param>
/// <param name="states">The set of possible string states</param>
public StringStateAttribute(SpecialState specialState, params string[] states)
: base(new StringStateKeeper(states), specialState, new SenderIdResolver()) { }
}
}
+8 -21
View File
@@ -12,7 +12,10 @@ namespace Telegrator.Attributes
public abstract class FilterAnnotation<T> : UpdateFilterAttribute<T>, IFilter<T> where T : class
{
/// <inheritdoc/>
public bool IsCollectible => false;
public virtual bool IsCollectible { get; } = false;
/// <inheritdoc/>
public override UpdateType[] AllowedTypes { get; } = typeof(T).GetAllowedUpdateTypes();
/// <summary>
/// Initializes new instance of <see cref="FilterAnnotation{T}"/>
@@ -23,27 +26,11 @@ namespace Telegrator.Attributes
AnonymousFilter = AnonymousTypeFilter.Compile(UpdateFilter, GetFilterringTarget);
}
/// <inheritdoc/>
public override T? GetFilterringTarget(Update update)
=> update.GetActualUpdateObject<T>();
/// <inheritdoc/>
public abstract bool CanPass(FilterExecutionContext<T> context);
}
/// <inheritdoc/>
public abstract class MessageFilterAnnotation() : FilterAnnotation<Message>()
{
/// <inheritdoc/>
public override UpdateType[] AllowedTypes => [UpdateType.Message, UpdateType.EditedMessage, UpdateType.ChannelPost, UpdateType.EditedChannelPost, UpdateType.BusinessMessage, UpdateType.EditedBusinessMessage];
/// <inheritdoc/>
public override Message? GetFilterringTarget(Update update) => update.Message;
}
/// <inheritdoc/>
public abstract class CallbackQueryFilterAnnotation() : FilterAnnotation<CallbackQuery>()
{
/// <inheritdoc/>
public override UpdateType[] AllowedTypes => [UpdateType.CallbackQuery];
/// <inheritdoc/>
public override CallbackQuery? GetFilterringTarget(Update update) => update.CallbackQuery;
}
}
+17 -7
View File
@@ -15,10 +15,20 @@ namespace Telegrator.Attributes
/// <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 StateKeeper { get; internal set; } = null!;
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.
@@ -37,8 +47,7 @@ namespace Telegrator.Attributes
/// <param name="keyResolver">The key resolver for state keeping</param>
protected StateKeeperAttribute(TState myState, IStateKeyResolver<TKey> keyResolver) : base(typeof(TKeeper))
{
StateKeeper ??= new TKeeper();
StateKeeper.KeyResolver = keyResolver;
Shared.KeyResolver = keyResolver;
MyState = myState;
SpecialState = SpecialState.None;
}
@@ -50,12 +59,12 @@ namespace Telegrator.Attributes
/// <param name="keyResolver">The key resolver for state keeping</param>
protected StateKeeperAttribute(SpecialState specialState, IStateKeyResolver<TKey> keyResolver) : base(typeof(TKeeper))
{
StateKeeper ??= new TKeeper();
StateKeeper.KeyResolver = keyResolver;
MyState = StateKeeper.DefaultState;
Shared.KeyResolver = keyResolver;
MyState = Shared.DefaultState;
SpecialState = specialState;
}
/*
/// <summary>
/// Initializes the attribute with a custom state keeper, a specific state, and a custom key resolver.
/// </summary>
@@ -83,6 +92,7 @@ namespace Telegrator.Attributes
MyState = StateKeeper.DefaultState;
SpecialState = specialState;
}
*/
/// <summary>
/// Determines whether the current update context passes the state filter.
@@ -94,7 +104,7 @@ namespace Telegrator.Attributes
if (SpecialState == SpecialState.AnyState)
return true;
if (!StateKeeper.TryGetState(context.Input, out TState? state))
if (!Shared.TryGetState(context.Input, out TState? state))
return SpecialState == SpecialState.NoState;
if (state == null)
+9 -40
View File
@@ -1,6 +1,4 @@
using Telegram.Bot;
using Telegram.Bot.Types;
using Telegram.Bot.Types.Enums;
using Telegram.Bot.Types.Enums;
using Telegrator.Handlers.Components;
using Telegrator.MadiatorCore.Descriptors;
@@ -17,51 +15,22 @@ namespace Telegrator.MadiatorCore
public IEnumerable<UpdateType> AllowedTypes { get; }
/// <summary>
/// Gets the handlers for the specified update and context.
///
/// </summary>
/// <param name="updateRouter">The update router.</param>
/// <param name="client">The Telegram bot client.</param>
/// <param name="update">The update to handle.</param>
/// <param name="cancellationToken"></param>
/// <returns>An enumerable of described handler info.</returns>
public IEnumerable<DescribedHandlerInfo> GetHandlers(IUpdateRouter updateRouter, ITelegramBotClient client, Update update, CancellationToken cancellationToken = default);
/// <param name="updateType"></param>
/// <param name="list"></param>
/// <returns></returns>
public bool TryGetDescriptorList(UpdateType updateType, out HandlerDescriptorList? list);
/// <summary>
/// Describes all handler descriptors in the list for the given context.
/// </summary>
/// <param name="descriptors">The handler descriptor list.</param>
/// <param name="updateRouter">The update router.</param>
/// <param name="client">The Telegram bot client.</param>
/// <param name="update">The update to handle.</param>
/// <param name="cancellationToken"></param>
/// <returns>An enumerable of described handler info.</returns>
public IEnumerable<DescribedHandlerInfo> DescribeDescriptors(HandlerDescriptorList descriptors, IUpdateRouter updateRouter, ITelegramBotClient client, Update update, CancellationToken cancellationToken = default);
/// <summary>
/// Describes a single handler descriptor for the given context.
/// </summary>
/// <param name="descriptor">The handler descriptor.</param>
/// <param name="updateRouter">The update router.</param>
/// <param name="client">The Telegram bot client.</param>
/// <param name="update">The update to handle.</param>
/// <param name="cancellationToken"></param>
/// <returns>The described handler info, or null if not applicable.</returns>
public DescribedHandlerInfo? DescribeHandler(HandlerDescriptor descriptor, IUpdateRouter updateRouter, ITelegramBotClient client, Update update, CancellationToken cancellationToken = default);
/// <summary>
/// Gets an instance of the handler for the specified descriptor.
/// Instantiates a handler for the given descriptor, using the appropriate creation strategy based on descriptor type.
/// Supports singleton, implicit, keyed, and general descriptor types with different instantiation patterns.
/// </summary>
/// <param name="descriptor">The handler descriptor.</param>
/// <param name="cancellationToken"></param>
/// <returns>The handler instance.</returns>
/// <returns>An instance of <see cref="UpdateHandlerBase"/> for the descriptor</returns>
public UpdateHandlerBase GetHandlerInstance(HandlerDescriptor descriptor, CancellationToken cancellationToken = default);
/// <summary>
/// Gets the list of bot commands supported by the provider.
/// </summary>
/// <returns>An enumerable of bot commands.</returns>
public IEnumerable<BotCommand> GetBotCommands(CancellationToken cancellationToken = default);
/// <summary>
/// Determines whether the provider contains any handlers.
/// </summary>
+128 -37
View File
@@ -1,10 +1,10 @@
using System;
using System.Text;
using System.Text;
using Telegram.Bot;
using Telegram.Bot.Polling;
using Telegram.Bot.Types;
using Telegram.Bot.Types.Enums;
using Telegrator.Configuration;
using Telegrator.Filters.Components;
using Telegrator.Handlers.Components;
using Telegrator.MadiatorCore;
using Telegrator.MadiatorCore.Descriptors;
@@ -17,25 +17,11 @@ namespace Telegrator.Polling
/// </summary>
public class UpdateRouter : IUpdateRouter
{
/// <summary>
/// The bot configuration options.
/// </summary>
private readonly TelegramBotOptions _options;
/// <summary>
/// The provider for regular handlers.
/// </summary>
private readonly IHandlersProvider _handlersProvider;
/// <summary>
/// The provider for awaiting handlers.
/// </summary>
private readonly IAwaitingProvider _awaitingProvider;
/// <summary>
/// The pool for managing handler execution.
/// </summary>
private readonly IUpdateHandlersPool _HandlersPool;
private readonly ITelegramBotInfo _botInfo;
/// <inheritdoc/>
public IHandlersProvider HandlersProvider => _handlersProvider;
@@ -61,12 +47,14 @@ namespace Telegrator.Polling
/// <param name="handlersProvider">The provider for regular handlers.</param>
/// <param name="awaitingProvider">The provider for awaiting handlers.</param>
/// <param name="options">The bot configuration options.</param>
public UpdateRouter(IHandlersProvider handlersProvider, IAwaitingProvider awaitingProvider, TelegramBotOptions options)
/// <param name="botInfo"></param>
public UpdateRouter(IHandlersProvider handlersProvider, IAwaitingProvider awaitingProvider, TelegramBotOptions options, ITelegramBotInfo botInfo)
{
_options = options;
_handlersProvider = handlersProvider;
_awaitingProvider = awaitingProvider;
_HandlersPool = new UpdateHandlersPool(_options, _options.GlobalCancellationToken);
_botInfo = botInfo;
}
/// <summary>
@@ -76,12 +64,14 @@ namespace Telegrator.Polling
/// <param name="awaitingProvider">The provider for awaiting handlers.</param>
/// <param name="options">The bot configuration options.</param>
/// <param name="handlersPool">The custom handlers pool to use.</param>
public UpdateRouter(IHandlersProvider handlersProvider, IAwaitingProvider awaitingProvider, TelegramBotOptions options, IUpdateHandlersPool handlersPool)
/// <param name="botInfo"></param>
public UpdateRouter(IHandlersProvider handlersProvider, IAwaitingProvider awaitingProvider, TelegramBotOptions options, IUpdateHandlersPool handlersPool, ITelegramBotInfo botInfo)
{
_options = options;
_handlersProvider = handlersProvider;
_awaitingProvider = awaitingProvider;
_HandlersPool = handlersPool;
_botInfo = botInfo;
}
/// <summary>
@@ -112,38 +102,139 @@ namespace Telegrator.Polling
LeveledDebug.RouterWriteLine("Received Update ({0}) of type \"{1}\"", update.Id, update.Type);
LogUpdate(update);
// Queuing handlers for execution
foreach (DescribedHandlerInfo handler in GetHandlers(botClient, update, cancellationToken))
HandlersPool.Enqueue(handler);
LeveledDebug.RouterWriteLine("Receiving Update ({0}) finished", update.Id);
return Task.CompletedTask;
}
private IEnumerable<DescribedHandlerInfo> GetHandlers(ITelegramBotClient botClient, Update update, CancellationToken cancellationToken)
{
try
{
// Getting handlers in update awaiting pool
IEnumerable<DescribedHandlerInfo> handlers = AwaitingProvider.GetHandlers(this, botClient, update, cancellationToken);
if (handlers.Any() && Options.ExclusiveAwaitingHandlerRouting)
return handlers;
IEnumerable<DescribedHandlerInfo> handlers = GetHandlers(AwaitingProvider, this, botClient, update, cancellationToken);
if (handlers.Any())
{
// Enqueuing found awiting handlers
HandlersPool.Enqueue(handlers);
return handlers.Concat(HandlersProvider.GetHandlers(this, botClient, update, cancellationToken));
// Chicking if awaiting handlers has exclusive routing
if (Options.ExclusiveAwaitingHandlerRouting)
{
LeveledDebug.RouterWriteLine("Receiving Update ({0}) completed with only awaiting handlers", update.Id);
return Task.CompletedTask;
}
}
// Queuing reagular handlers for execution
HandlersPool.Enqueue(GetHandlers(HandlersProvider, this, botClient, update, cancellationToken));
LeveledDebug.RouterWriteLine("Receiving Update ({0}) finished", update.Id);
return Task.CompletedTask;
}
catch (OperationCanceledException)
{
_ = 0xBAD + 0xC0DE;
return [];
LeveledDebug.RouterWriteLine("Receiving Update ({0}) cancelled", update.Id);
return Task.CompletedTask;
}
catch (Exception ex)
{
LeveledDebug.RouterWriteLine("Receiving Update ({0}) finished with exception {1}", update.Id, ex.Message);
ExceptionHandler?.HandleException(botClient, ex, HandleErrorSource.PollingError, cancellationToken);
return [];
return Task.CompletedTask;
}
}
private static void LogUpdate(Update update)
/// <summary>
/// Gets the handlers that match the specified update, using the provided router and client.
/// Searches for handlers by update type, falling back to Unknown type if no specific handlers are found.
/// </summary>
/// <param name="provider">The privode used to get handlers instance</param>
/// <param name="updateRouter">The update router for handler execution</param>
/// <param name="client">The Telegram bot client instance</param>
/// <param name="update">The incoming Telegram update to process</param>
/// <param name="cancellationToken"></param>
/// <returns>A collection of described handler information for the update</returns>
protected virtual IEnumerable<DescribedHandlerInfo> GetHandlers(IHandlersProvider provider, IUpdateRouter updateRouter, ITelegramBotClient client, Update update, CancellationToken cancellationToken = default)
{
LeveledDebug.ProviderWriteLine("Requested handlers for UpdateType.{0}", update.Type);
if (!provider.TryGetDescriptorList(update.Type, out HandlerDescriptorList? descriptors))
{
LeveledDebug.ProviderWriteLine("No registered, providing Any");
provider.TryGetDescriptorList(UpdateType.Unknown, out descriptors);
}
if (descriptors == null || descriptors.Count == 0)
{
LeveledDebug.ProviderWriteLine("No handlers provided");
return [];
}
IEnumerable<DescribedHandlerInfo> described = DescribeDescriptors(provider, descriptors, updateRouter, client, update, cancellationToken);
LeveledDebug.ProviderWriteLine("Described total of {0} handlers for Update ({1}) from {2} provider", described.Count(), update.Id, provider.GetType().Name);
LeveledDebug.ProviderWriteLine("Described handlers : {0}", string.Join(", ", described));
return described;
}
/// <summary>
/// Describes all handler descriptors for a given update context.
/// Processes descriptors in reverse order and respects the ExecuteOnlyFirstFoundHanlder option.
/// </summary>
/// <param name="provider">The privode used to get handlers instance</param>
/// <param name="descriptors">The list of handler descriptors to process</param>
/// <param name="updateRouter">The update router for handler execution</param>
/// <param name="client">The Telegram bot client instance</param>
/// <param name="update">The incoming Telegram update to process</param>
/// <param name="cancellationToken"></param>
/// <returns>A collection of described handler information</returns>
protected virtual IEnumerable<DescribedHandlerInfo> DescribeDescriptors(IHandlersProvider provider, HandlerDescriptorList descriptors, IUpdateRouter updateRouter, ITelegramBotClient client, Update update, CancellationToken cancellationToken = default)
{
try
{
LeveledDebug.ProviderWriteLine("Describing descriptors of descriptorsList.HandlingType.{0} for Update ({1})", descriptors.HandlingType, update.Id);
foreach (HandlerDescriptor descriptor in descriptors.Reverse())
{
cancellationToken.ThrowIfCancellationRequested();
DescribedHandlerInfo? describedHandler = DescribeHandler(provider, descriptor, updateRouter, client, update, cancellationToken);
if (describedHandler == null)
continue;
yield return describedHandler;
if (Options.ExecuteOnlyFirstFoundHanlder)
break;
}
}
finally
{
LeveledDebug.ProviderWriteLine("Describing for Update ({0}) finished", update.Id);
}
}
/// <summary>
/// Describes a single handler descriptor for a given update context.
/// Validates the handler's filters against the update and creates a handler instance if validation passes.
/// </summary>
/// <param name="provider">The privode used to get handlers instance</param>
/// <param name="descriptor">The handler descriptor to process</param>
/// <param name="updateRouter">The update router for handler execution</param>
/// <param name="client">The Telegram bot client instance</param>
/// <param name="update">The incoming Telegram update to process</param>
/// <param name="cancellationToken"></param>
/// <returns>The described handler info if validation passes; otherwise, null</returns>
public virtual DescribedHandlerInfo? DescribeHandler(IHandlersProvider provider, HandlerDescriptor descriptor, IUpdateRouter updateRouter, ITelegramBotClient client, Update update, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
Dictionary<string, object> data = new Dictionary<string, object>()
{
{ "handler_name", descriptor.ToString() }
};
FilterExecutionContext<Update> filterContext = new FilterExecutionContext<Update>(_botInfo, update, update, data, []);
if (!descriptor.Filters.Validate(filterContext))
return null;
UpdateHandlerBase handlerInstance = provider.GetHandlerInstance(descriptor, cancellationToken);
return new DescribedHandlerInfo(updateRouter, client, handlerInstance, filterContext, descriptor.DisplayString);
}
/// <summary>
/// Methos used to log received <see cref="Update"/> object
/// </summary>
/// <param name="update"></param>
/// <exception cref="NullReferenceException"></exception>
protected static void LogUpdate(Update update)
{
switch (update.Type)
{
+5 -6
View File
@@ -1,5 +1,4 @@
using Telegram.Bot;
using Telegram.Bot.Types;
using Telegram.Bot.Types.Enums;
using Telegrator.Configuration;
using Telegrator.MadiatorCore;
using Telegrator.MadiatorCore.Descriptors;
@@ -11,8 +10,7 @@ namespace Telegrator.Providers
/// Extends HandlersProvider to provide functionality for creating and managing awaiter handlers.
/// </summary>
/// <param name="options">The bot configuration options.</param>
/// <param name="botInfo">The bot information.</param>
public class AwaitingProvider(TelegramBotOptions options, ITelegramBotInfo botInfo) : HandlersProvider([], options, botInfo), IAwaitingProvider
public class AwaitingProvider(TelegramBotOptions options) : HandlersProvider([], options), IAwaitingProvider
{
/// <summary>
/// List of handler descriptors for awaiting handlers.
@@ -20,9 +18,10 @@ namespace Telegrator.Providers
protected readonly HandlerDescriptorList HandlersList = [];
/// <inheritdoc/>
public override IEnumerable<DescribedHandlerInfo> GetHandlers(IUpdateRouter updateRouter, ITelegramBotClient client, Update update, CancellationToken cancellationToken = default)
public override bool TryGetDescriptorList(UpdateType updateType, out HandlerDescriptorList? list)
{
return DescribeDescriptors(HandlersList, updateRouter, client, update, cancellationToken);
list = HandlersList;
return true;
}
/// <inheritdoc/>
+8 -135
View File
@@ -1,12 +1,7 @@
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Reflection;
using Telegram.Bot;
using Telegram.Bot.Types;
using Telegram.Bot.Types.Enums;
using Telegrator.Annotations;
using Telegrator.Configuration;
using Telegrator.Filters.Components;
using Telegrator.Handlers.Components;
using Telegrator.MadiatorCore;
using Telegrator.MadiatorCore.Descriptors;
@@ -27,31 +22,24 @@ namespace Telegrator.Providers
/// Read-only dictionary mapping <see cref="UpdateType"/> to lists of handler descriptors.
/// Each descriptor list is frozen to prevent modification after initialization.
/// </summary>
protected readonly ReadOnlyDictionary<UpdateType, HandlerDescriptorList> HandlersDictionary;
public readonly ReadOnlyDictionary<UpdateType, HandlerDescriptorList> HandlersDictionary;
/// <summary>
/// Configuration options for the bot and handler execution behavior.
/// </summary>
protected readonly TelegramBotOptions Options;
/// <summary>
/// Information about the Telegram bot instance, used for filter context creation.
/// </summary>
protected readonly ITelegramBotInfo BotInfo;
/// <summary>
/// Initializes a new instance of <see cref="HandlersProvider"/> with the specified handler collections and configuration.
/// </summary>
/// <param name="handlers">Collection of handler descriptor lists organized by update type</param>
/// <param name="options">Configuration options for the bot and handler execution</param>
/// <param name="botInfo">Information about the Telegram bot instance</param>
/// <exception cref="ArgumentNullException">Thrown when options or botInfo is null</exception>
public HandlersProvider(IHandlersCollection handlers, TelegramBotOptions options, ITelegramBotInfo botInfo)
public HandlersProvider(IHandlersCollection handlers, TelegramBotOptions options)
{
AllowedTypes = handlers.AllowedTypes;
HandlersDictionary = handlers.Values.ForEach(list => list.Freeze()).ToReadOnlyDictionary(list => list.HandlingType);
Options = options ?? throw new ArgumentNullException(nameof(options));
BotInfo = botInfo ?? throw new ArgumentNullException(nameof(botInfo));
}
/// <summary>
@@ -59,112 +47,15 @@ namespace Telegrator.Providers
/// </summary>
/// <param name="handlers">Collection of handler descriptor lists organized by update type</param>
/// <param name="options">Configuration options for the bot and handler execution</param>
/// <param name="botInfo">Information about the Telegram bot instance</param>
/// <exception cref="ArgumentNullException">Thrown when options or botInfo is null</exception>
public HandlersProvider(IEnumerable<HandlerDescriptorList> handlers, TelegramBotOptions options, ITelegramBotInfo botInfo)
public HandlersProvider(IEnumerable<HandlerDescriptorList> handlers, TelegramBotOptions options)
{
AllowedTypes = Update.AllTypes;
HandlersDictionary = handlers.ForEach(list => list.Freeze()).ToReadOnlyDictionary(list => list.HandlingType);
Options = options ?? throw new ArgumentNullException(nameof(options));
BotInfo = botInfo ?? throw new ArgumentNullException(nameof(botInfo));
}
/// <summary>
/// Gets the handlers that match the specified update, using the provided router and client.
/// Searches for handlers by update type, falling back to Unknown type if no specific handlers are found.
/// </summary>
/// <param name="updateRouter">The update router for handler execution</param>
/// <param name="client">The Telegram bot client instance</param>
/// <param name="update">The incoming Telegram update to process</param>
/// <param name="cancellationToken"></param>
/// <returns>A collection of described handler information for the update</returns>
public virtual IEnumerable<DescribedHandlerInfo> GetHandlers(IUpdateRouter updateRouter, ITelegramBotClient client, Update update, CancellationToken cancellationToken = default)
{
LeveledDebug.ProviderWriteLine("Requested handlers for UpdateType.{0}", update.Type);
if (!HandlersDictionary.TryGetValue(update.Type, out HandlerDescriptorList? descriptors))
{
LeveledDebug.ProviderWriteLine("No registered, providing Any");
HandlersDictionary.TryGetValue(UpdateType.Unknown, out descriptors);
}
if (descriptors == null || descriptors.Count == 0)
{
LeveledDebug.ProviderWriteLine("No handlers provided");
return [];
}
IEnumerable<DescribedHandlerInfo> described = DescribeDescriptors(descriptors, updateRouter, client, update, cancellationToken);
LeveledDebug.ProviderWriteLine("Described total of {0} handlers for Update ({1})", described.Count(), update.Id);
LeveledDebug.ProviderWriteLine("Described handlers : {0}", string.Join(", ", described));
return described;
}
/// <summary>
/// Describes all handler descriptors for a given update context.
/// Processes descriptors in reverse order and respects the ExecuteOnlyFirstFoundHanlder option.
/// </summary>
/// <param name="descriptors">The list of handler descriptors to process</param>
/// <param name="updateRouter">The update router for handler execution</param>
/// <param name="client">The Telegram bot client instance</param>
/// <param name="update">The incoming Telegram update to process</param>
/// <param name="cancellationToken"></param>
/// <returns>A collection of described handler information</returns>
public virtual IEnumerable<DescribedHandlerInfo> DescribeDescriptors(HandlerDescriptorList descriptors, IUpdateRouter updateRouter, ITelegramBotClient client, Update update, CancellationToken cancellationToken = default)
{
try
{
LeveledDebug.ProviderWriteLine("Describing descriptors of descriptorsList.HandlingType.{0} for Update ({1})", descriptors.HandlingType, update.Id);
foreach (HandlerDescriptor descriptor in descriptors.Reverse())
{
cancellationToken.ThrowIfCancellationRequested();
DescribedHandlerInfo? describedHandler = DescribeHandler(descriptor, updateRouter, client, update, cancellationToken);
if (describedHandler == null)
continue;
yield return describedHandler;
if (Options.ExecuteOnlyFirstFoundHanlder)
break;
}
}
finally
{
LeveledDebug.ProviderWriteLine("Describing for Update ({0}) finished", update.Id);
}
}
/// <summary>
/// Describes a single handler descriptor for a given update context.
/// Validates the handler's filters against the update and creates a handler instance if validation passes.
/// </summary>
/// <param name="descriptor">The handler descriptor to process</param>
/// <param name="updateRouter">The update router for handler execution</param>
/// <param name="client">The Telegram bot client instance</param>
/// <param name="update">The incoming Telegram update to process</param>
/// <param name="cancellationToken"></param>
/// <returns>The described handler info if validation passes; otherwise, null</returns>
public virtual DescribedHandlerInfo? DescribeHandler(HandlerDescriptor descriptor, IUpdateRouter updateRouter, ITelegramBotClient client, Update update, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
Dictionary<string, object> data = new Dictionary<string, object>()
{
{ "handler_name", descriptor.ToString() }
};
FilterExecutionContext<Update> filterContext = new FilterExecutionContext<Update>(BotInfo, update, update, data, []);
if (!descriptor.Filters.Validate(filterContext))
return null;
UpdateHandlerBase handlerInstance = GetHandlerInstance(descriptor, cancellationToken);
return new DescribedHandlerInfo(updateRouter, client, handlerInstance, filterContext, descriptor.DisplayString);
}
/// <summary>
/// Instantiates a handler for the given descriptor, using the appropriate creation strategy based on descriptor type.
/// Supports singleton, implicit, keyed, and general descriptor types with different instantiation patterns.
/// </summary>
/// <param name="descriptor">The handler descriptor containing type and instantiation information</param>
/// <param name="cancellationToken"></param>
/// <returns>An instance of <see cref="UpdateHandlerBase"/> for the descriptor</returns>
/// <inheritdoc/>
/// <exception cref="Exception">Thrown when the descriptor type is not recognized</exception>
public virtual UpdateHandlerBase GetHandlerInstance(HandlerDescriptor descriptor, CancellationToken cancellationToken = default)
{
@@ -192,31 +83,13 @@ namespace Telegrator.Providers
}
}
/// <summary>
/// Gets the list of bot commands defined by all handler types with <see cref="CommandAlliasAttribute"/>.
/// Extracts command aliases and descriptions from message handlers for bot command registration.
/// </summary>
/// <param name="cancellationToken"></param>
/// <returns>A collection of <see cref="BotCommand"/> objects for the bot</returns>
public IEnumerable<BotCommand> GetBotCommands(CancellationToken cancellationToken = default)
/// <inheritdoc/>
public virtual bool TryGetDescriptorList(UpdateType updateType, out HandlerDescriptorList? list)
{
if (!HandlersDictionary.TryGetValue(UpdateType.Message, out HandlerDescriptorList? list))
yield break;
foreach (BotCommand botCommand in list
.Select(descriptor => descriptor.HandlerType)
.SelectMany(handlerType => handlerType.GetCustomAttributes<CommandAlliasAttribute>()
.SelectMany(attribute => attribute.Alliases.Select(alias => new BotCommand(alias, attribute.Description)))))
{
cancellationToken.ThrowIfCancellationRequested();
yield return botCommand;
}
return HandlersDictionary.TryGetValue(updateType, out list);
}
/// <summary>
/// Determines whether the provider contains any handlers.
/// </summary>
/// <returns>True if there are no handlers registered; otherwise, false</returns>
/// <inheritdoc/>
public virtual bool IsEmpty()
{
return HandlersDictionary.Count == 0;
+2 -2
View File
@@ -30,7 +30,7 @@ namespace Telegrator.StateKeeping
/// <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>.StateKeeper;
=> EnumStateAttribute<TEnum>.Shared;
/// <summary>
/// Creates a new enum state for the current update.
@@ -55,7 +55,7 @@ namespace Telegrator.StateKeeping
/// <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>.StateKeeper.DefaultState);
=> container.EnumStateKeeper<TEnum>().SetState(container.HandlingUpdate, newState ?? EnumStateAttribute<TEnum>.DefaultState);
/// <summary>
/// Moves the enum state forward to the next value in the enum sequence.
@@ -50,7 +50,7 @@ namespace Telegrator.StateKeeping
/// <param name="_">The handler container instance</param>
/// <returns>The <see cref="NumericStateKeeper"/> instance</returns>
public static NumericStateKeeper NumericStateKeeper(this IHandlerContainer _)
=> NumericStateAttribute.StateKeeper;
=> NumericStateAttribute.Shared;
/// <summary>
/// Creates a new numeric state for the current update being handled.
@@ -73,7 +73,7 @@ namespace Telegrator.StateKeeping
/// <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.StateKeeper.DefaultState);
=> container.NumericStateKeeper().SetState(container.HandlingUpdate, newState ?? NumericStateAttribute.DefaultState);
/// <summary>
/// Moves the numeric state forward by incrementing the current value.
+2 -3
View File
@@ -1,6 +1,5 @@
using Telegrator.Annotations.StateKeeping;
using Telegrator.Handlers.Components;
using Telegrator.StateKeeping;
namespace Telegrator.StateKeeping
{
@@ -34,7 +33,7 @@ namespace Telegrator.StateKeeping
/// <param name="_">The handler container instance</param>
/// <returns>The <see cref="StringStateKeeper"/> instance</returns>
public static StringStateKeeper StringStateKeeper(this IHandlerContainer _)
=> StringStateAttribute.StateKeeper;
=> StringStateAttribute.Shared;
/// <summary>
/// Creates a new string state for the current update being handled.
@@ -57,7 +56,7 @@ namespace Telegrator.StateKeeping
/// <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.StateKeeper.DefaultState);
=> container.StringStateKeeper().SetState(container.HandlingUpdate, newState ?? StringStateAttribute.DefaultState);
/// <summary>
/// Moves the string state forward to the next state in the sequence.
+1 -1
View File
@@ -17,7 +17,7 @@
<EnableNETAnalyzers>True</EnableNETAnalyzers>
<EnforceCodeStyleInBuild>True</EnforceCodeStyleInBuild>
<PackageLicenseFile>LICENSE</PackageLicenseFile>
<Version>1.0.2</Version>
<Version>1.0.3</Version>
</PropertyGroup>
<ItemGroup>
+3 -3
View File
@@ -63,10 +63,10 @@ namespace Telegrator
if (Options.GlobalCancellationToken == CancellationToken.None)
Options.GlobalCancellationToken = cancellationToken;
HandlersProvider handlerProvider = new HandlersProvider(Handlers, Options, BotInfo);
AwaitingProvider awaitingProvider = new AwaitingProvider(Options, BotInfo);
HandlersProvider handlerProvider = new HandlersProvider(Handlers, Options);
AwaitingProvider awaitingProvider = new AwaitingProvider(Options);
updateRouter = new UpdateRouter(handlerProvider, awaitingProvider, Options);
updateRouter = new UpdateRouter(handlerProvider, awaitingProvider, Options, BotInfo);
StartReceivingInternal(receiverOptions, cancellationToken);
}
+63 -1
View File
@@ -4,6 +4,7 @@ using System.Reflection;
using Telegram.Bot.Types;
using Telegram.Bot.Types.Enums;
using Telegram.Bot.Types.Payments;
using Telegrator.Annotations;
using Telegrator.Annotations.StateKeeping;
using Telegrator.Attributes;
using Telegrator.Filters.Components;
@@ -11,6 +12,7 @@ using Telegrator.Handlers.Building;
using Telegrator.Handlers.Building.Components;
using Telegrator.Handlers.Components;
using Telegrator.MadiatorCore;
using Telegrator.MadiatorCore.Descriptors;
using Telegrator.Providers;
using Telegrator.StateKeeping;
using Telegrator.StateKeeping.Components;
@@ -87,7 +89,7 @@ namespace Telegrator
/// <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>.StateKeeper;
=> StateKeeperAttribute<TKey, TState, TKeeper>.Shared;
}
/// <summary>
@@ -161,6 +163,31 @@ namespace Telegrator
=> awaitingProvider.CreateAbstract<CallbackQuery>(UpdateType.CallbackQuery, handlingUpdate);
}
/// <summary>
/// Extesions method for handlers providers
/// </summary>
public static class HandlersProviderExtensions
{
/// <summary>
/// Gets the list of bot commands supported by the provider.
/// </summary>
/// <returns>An enumerable of bot commands.</returns>
public static IEnumerable<BotCommand> GetBotCommands(this IHandlersProvider provider, CancellationToken cancellationToken = default)
{
if (!provider.TryGetDescriptorList(UpdateType.Message, out HandlerDescriptorList? list))
yield break;
foreach (BotCommand botCommand in list
.Select(descriptor => descriptor.HandlerType)
.SelectMany(handlerType => handlerType.GetCustomAttributes<CommandAlliasAttribute>()
.SelectMany(attribute => attribute.Alliases.Select(alias => new BotCommand(alias, attribute.Description)))))
{
cancellationToken.ThrowIfCancellationRequested();
yield return botCommand;
}
}
}
/// <summary>
/// Extension methods for handlers collections.
/// Provides convenient methods for creating implicit handlers.
@@ -850,6 +877,41 @@ namespace Telegrator
_ => update
};
/// <summary>
/// Selecting corresponding <see cref="UpdateType"/>s for <see cref="Update"/>'s sub-type
/// </summary>
/// <returns></returns>
public static UpdateType[] GetAllowedUpdateTypes(this Type type) => type.FullName switch
{
"Telegram.Bot.Types.Message" => UpdateTypeExtensions.MessageTypes,
"Telegram.Bot.Types.ChatMemberUpdated" => [UpdateType.MyChatMember, UpdateType.ChatMember],
"Telegram.Bot.Types.InlineQuery" => [UpdateType.InlineQuery],
"Telegram.Bot.Types.ChosenInlineResult" => [UpdateType.ChosenInlineResult],
"Telegram.Bot.Types.CallbackQuery" => [UpdateType.CallbackQuery],
"Telegram.Bot.Types.ShippingQuery" => [UpdateType.ShippingQuery],
"Telegram.Bot.Types.PreCheckoutQuery" => [UpdateType.PreCheckoutQuery],
"Telegram.Bot.Types.Poll" => [UpdateType.Poll],
"Telegram.Bot.Types.PollAnswer" => [UpdateType.PollAnswer],
"Telegram.Bot.Types.ChatJoinRequest" => [UpdateType.ChatJoinRequest],
"Telegram.Bot.Types.MessageReactionUpdated" => [UpdateType.MessageReaction],
"Telegram.Bot.TypesMessageReactionCountUpdated" => [UpdateType.MessageReactionCount],
"Telegram.Bot.Types.ChatBoostUpdated" => [UpdateType.ChatBoost],
"Telegram.Bot.Types.ChatBoostRemoved" => [UpdateType.RemovedChatBoost],
"Telegram.Bot.Types.BusinessConnection" => [UpdateType.BusinessConnection],
"Telegram.Bot.Types.BusinessMessagesDeleted" => [UpdateType.DeletedBusinessMessages],
"Telegram.Bot.Types.PaidMediaPurchased" => [UpdateType.PurchasedPaidMedia],
"Telegram.Bot.Types.Update" => Update.AllTypes,
_ => []
};
/// <summary>
/// Selecting corresponding <see cref="UpdateType"/>s for <see cref="Update"/>'s sub-type
/// </summary>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
public static UpdateType[] GetAllowedUpdateTypes<T>() where T : class
=> GetAllowedUpdateTypes(typeof(T));
/// <summary>
/// Selects from <see cref="Update"/> an <typeparamref name="T"/> that contains information about the update
/// </summary>