diff --git a/Telegrator.Generators/ImplicitHandlerBuilderExtensionsGenerator.cs b/Telegrator.Generators/ImplicitHandlerBuilderExtensionsGenerator.cs index 271c161..9157517 100644 --- a/Telegrator.Generators/ImplicitHandlerBuilderExtensionsGenerator.cs +++ b/Telegrator.Generators/ImplicitHandlerBuilderExtensionsGenerator.cs @@ -103,10 +103,13 @@ namespace Telegrator.Generators private static void ParseClassDeclaration(StringBuilder sourceBuilder, ClassDeclarationSyntax classDeclaration, Dictionary targeters) { + string className = classDeclaration.Identifier.ToString(); + if (className == "FilterAnnotation") + return; + IEnumerable methods = classDeclaration.Members.OfType(); MethodDeclarationSyntax? targeterMethod = methods.FirstOrDefault(method => method.Identifier.ToString() == "GetFilterringTarget"); - string className = classDeclaration.Identifier.ToString(); string filterName = className.Replace("Attribute", string.Empty); string classTargetterMethodName = filterName + "_GetFilterringTarget"; diff --git a/Telegrator.Hosting/Polling/HostUpdateRouter.cs b/Telegrator.Hosting/Polling/HostUpdateRouter.cs index 5683718..edd8145 100644 --- a/Telegrator.Hosting/Polling/HostUpdateRouter.cs +++ b/Telegrator.Hosting/Polling/HostUpdateRouter.cs @@ -3,7 +3,6 @@ using Microsoft.Extensions.Options; using Telegram.Bot; using Telegram.Bot.Polling; using Telegram.Bot.Types; -using Telegrator; using Telegrator.Configuration; using Telegrator.MadiatorCore; using Telegrator.Polling; @@ -24,7 +23,8 @@ namespace Telegrator.Hosting.Polling IAwaitingProvider awaitingProvider, IOptions options, IUpdateHandlersPool handlersPool, - ILogger logger) : base(handlersProvider, awaitingProvider, options.Value, handlersPool) + ITelegramBotInfo botInfo, + ILogger logger) : base(handlersProvider, awaitingProvider, options.Value, handlersPool, botInfo) { Logger = logger; ExceptionHandler = new DefaultRouterExceptionHandler(HandleException); diff --git a/Telegrator.Hosting/Providers/HostAwaitingProvider.cs b/Telegrator.Hosting/Providers/HostAwaitingProvider.cs index cff1610..e61cd07 100644 --- a/Telegrator.Hosting/Providers/HostAwaitingProvider.cs +++ b/Telegrator.Hosting/Providers/HostAwaitingProvider.cs @@ -1,17 +1,16 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; -using Telegram.Bot; -using Telegram.Bot.Types; using Telegrator.Configuration; -using Telegrator.MadiatorCore; -using Telegrator.MadiatorCore.Descriptors; using Telegrator.Providers; namespace Telegrator.Hosting.Providers { /// - public class HostAwaitingProvider(IOptions options, ITelegramBotInfo botInfo, ILogger logger) : AwaitingProvider(options.Value, botInfo) + public class HostAwaitingProvider(IOptions options, ILogger logger) : AwaitingProvider(options.Value) { + private readonly ILogger _logger = logger; + + /* /// public override IEnumerable GetHandlers(IUpdateRouter updateRouter, ITelegramBotClient client, Update update, CancellationToken cancellationToken = default) { @@ -19,5 +18,6 @@ namespace Telegrator.Hosting.Providers logger.LogInformation("Described awaiting handlers : {handlers}", string.Join(", ", handlers.Select(hndlr => hndlr.HandlerInstance.GetType().Name))); return handlers; } + */ } } diff --git a/Telegrator.Hosting/Providers/HostHandlersProvider.cs b/Telegrator.Hosting/Providers/HostHandlersProvider.cs index 358eb68..167737b 100644 --- a/Telegrator.Hosting/Providers/HostHandlersProvider.cs +++ b/Telegrator.Hosting/Providers/HostHandlersProvider.cs @@ -1,8 +1,6 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; -using Telegram.Bot; -using Telegram.Bot.Types; using Telegrator.Configuration; using Telegrator.Handlers.Components; using Telegrator.MadiatorCore; @@ -21,14 +19,14 @@ namespace Telegrator.Hosting.Providers public HostHandlersProvider( IHandlersCollection handlers, IOptions options, - ITelegramBotInfo botInfo, IServiceProvider serviceProvider, - ILogger logger) : base(handlers, options.Value, botInfo) + ILogger logger) : base(handlers, options.Value) { Services = serviceProvider; Logger = logger; } + /* /// public override IEnumerable GetHandlers(IUpdateRouter updateRouter, ITelegramBotClient client, Update update, CancellationToken cancellationToken = default) { @@ -36,18 +34,20 @@ namespace Telegrator.Hosting.Providers Logger.LogInformation("Described handlers : {handlers}", string.Join(", ", handlers.Select(hndlr => hndlr.DisplayString ?? hndlr.HandlerInstance.GetType().Name))); return handlers; } + */ /// public override UpdateHandlerBase GetHandlerInstance(HandlerDescriptor descriptor, CancellationToken cancellationToken = default) { cancellationToken.ThrowIfCancellationRequested(); IServiceScope scope = Services.CreateScope(); + object handlerInstance = descriptor.ServiceKey == null ? scope.ServiceProvider.GetRequiredService(descriptor.HandlerType) : scope.ServiceProvider.GetRequiredKeyedService(descriptor.HandlerType, descriptor.ServiceKey); if (handlerInstance is not UpdateHandlerBase updateHandler) - throw new InvalidOperationException(); + throw new InvalidOperationException("Failed to resolve " + descriptor.HandlerType + " as UpdateHandlerBase"); updateHandler.LifetimeToken.OnLifetimeEnded += _ => scope.Dispose(); return updateHandler; diff --git a/Telegrator.Hosting/Telegrator.Hosting.csproj b/Telegrator.Hosting/Telegrator.Hosting.csproj index 278fdef..4eca130 100644 --- a/Telegrator.Hosting/Telegrator.Hosting.csproj +++ b/Telegrator.Hosting/Telegrator.Hosting.csproj @@ -15,7 +15,7 @@ True True LICENSE - 1.0.2 + 1.0.3 diff --git a/Telegrator/Annotations/StateKeeping/StringStateAttribute.cs b/Telegrator/Annotations/StateKeeping/StringStateAttribute.cs index e2f684a..e91a0ac 100644 --- a/Telegrator/Annotations/StateKeeping/StringStateAttribute.cs +++ b/Telegrator/Annotations/StateKeeping/StringStateAttribute.cs @@ -39,39 +39,5 @@ namespace Telegrator.Annotations.StateKeeping /// The string state to associate public StringStateAttribute(string myState) : base(myState, new SenderIdResolver()) { } - - /// - /// Initializes the attribute with a specific state, a custom key resolver, and a set of possible states. - /// - /// The string state to associate - /// The key resolver for state keeping - /// The set of possible string states - public StringStateAttribute(string myState, IStateKeyResolver keyResolver, params string[] states) - : base(new StringStateKeeper(states), myState, keyResolver) { } - - /// - /// Initializes the attribute with a special state, a custom key resolver, and a set of possible states. - /// - /// The special state to associate - /// The key resolver for state keeping - /// The set of possible string states - public StringStateAttribute(SpecialState specialState, IStateKeyResolver keyResolver, params string[] states) - : base(new StringStateKeeper(states), specialState, keyResolver) { } - - /// - /// Initializes the attribute with a specific state, the default sender ID resolver, and a set of possible states. - /// - /// The string state to associate - /// The set of possible string states - public StringStateAttribute(string myState, params string[] states) - : base(new StringStateKeeper(states), myState, new SenderIdResolver()) { } - - /// - /// Initializes the attribute with a special state, the default sender ID resolver, and a set of possible states. - /// - /// The special state to associate - /// The set of possible string states - public StringStateAttribute(SpecialState specialState, params string[] states) - : base(new StringStateKeeper(states), specialState, new SenderIdResolver()) { } } } diff --git a/Telegrator/Attributes/FilterAnnotation.cs b/Telegrator/Attributes/FilterAnnotation.cs index cb89ff7..40bd0b5 100644 --- a/Telegrator/Attributes/FilterAnnotation.cs +++ b/Telegrator/Attributes/FilterAnnotation.cs @@ -12,7 +12,10 @@ namespace Telegrator.Attributes public abstract class FilterAnnotation : UpdateFilterAttribute, IFilter where T : class { /// - public bool IsCollectible => false; + public virtual bool IsCollectible { get; } = false; + + /// + public override UpdateType[] AllowedTypes { get; } = typeof(T).GetAllowedUpdateTypes(); /// /// Initializes new instance of @@ -23,27 +26,11 @@ namespace Telegrator.Attributes AnonymousFilter = AnonymousTypeFilter.Compile(UpdateFilter, GetFilterringTarget); } + /// + public override T? GetFilterringTarget(Update update) + => update.GetActualUpdateObject(); + /// public abstract bool CanPass(FilterExecutionContext context); } - - /// - public abstract class MessageFilterAnnotation() : FilterAnnotation() - { - /// - public override UpdateType[] AllowedTypes => [UpdateType.Message, UpdateType.EditedMessage, UpdateType.ChannelPost, UpdateType.EditedChannelPost, UpdateType.BusinessMessage, UpdateType.EditedBusinessMessage]; - - /// - public override Message? GetFilterringTarget(Update update) => update.Message; - } - - /// - public abstract class CallbackQueryFilterAnnotation() : FilterAnnotation() - { - /// - public override UpdateType[] AllowedTypes => [UpdateType.CallbackQuery]; - - /// - public override CallbackQuery? GetFilterringTarget(Update update) => update.CallbackQuery; - } } diff --git a/Telegrator/Attributes/StateKeeperAttribute.cs b/Telegrator/Attributes/StateKeeperAttribute.cs index 158cc78..85ac133 100644 --- a/Telegrator/Attributes/StateKeeperAttribute.cs +++ b/Telegrator/Attributes/StateKeeperAttribute.cs @@ -15,10 +15,20 @@ namespace Telegrator.Attributes /// The type of the state keeper implementation. public abstract class StateKeeperAttribute : StateKeeperAttributeBase where TKey : notnull where TState : notnull where TKeeper : StateKeeperBase, new() { + /* + private static readonly TKeeper _shared = new TKeeper(); + private static readonly Dictionary _keyed = []; + */ + /// /// Gets or sets the singleton instance of the state keeper for this attribute type. /// - public static TKeeper StateKeeper { get; internal set; } = null!; + public static TKeeper Shared { get; } = new TKeeper(); + + /// + /// Gets the default state value of this statekeeper. + /// + public static TState DefaultState => Shared.DefaultState; /// /// Gets the state value associated with this attribute instance. @@ -37,8 +47,7 @@ namespace Telegrator.Attributes /// The key resolver for state keeping protected StateKeeperAttribute(TState myState, IStateKeyResolver 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 /// The key resolver for state keeping protected StateKeeperAttribute(SpecialState specialState, IStateKeyResolver keyResolver) : base(typeof(TKeeper)) { - StateKeeper ??= new TKeeper(); - StateKeeper.KeyResolver = keyResolver; - MyState = StateKeeper.DefaultState; + Shared.KeyResolver = keyResolver; + MyState = Shared.DefaultState; SpecialState = specialState; } + /* /// /// Initializes the attribute with a custom state keeper, a specific state, and a custom key resolver. /// @@ -83,6 +92,7 @@ namespace Telegrator.Attributes MyState = StateKeeper.DefaultState; SpecialState = specialState; } + */ /// /// 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) diff --git a/Telegrator/MadiatorCore/IHandlersProvider.cs b/Telegrator/MadiatorCore/IHandlersProvider.cs index d6555e6..3fc5675 100644 --- a/Telegrator/MadiatorCore/IHandlersProvider.cs +++ b/Telegrator/MadiatorCore/IHandlersProvider.cs @@ -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 AllowedTypes { get; } /// - /// Gets the handlers for the specified update and context. + /// /// - /// The update router. - /// The Telegram bot client. - /// The update to handle. - /// - /// An enumerable of described handler info. - public IEnumerable GetHandlers(IUpdateRouter updateRouter, ITelegramBotClient client, Update update, CancellationToken cancellationToken = default); + /// + /// + /// + public bool TryGetDescriptorList(UpdateType updateType, out HandlerDescriptorList? list); /// - /// Describes all handler descriptors in the list for the given context. - /// - /// The handler descriptor list. - /// The update router. - /// The Telegram bot client. - /// The update to handle. - /// - /// An enumerable of described handler info. - public IEnumerable DescribeDescriptors(HandlerDescriptorList descriptors, IUpdateRouter updateRouter, ITelegramBotClient client, Update update, CancellationToken cancellationToken = default); - - /// - /// Describes a single handler descriptor for the given context. - /// - /// The handler descriptor. - /// The update router. - /// The Telegram bot client. - /// The update to handle. - /// - /// The described handler info, or null if not applicable. - public DescribedHandlerInfo? DescribeHandler(HandlerDescriptor descriptor, IUpdateRouter updateRouter, ITelegramBotClient client, Update update, CancellationToken cancellationToken = default); - - /// - /// 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. /// /// The handler descriptor. /// - /// The handler instance. + /// An instance of for the descriptor public UpdateHandlerBase GetHandlerInstance(HandlerDescriptor descriptor, CancellationToken cancellationToken = default); - /// - /// Gets the list of bot commands supported by the provider. - /// - /// An enumerable of bot commands. - public IEnumerable GetBotCommands(CancellationToken cancellationToken = default); - /// /// Determines whether the provider contains any handlers. /// diff --git a/Telegrator/Polling/UpdateRouter.cs b/Telegrator/Polling/UpdateRouter.cs index 1cb768f..bd8e9ce 100644 --- a/Telegrator/Polling/UpdateRouter.cs +++ b/Telegrator/Polling/UpdateRouter.cs @@ -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 /// public class UpdateRouter : IUpdateRouter { - /// - /// The bot configuration options. - /// private readonly TelegramBotOptions _options; - - /// - /// The provider for regular handlers. - /// private readonly IHandlersProvider _handlersProvider; - - /// - /// The provider for awaiting handlers. - /// private readonly IAwaitingProvider _awaitingProvider; - - /// - /// The pool for managing handler execution. - /// private readonly IUpdateHandlersPool _HandlersPool; + private readonly ITelegramBotInfo _botInfo; /// public IHandlersProvider HandlersProvider => _handlersProvider; @@ -61,12 +47,14 @@ namespace Telegrator.Polling /// The provider for regular handlers. /// The provider for awaiting handlers. /// The bot configuration options. - public UpdateRouter(IHandlersProvider handlersProvider, IAwaitingProvider awaitingProvider, TelegramBotOptions options) + /// + public UpdateRouter(IHandlersProvider handlersProvider, IAwaitingProvider awaitingProvider, TelegramBotOptions options, ITelegramBotInfo botInfo) { _options = options; _handlersProvider = handlersProvider; _awaitingProvider = awaitingProvider; _HandlersPool = new UpdateHandlersPool(_options, _options.GlobalCancellationToken); + _botInfo = botInfo; } /// @@ -76,12 +64,14 @@ namespace Telegrator.Polling /// The provider for awaiting handlers. /// The bot configuration options. /// The custom handlers pool to use. - public UpdateRouter(IHandlersProvider handlersProvider, IAwaitingProvider awaitingProvider, TelegramBotOptions options, IUpdateHandlersPool handlersPool) + /// + public UpdateRouter(IHandlersProvider handlersProvider, IAwaitingProvider awaitingProvider, TelegramBotOptions options, IUpdateHandlersPool handlersPool, ITelegramBotInfo botInfo) { _options = options; _handlersProvider = handlersProvider; _awaitingProvider = awaitingProvider; _HandlersPool = handlersPool; + _botInfo = botInfo; } /// @@ -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 GetHandlers(ITelegramBotClient botClient, Update update, CancellationToken cancellationToken) - { try { // Getting handlers in update awaiting pool - IEnumerable handlers = AwaitingProvider.GetHandlers(this, botClient, update, cancellationToken); - if (handlers.Any() && Options.ExclusiveAwaitingHandlerRouting) - return handlers; + IEnumerable 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) + /// + /// 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. + /// + /// The privode used to get handlers instance + /// The update router for handler execution + /// The Telegram bot client instance + /// The incoming Telegram update to process + /// + /// A collection of described handler information for the update + protected virtual IEnumerable 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 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; + } + + /// + /// Describes all handler descriptors for a given update context. + /// Processes descriptors in reverse order and respects the ExecuteOnlyFirstFoundHanlder option. + /// + /// The privode used to get handlers instance + /// The list of handler descriptors to process + /// The update router for handler execution + /// The Telegram bot client instance + /// The incoming Telegram update to process + /// + /// A collection of described handler information + protected virtual IEnumerable 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); + } + } + + /// + /// 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. + /// + /// The privode used to get handlers instance + /// The handler descriptor to process + /// The update router for handler execution + /// The Telegram bot client instance + /// The incoming Telegram update to process + /// + /// The described handler info if validation passes; otherwise, null + public virtual DescribedHandlerInfo? DescribeHandler(IHandlersProvider provider, HandlerDescriptor descriptor, IUpdateRouter updateRouter, ITelegramBotClient client, Update update, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + Dictionary data = new Dictionary() + { + { "handler_name", descriptor.ToString() } + }; + + FilterExecutionContext filterContext = new FilterExecutionContext(_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); + } + + /// + /// Methos used to log received object + /// + /// + /// + protected static void LogUpdate(Update update) { switch (update.Type) { diff --git a/Telegrator/Providers/AwaitingProvider.cs b/Telegrator/Providers/AwaitingProvider.cs index ad82f8d..49ad817 100644 --- a/Telegrator/Providers/AwaitingProvider.cs +++ b/Telegrator/Providers/AwaitingProvider.cs @@ -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. /// /// The bot configuration options. - /// The bot information. - public class AwaitingProvider(TelegramBotOptions options, ITelegramBotInfo botInfo) : HandlersProvider([], options, botInfo), IAwaitingProvider + public class AwaitingProvider(TelegramBotOptions options) : HandlersProvider([], options), IAwaitingProvider { /// /// List of handler descriptors for awaiting handlers. @@ -20,9 +18,10 @@ namespace Telegrator.Providers protected readonly HandlerDescriptorList HandlersList = []; /// - public override IEnumerable 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; } /// diff --git a/Telegrator/Providers/HandlersProvider.cs b/Telegrator/Providers/HandlersProvider.cs index bb3d615..b613919 100644 --- a/Telegrator/Providers/HandlersProvider.cs +++ b/Telegrator/Providers/HandlersProvider.cs @@ -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 to lists of handler descriptors. /// Each descriptor list is frozen to prevent modification after initialization. /// - protected readonly ReadOnlyDictionary HandlersDictionary; + public readonly ReadOnlyDictionary HandlersDictionary; /// /// Configuration options for the bot and handler execution behavior. /// protected readonly TelegramBotOptions Options; - /// - /// Information about the Telegram bot instance, used for filter context creation. - /// - protected readonly ITelegramBotInfo BotInfo; - /// /// Initializes a new instance of with the specified handler collections and configuration. /// /// Collection of handler descriptor lists organized by update type /// Configuration options for the bot and handler execution - /// Information about the Telegram bot instance /// Thrown when options or botInfo is null - 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)); } /// @@ -59,112 +47,15 @@ namespace Telegrator.Providers /// /// Collection of handler descriptor lists organized by update type /// Configuration options for the bot and handler execution - /// Information about the Telegram bot instance /// Thrown when options or botInfo is null - public HandlersProvider(IEnumerable handlers, TelegramBotOptions options, ITelegramBotInfo botInfo) + public HandlersProvider(IEnumerable 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)); } - /// - /// 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. - /// - /// The update router for handler execution - /// The Telegram bot client instance - /// The incoming Telegram update to process - /// - /// A collection of described handler information for the update - public virtual IEnumerable 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 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; - } - - /// - /// Describes all handler descriptors for a given update context. - /// Processes descriptors in reverse order and respects the ExecuteOnlyFirstFoundHanlder option. - /// - /// The list of handler descriptors to process - /// The update router for handler execution - /// The Telegram bot client instance - /// The incoming Telegram update to process - /// - /// A collection of described handler information - public virtual IEnumerable 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); - } - } - - /// - /// 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. - /// - /// The handler descriptor to process - /// The update router for handler execution - /// The Telegram bot client instance - /// The incoming Telegram update to process - /// - /// The described handler info if validation passes; otherwise, null - public virtual DescribedHandlerInfo? DescribeHandler(HandlerDescriptor descriptor, IUpdateRouter updateRouter, ITelegramBotClient client, Update update, CancellationToken cancellationToken = default) - { - cancellationToken.ThrowIfCancellationRequested(); - Dictionary data = new Dictionary() - { - { "handler_name", descriptor.ToString() } - }; - - FilterExecutionContext filterContext = new FilterExecutionContext(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); - } - - /// - /// 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. - /// - /// The handler descriptor containing type and instantiation information - /// - /// An instance of for the descriptor + /// /// Thrown when the descriptor type is not recognized public virtual UpdateHandlerBase GetHandlerInstance(HandlerDescriptor descriptor, CancellationToken cancellationToken = default) { @@ -192,31 +83,13 @@ namespace Telegrator.Providers } } - /// - /// Gets the list of bot commands defined by all handler types with . - /// Extracts command aliases and descriptions from message handlers for bot command registration. - /// - /// - /// A collection of objects for the bot - public IEnumerable GetBotCommands(CancellationToken cancellationToken = default) + /// + 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() - .SelectMany(attribute => attribute.Alliases.Select(alias => new BotCommand(alias, attribute.Description))))) - { - cancellationToken.ThrowIfCancellationRequested(); - yield return botCommand; - } + return HandlersDictionary.TryGetValue(updateType, out list); } - /// - /// Determines whether the provider contains any handlers. - /// - /// True if there are no handlers registered; otherwise, false + /// public virtual bool IsEmpty() { return HandlersDictionary.Count == 0; diff --git a/Telegrator/StateKeeping/EnumStateKeeper.cs b/Telegrator/StateKeeping/EnumStateKeeper.cs index 81a2f88..54ce103 100644 --- a/Telegrator/StateKeeping/EnumStateKeeper.cs +++ b/Telegrator/StateKeeping/EnumStateKeeper.cs @@ -30,7 +30,7 @@ namespace Telegrator.StateKeeping /// The handler container (unused parameter for extension method syntax). /// The enum state keeper instance. public static EnumStateKeeper EnumStateKeeper(this IHandlerContainer _) where TEnum : Enum - => EnumStateAttribute.StateKeeper; + => EnumStateAttribute.Shared; /// /// Creates a new enum state for the current update. @@ -55,7 +55,7 @@ namespace Telegrator.StateKeeping /// The handler container. /// The new state value. If null, uses the default state. public static void SetEnumState(this IHandlerContainer container, TEnum? newState) where TEnum : Enum - => container.EnumStateKeeper().SetState(container.HandlingUpdate, newState ?? EnumStateAttribute.StateKeeper.DefaultState); + => container.EnumStateKeeper().SetState(container.HandlingUpdate, newState ?? EnumStateAttribute.DefaultState); /// /// Moves the enum state forward to the next value in the enum sequence. diff --git a/Telegrator/StateKeeping/NumericStateKeeper.cs b/Telegrator/StateKeeping/NumericStateKeeper.cs index 9f398c1..b8ea824 100644 --- a/Telegrator/StateKeeping/NumericStateKeeper.cs +++ b/Telegrator/StateKeeping/NumericStateKeeper.cs @@ -50,7 +50,7 @@ namespace Telegrator.StateKeeping /// The handler container instance /// The instance public static NumericStateKeeper NumericStateKeeper(this IHandlerContainer _) - => NumericStateAttribute.StateKeeper; + => NumericStateAttribute.Shared; /// /// Creates a new numeric state for the current update being handled. @@ -73,7 +73,7 @@ namespace Telegrator.StateKeeping /// The handler container instance /// The new numeric state to set, or null to use default public static void SetNumericState(this IHandlerContainer container, int? newState) - => container.NumericStateKeeper().SetState(container.HandlingUpdate, newState ?? NumericStateAttribute.StateKeeper.DefaultState); + => container.NumericStateKeeper().SetState(container.HandlingUpdate, newState ?? NumericStateAttribute.DefaultState); /// /// Moves the numeric state forward by incrementing the current value. diff --git a/Telegrator/StateKeeping/StringStateKeeper.cs b/Telegrator/StateKeeping/StringStateKeeper.cs index 9df77e3..67e7382 100644 --- a/Telegrator/StateKeeping/StringStateKeeper.cs +++ b/Telegrator/StateKeeping/StringStateKeeper.cs @@ -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 /// The handler container instance /// The instance public static StringStateKeeper StringStateKeeper(this IHandlerContainer _) - => StringStateAttribute.StateKeeper; + => StringStateAttribute.Shared; /// /// Creates a new string state for the current update being handled. @@ -57,7 +56,7 @@ namespace Telegrator.StateKeeping /// The handler container instance /// The new string state to set, or null to use default public static void SetStringState(this IHandlerContainer container, string? newState) - => container.StringStateKeeper().SetState(container.HandlingUpdate, newState ?? StringStateAttribute.StateKeeper.DefaultState); + => container.StringStateKeeper().SetState(container.HandlingUpdate, newState ?? StringStateAttribute.DefaultState); /// /// Moves the string state forward to the next state in the sequence. diff --git a/Telegrator/Telegrator.csproj b/Telegrator/Telegrator.csproj index ebcb80f..36281d2 100644 --- a/Telegrator/Telegrator.csproj +++ b/Telegrator/Telegrator.csproj @@ -17,7 +17,7 @@ True True LICENSE - 1.0.2 + 1.0.3 diff --git a/Telegrator/TelegratorClient.cs b/Telegrator/TelegratorClient.cs index 94b443b..73e760e 100644 --- a/Telegrator/TelegratorClient.cs +++ b/Telegrator/TelegratorClient.cs @@ -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); } diff --git a/Telegrator/TypesExtensions.cs b/Telegrator/TypesExtensions.cs index fcf1d15..193387c 100644 --- a/Telegrator/TypesExtensions.cs +++ b/Telegrator/TypesExtensions.cs @@ -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 /// The handler container (unused). /// The state keeper instance. public static TKeeper GetStateKeeper(this IHandlerContainer _) where TKey : notnull where TState : IEquatable where TKeeper : StateKeeperBase, new() - => StateKeeperAttribute.StateKeeper; + => StateKeeperAttribute.Shared; } /// @@ -161,6 +163,31 @@ namespace Telegrator => awaitingProvider.CreateAbstract(UpdateType.CallbackQuery, handlingUpdate); } + /// + /// Extesions method for handlers providers + /// + public static class HandlersProviderExtensions + { + /// + /// Gets the list of bot commands supported by the provider. + /// + /// An enumerable of bot commands. + public static IEnumerable 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() + .SelectMany(attribute => attribute.Alliases.Select(alias => new BotCommand(alias, attribute.Description))))) + { + cancellationToken.ThrowIfCancellationRequested(); + yield return botCommand; + } + } + } + /// /// Extension methods for handlers collections. /// Provides convenient methods for creating implicit handlers. @@ -850,6 +877,41 @@ namespace Telegrator _ => update }; + /// + /// Selecting corresponding s for 's sub-type + /// + /// + 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, + _ => [] + }; + + /// + /// Selecting corresponding s for 's sub-type + /// + /// + /// + public static UpdateType[] GetAllowedUpdateTypes() where T : class + => GetAllowedUpdateTypes(typeof(T)); + /// /// Selects from an that contains information about the update ///