diff --git a/Telegrator.Hosting/Polling/HostUpdateHandlersPool.cs b/Telegrator.Hosting/Polling/HostUpdateHandlersPool.cs index 5593518..2814287 100644 --- a/Telegrator.Hosting/Polling/HostUpdateHandlersPool.cs +++ b/Telegrator.Hosting/Polling/HostUpdateHandlersPool.cs @@ -6,11 +6,12 @@ using Telegrator.Polling; namespace Telegrator.Hosting.Polling { - public class HostUpdateHandlersPool(IOptions options, ILogger logger) - : UpdateHandlersPool(options.Value, options.Value.GlobalCancellationToken) + /// + public class HostUpdateHandlersPool(IOptions options, ILogger logger) : UpdateHandlersPool(options.Value, options.Value.GlobalCancellationToken) { private readonly ILogger _logger = logger; + /// protected override async Task ExecuteHandlerWrapper(DescribedHandlerInfo enqueuedHandler) { _logger.LogInformation("Handler \"{0}\" has entered execution pool", enqueuedHandler.DisplayString); diff --git a/Telegrator.Hosting/Polling/HostUpdateRouter.cs b/Telegrator.Hosting/Polling/HostUpdateRouter.cs index 94b9f6c..c80008a 100644 --- a/Telegrator.Hosting/Polling/HostUpdateRouter.cs +++ b/Telegrator.Hosting/Polling/HostUpdateRouter.cs @@ -10,23 +10,38 @@ using Telegrator.Polling; namespace Telegrator.Hosting.Polling { + /// public class HostUpdateRouter : UpdateRouter { + /// + /// of this router + /// protected readonly ILogger Logger; - public HostUpdateRouter(IHandlersProvider handlersProvider, IAwaitingProvider awaitingProvider, IOptions options, IUpdateHandlersPool handlersPool, ILogger logger) - : base(handlersProvider, awaitingProvider, options.Value, handlersPool) + // Ehat a mess :/ + /// + public HostUpdateRouter( + IHandlersProvider handlersProvider, + IAwaitingProvider awaitingProvider, + IOptions options, + IUpdateHandlersPool handlersPool, + ILogger logger) : base(handlersProvider, awaitingProvider, options.Value, handlersPool) { Logger = logger; ExceptionHandler = new HostExceptionHandler(logger); } + /// public override Task HandleUpdateAsync(ITelegramBotClient botClient, Update update, CancellationToken cancellationToken) { Logger.LogInformation("Received update of type \"{type}\"", update.Type); return base.HandleUpdateAsync(botClient, update, cancellationToken); } + /// + /// Default exception handler of this router + /// + /// private class HostExceptionHandler(ILogger logger) : IRouterExceptionHandler { public void HandleException(ITelegramBotClient botClient, Exception exception, HandleErrorSource source, CancellationToken cancellationToken) @@ -34,7 +49,7 @@ namespace Telegrator.Hosting.Polling if (exception is HandlerFaultedException handlerFaultedException) { logger.LogError("\"{handler}\" handler's execution was faulted :\n{exception}", - handlerFaultedException.HandlerInfo.DisplayString, + handlerFaultedException.HandlerInfo.ToString(), handlerFaultedException.InnerException?.ToString() ?? "No inner exception"); return; } diff --git a/Telegrator.Hosting/Polling/HostedUpdateReceiver.cs b/Telegrator.Hosting/Polling/HostedUpdateReceiver.cs index 4ac2592..bb94ec3 100644 --- a/Telegrator.Hosting/Polling/HostedUpdateReceiver.cs +++ b/Telegrator.Hosting/Polling/HostedUpdateReceiver.cs @@ -9,17 +9,26 @@ using Telegrator.Polling; namespace Telegrator.Hosting.Polling { + /// + /// Service for receiving updates for Hosted telegram bots + /// + /// + /// + /// + /// + /// public class HostedUpdateReceiver(ITelegramBotHost botHost, ITelegramBotClient botClient, IUpdateRouter updateRouter, IOptions options, ILogger logger) : BackgroundService { - private readonly ReceiverOptions ReceiverOptions = options.Value; - private readonly IUpdateRouter UpdateRouter = updateRouter; + private readonly ReceiverOptions _receiverOptions = options.Value; + private readonly IUpdateRouter _updateRouter = updateRouter; + /// protected override async Task ExecuteAsync(CancellationToken stoppingToken) { logger.LogInformation("Starting receiving updates via long-polling"); - ReceiverOptions.AllowedUpdates = botHost.UpdateRouter.HandlersProvider.AllowedTypes.ToArray(); - ReactiveUpdateReceiver updateReceiver = new ReactiveUpdateReceiver(botClient, ReceiverOptions); - await updateReceiver.ReceiveAsync(UpdateRouter, stoppingToken).ConfigureAwait(false); + _receiverOptions.AllowedUpdates = botHost.UpdateRouter.HandlersProvider.AllowedTypes.ToArray(); + ReactiveUpdateReceiver updateReceiver = new ReactiveUpdateReceiver(botClient, _receiverOptions); + await updateReceiver.ReceiveAsync(_updateRouter, stoppingToken).ConfigureAwait(false); } } } diff --git a/Telegrator.Hosting/Providers/HostAwaitingProvider.cs b/Telegrator.Hosting/Providers/HostAwaitingProvider.cs index 56b9667..cff1610 100644 --- a/Telegrator.Hosting/Providers/HostAwaitingProvider.cs +++ b/Telegrator.Hosting/Providers/HostAwaitingProvider.cs @@ -9,8 +9,10 @@ using Telegrator.Providers; namespace Telegrator.Hosting.Providers { + /// public class HostAwaitingProvider(IOptions options, ITelegramBotInfo botInfo, ILogger logger) : AwaitingProvider(options.Value, botInfo) { + /// public override IEnumerable GetHandlers(IUpdateRouter updateRouter, ITelegramBotClient client, Update update, CancellationToken cancellationToken = default) { IEnumerable handlers = base.GetHandlers(updateRouter, client, update, cancellationToken).ToArray(); diff --git a/Telegrator.Hosting/Providers/HostHandlersCollection.cs b/Telegrator.Hosting/Providers/HostHandlersCollection.cs index 5175120..bef073c 100644 --- a/Telegrator.Hosting/Providers/HostHandlersCollection.cs +++ b/Telegrator.Hosting/Providers/HostHandlersCollection.cs @@ -8,28 +8,35 @@ using Telegrator.Providers; namespace Telegrator.Hosting.Providers { + /// + /// Pre host building task + /// + /// + public delegate void PreBuildingRoutine(TelegramBotHostBuilder builder); + + /// public class HostHandlersCollection(IServiceCollection hostServiceColletion, IHandlersCollectingOptions options) : HandlersCollection(options) { private readonly IServiceCollection Services = hostServiceColletion; - public readonly List> PreBuilderRoutines = []; + + /// protected override bool MustHaveParameterlessCtor => false; + /// + /// List of tasks that should be completed right before building the bot + /// + public readonly List PreBuilderRoutines = []; + + /// public override IHandlersCollection AddHandler(Type handlerType) { - // - if (handlerType.GetInterface(nameof(IPreBuildingRoutine)) != null) - { - MethodInfo? methodInfo = handlerType.GetMethod(nameof(IPreBuildingRoutine.PreBuildingRoutine), BindingFlags.Static | BindingFlags.Public); - if (methodInfo != null) - { - Action routineDelegate = methodInfo.CreateDelegate>(null); - PreBuilderRoutines.Add(routineDelegate); - } - } + if (handlerType.IsPreBuildingRoutine(out MethodInfo? routineMethod)) + PreBuilderRoutines.Add(routineMethod.CreateDelegate(null)); return base.AddHandler(handlerType); } + /// public override IHandlersCollection AddDescriptor(HandlerDescriptor descriptor) { switch (descriptor.Type) diff --git a/Telegrator.Hosting/Providers/HostHandlersProvider.cs b/Telegrator.Hosting/Providers/HostHandlersProvider.cs index 60e73b3..358eb68 100644 --- a/Telegrator.Hosting/Providers/HostHandlersProvider.cs +++ b/Telegrator.Hosting/Providers/HostHandlersProvider.cs @@ -11,18 +11,25 @@ using Telegrator.Providers; namespace Telegrator.Hosting.Providers { + /// public class HostHandlersProvider : HandlersProvider { private readonly IServiceProvider Services; private readonly ILogger Logger; - public HostHandlersProvider(IHandlersCollection handlers, IOptions options, ITelegramBotInfo botInfo, IServiceProvider serviceProvider, ILogger logger) - : base(handlers, options.Value, botInfo) + /// + public HostHandlersProvider( + IHandlersCollection handlers, + IOptions options, + ITelegramBotInfo botInfo, + IServiceProvider serviceProvider, + ILogger logger) : base(handlers, options.Value, botInfo) { Services = serviceProvider; Logger = logger; } + /// public override IEnumerable GetHandlers(IUpdateRouter updateRouter, ITelegramBotClient client, Update update, CancellationToken cancellationToken = default) { IEnumerable handlers = base.GetHandlers(updateRouter, client, update, cancellationToken).ToArray(); @@ -30,6 +37,7 @@ namespace Telegrator.Hosting.Providers return handlers; } + /// public override UpdateHandlerBase GetHandlerInstance(HandlerDescriptor descriptor, CancellationToken cancellationToken = default) { cancellationToken.ThrowIfCancellationRequested(); diff --git a/Telegrator.Hosting/TelegramBotHost.cs b/Telegrator.Hosting/TelegramBotHost.cs index aadc395..079ce47 100644 --- a/Telegrator.Hosting/TelegramBotHost.cs +++ b/Telegrator.Hosting/TelegramBotHost.cs @@ -10,6 +10,9 @@ using Telegrator.MadiatorCore.Descriptors; namespace Telegrator.Hosting { + /// + /// Represents a hosted telegram bot + /// public class TelegramBotHost : ITelegramBotHost { private readonly IHost _innerHost; @@ -32,7 +35,8 @@ namespace Telegrator.Hosting /// /// Initializes a new instance of the class. /// - /// The service provider. + /// The service provider. + /// internal TelegramBotHost(HostApplicationBuilder hostApplicationBuilder, HostHandlersCollection handlers) { RegisterHostServices(hostApplicationBuilder, handlers); @@ -44,22 +48,52 @@ namespace Telegrator.Hosting LogHandlers(handlers); } + /// + /// Creates new with default configuration, services and long-polling update receiving scheme + /// + /// public static TelegramBotHostBuilder CreateBuilder() { - TelegramBotHostBuilder builder = new TelegramBotHostBuilder(null); + HostApplicationBuilder innerBuilder = new HostApplicationBuilder(settings: null); + TelegramBotHostBuilder builder = new TelegramBotHostBuilder(innerBuilder, null); builder.Services.AddTelegramBotHostDefaults(); builder.Services.AddTelegramReceiver(); return builder; } + /// + /// Creates new with default services and long-polling update receiving scheme + /// + /// public static TelegramBotHostBuilder CreateBuilder(TelegramBotHostBuilderSettings? settings) { - TelegramBotHostBuilder builder = new TelegramBotHostBuilder(settings); + HostApplicationBuilder innerBuilder = new HostApplicationBuilder(settings?.ToApplicationBuilderSettings()); + TelegramBotHostBuilder builder = new TelegramBotHostBuilder(innerBuilder, settings); builder.Services.AddTelegramBotHostDefaults(); builder.Services.AddTelegramReceiver(); return builder; } + /// + /// Creates new EMPTY WITHOUT any services or update receiving schemes + /// + /// + public static TelegramBotHostBuilder CreateEmptyBuilder() + { + HostApplicationBuilder innerBuilder = Host.CreateEmptyApplicationBuilder(null); + return new TelegramBotHostBuilder(innerBuilder, null); + } + + /// + /// Creates new EMPTY WITHOUT any services or update receiving schemes + /// + /// + public static TelegramBotHostBuilder CreateEmptyBuilder(TelegramBotHostBuilderSettings? settings) + { + HostApplicationBuilder innerBuilder = Host.CreateEmptyApplicationBuilder(null); + return new TelegramBotHostBuilder(innerBuilder, settings); + } + /// public async Task StartAsync(CancellationToken cancellationToken = default) { diff --git a/Telegrator.Hosting/TelegramBotHostBuilder.cs b/Telegrator.Hosting/TelegramBotHostBuilder.cs index 9d160bd..2edb5a0 100644 --- a/Telegrator.Hosting/TelegramBotHostBuilder.cs +++ b/Telegrator.Hosting/TelegramBotHostBuilder.cs @@ -14,6 +14,9 @@ using Telegrator.MadiatorCore; #pragma warning disable IDE0001 namespace Telegrator.Hosting { + /// + /// Represents a hosted telegram bots and services builder that helps manage configuration, logging, lifetime, and more. + /// public class TelegramBotHostBuilder : ITelegramBotHostBuilder { private readonly HostApplicationBuilder _innerBuilder; @@ -38,10 +41,12 @@ namespace Telegrator.Hosting /// /// Initializes a new instance of the class. /// - internal TelegramBotHostBuilder(TelegramBotHostBuilderSettings? settings = null) + /// + /// + internal TelegramBotHostBuilder(HostApplicationBuilder hostApplicationBuilder, TelegramBotHostBuilderSettings? settings = null) { + _innerBuilder = hostApplicationBuilder; _settings = settings ?? new TelegramBotHostBuilderSettings(); - _innerBuilder = new HostApplicationBuilder(settings?.ToApplicationBuilderSettings()); _handlers = new HostHandlersCollection(Services, _settings); Services.Configure(Configuration.GetSection(nameof(TelegramBotOptions))); @@ -55,7 +60,7 @@ namespace Telegrator.Hosting /// public TelegramBotHost Build() { - foreach (var preBuildRoutine in _handlers.PreBuilderRoutines) + foreach (PreBuildingRoutine preBuildRoutine in _handlers.PreBuilderRoutines) { try { diff --git a/Telegrator.Hosting/TelegramBotHostBuilderSettings.cs b/Telegrator.Hosting/TelegramBotHostBuilderSettings.cs index 74b3d0f..3e7d1bf 100644 --- a/Telegrator.Hosting/TelegramBotHostBuilderSettings.cs +++ b/Telegrator.Hosting/TelegramBotHostBuilderSettings.cs @@ -5,7 +5,7 @@ using Telegrator.Configuration; namespace Telegrator.Hosting { /// - /// + /// Settings os hosted Telegram bot /// public class TelegramBotHostBuilderSettings() : IHandlersCollectingOptions { diff --git a/Telegrator.Hosting/Telegrator.Hosting.csproj b/Telegrator.Hosting/Telegrator.Hosting.csproj index 5ce8471..2d4ad62 100644 --- a/Telegrator.Hosting/Telegrator.Hosting.csproj +++ b/Telegrator.Hosting/Telegrator.Hosting.csproj @@ -15,6 +15,7 @@ True True LICENSE + 1.0.1 diff --git a/Telegrator.Hosting/TypesExtensions.cs b/Telegrator.Hosting/TypesExtensions.cs index 7db6fc6..3c35640 100644 --- a/Telegrator.Hosting/TypesExtensions.cs +++ b/Telegrator.Hosting/TypesExtensions.cs @@ -2,9 +2,11 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; +using System.Diagnostics.CodeAnalysis; +using System.Reflection; +using System.Runtime.CompilerServices; using Telegram.Bot; using Telegram.Bot.Types; -using Telegrator; using Telegrator.Configuration; using Telegrator.Hosting.Components; using Telegrator.Hosting.Configuration; @@ -14,14 +16,31 @@ using Telegrator.MadiatorCore; namespace Telegrator.Hosting { + /// + /// Contains extensions for + /// Provides method to configure + /// public static class ServicesCollectionExtensions { + /// + /// Registers a configuration instance that strongly-typed will bind against using . + /// + /// + /// + /// + /// + /// public static IServiceCollection Configure(this IServiceCollection services, IConfiguration configuration, ConfigureOptionsProxy optionsProxy) where TOptions : class { optionsProxy.Configure(services, configuration); return services; } + /// + /// Registers default services + /// + /// + /// public static IServiceCollection AddTelegramBotHostDefaults(this IServiceCollection services) { services.AddLogging(builder => builder.AddConsole()); @@ -34,6 +53,11 @@ namespace Telegrator.Hosting return services; } + /// + /// Registers service with to receive updates using long polling + /// + /// + /// public static IServiceCollection AddTelegramReceiver(this IServiceCollection services) { services.AddHttpClient("tgreceiver").RemoveAllLoggers().AddTypedClient(TypedTelegramBotClientFactory); @@ -41,12 +65,26 @@ namespace Telegrator.Hosting return services; } + /// + /// factory method + /// + /// + /// + /// private static ITelegramBotClient TypedTelegramBotClientFactory(HttpClient httpClient, IServiceProvider provider) => new TelegramBotClient(provider.GetRequiredService>().Value, httpClient); } + /// + /// Provides useful methods to adjust + /// public static class TelegramBotHostExtensions { + /// + /// Configures bots available commands depending on what handlers was registered + /// + /// + /// public static ITelegramBotHost SetBotCommands(this ITelegramBotHost botHost) { ITelegramBotClient client = botHost.Services.GetRequiredService(); @@ -55,4 +93,26 @@ namespace Telegrator.Hosting return botHost; } } + + /// + /// Provides extension methods for reflection and type inspection. + /// + public static class ReflectionExtensions + { + /// + /// Checks if a type implements the interface. + /// + /// The type to check. + /// + /// True if the type implements IPreBuildingRoutine; otherwise, false. + public static bool IsPreBuildingRoutine(this Type handlerType, [NotNullWhen(true)] out MethodInfo? routineMethod) + { + routineMethod = null; + if (handlerType.GetInterface(nameof(IPreBuildingRoutine)) == null) + return false; + + routineMethod = handlerType.GetMethod(nameof(IPreBuildingRoutine.PreBuildingRoutine), BindingFlags.Static | BindingFlags.Public); + return routineMethod != null; + } + } } diff --git a/Telegrator/Annotations/CallbackQueryAttributes.cs b/Telegrator/Annotations/CallbackQueryAttributes.cs new file mode 100644 index 0000000..fa14245 --- /dev/null +++ b/Telegrator/Annotations/CallbackQueryAttributes.cs @@ -0,0 +1,44 @@ +using Telegram.Bot.Types; +using Telegram.Bot.Types.Enums; +using Telegrator.Attributes; +using Telegrator.Filters; +using Telegrator.Filters.Components; + +namespace Telegrator.Annotations +{ + /// + /// Abstract base attribute for filtering callback-based updates. + /// Supports various message types including regular messages, edited messages, channel posts, and business messages. + /// + /// The filters to apply to messages + public abstract class CallbackQueryAttribute(params IFilter[] filters) : UpdateFilterAttribute(filters) + { + /// + /// Gets the allowed update types that this filter can process. + /// + public override UpdateType[] AllowedTypes => [UpdateType.CallbackQuery]; + + /// + /// Extracts the message from various types of updates. + /// + /// The Telegram update + /// The message from the update, or null if not present + public override CallbackQuery? GetFilterringTarget(Update update) + => update.CallbackQuery; + } + + /// + /// Attribute for filtering 's data + /// + /// + public class CallbackDataAttribute(string data) + : CallbackQueryAttribute(new CallbackDataFilter(data)) + { } + + /// + /// Attribute to check if belongs to a specific message by its ID + /// + public class CallbackInlineIdAttribute(string inlineMessageId) + : CallbackQueryAttribute(new CallbackInlineIdFilter(inlineMessageId)) + { } +} diff --git a/Telegrator/Annotations/CommandAlliasAttribute.cs b/Telegrator/Annotations/CommandAlliasAttribute.cs index bf45781..98db005 100644 --- a/Telegrator/Annotations/CommandAlliasAttribute.cs +++ b/Telegrator/Annotations/CommandAlliasAttribute.cs @@ -47,7 +47,7 @@ namespace Telegrator.Annotations /// /// The command aliases to match against. public CommandAlliasAttribute(params string[] alliases) - : base(new CommandAlliasFilter(alliases)) => Alliases = alliases; + : base(new CommandAlliasFilter(alliases)) => Alliases = alliases.Select(c => c.TrimStart('/')).ToArray(); /// /// Gets the filtering target (Message) from the update. diff --git a/Telegrator/Annotations/MessageRepliedAttributes.cs b/Telegrator/Annotations/MessageRepliedAttributes.cs index cfd5ba4..6d0a075 100644 --- a/Telegrator/Annotations/MessageRepliedAttributes.cs +++ b/Telegrator/Annotations/MessageRepliedAttributes.cs @@ -10,9 +10,18 @@ namespace Telegrator.Annotations { } /// - /// Attribute for filtering messages in reply chain. + /// Attribute for checking message's reply chain. /// - public class MessageRepliedAttribute(int replyDepth = 1) - : MessageFilterAttribute(new MessageRepliedFilter(replyDepth)) + public class HasReplyAttribute(int replyDepth = 1) + : MessageFilterAttribute(new MessageHasReplyFilter(replyDepth)) + { } + + /// + /// Helper filter class for filters that operate on replied messages. + /// Provides functionality to traverse reply chains and access replied message content. + /// + /// + public class FromReplyChainAttribute(int replyDepth = 1) + : MessageFilterAttribute(new FromReplyChainFilter(replyDepth)) { } } diff --git a/Telegrator/Annotations/WelcomeAttribute.cs b/Telegrator/Annotations/WelcomeAttribute.cs index 8118498..555b87f 100644 --- a/Telegrator/Annotations/WelcomeAttribute.cs +++ b/Telegrator/Annotations/WelcomeAttribute.cs @@ -1,4 +1,5 @@ -using Telegram.Bot.Types.Enums; +using Telegram.Bot.Types; +using Telegram.Bot.Types.Enums; using Telegrator.Filters; namespace Telegrator.Annotations @@ -12,7 +13,8 @@ namespace Telegrator.Annotations /// /// Creates new instance of /// - public WelcomeAttribute() : base(new MessageChatTypeFilter(ChatType.Private), new CommandAlliasFilter("start")) + /// + public WelcomeAttribute(bool onlyFirst = false) : base(new MessageChatTypeFilter(ChatType.Private), new CommandAlliasFilter("start"), Filter.If(ctx => !onlyFirst || ctx.Input.Id == 0)) { } } } diff --git a/Telegrator/Attributes/Components/UpdateHandlerAttributeBase.cs b/Telegrator/Attributes/Components/UpdateHandlerAttributeBase.cs index fb1ceb1..1e85896 100644 --- a/Telegrator/Attributes/Components/UpdateHandlerAttributeBase.cs +++ b/Telegrator/Attributes/Components/UpdateHandlerAttributeBase.cs @@ -26,9 +26,9 @@ namespace Telegrator.Attributes.Components public UpdateType Type { get; private set; } /// - /// Gets or sets concurrency of this in same pool + /// Gets or sets importance of this in same pool /// - public int Concurrency { get; set; } + public int Importance { get; set; } /// /// Gets or sets priority of this in same type handlers pool @@ -40,11 +40,11 @@ namespace Telegrator.Attributes.Components /// /// /// - /// + /// /// /// /// - protected internal UpdateHandlerAttributeBase(Type[] expectingHandlerType, UpdateType updateType, int concurrency = 0) + protected internal UpdateHandlerAttributeBase(Type[] expectingHandlerType, UpdateType updateType, int importance = 0) { if (expectingHandlerType == null) throw new ArgumentNullException(nameof(expectingHandlerType)); @@ -57,11 +57,11 @@ namespace Telegrator.Attributes.Components ExpectingHandlerType = expectingHandlerType; Type = updateType; - Concurrency = concurrency; + Importance = importance; } /// - /// Gets an of this from and + /// Gets an of this from and /// /// public DescriptorIndexer GetIndexer() diff --git a/Telegrator/Attributes/UpdateHandlerAttribute.cs b/Telegrator/Attributes/UpdateHandlerAttribute.cs index 1fa748a..0a8beb4 100644 --- a/Telegrator/Attributes/UpdateHandlerAttribute.cs +++ b/Telegrator/Attributes/UpdateHandlerAttribute.cs @@ -6,13 +6,13 @@ namespace Telegrator.Attributes { /// /// Abstract base attribute for marking update handler classes. - /// Provides a type-safe way to associate handler types with specific update types and concurrency settings. + /// Provides a type-safe way to associate handler types with specific update types and importance settings. /// /// The type of the update handler that this attribute is applied to. /// The type of update that this handler can process. - /// The concurrency level for this handler (default: 0 for unlimited). - public abstract class UpdateHandlerAttribute(UpdateType updateType, int concurrency = 0) - : UpdateHandlerAttributeBase([typeof(T)], updateType, concurrency) where T : UpdateHandlerBase + /// The importance level for this handler (default: 0 for unlimited). + public abstract class UpdateHandlerAttribute(UpdateType updateType, int importance = 0) + : UpdateHandlerAttributeBase([typeof(T)], updateType, importance) where T : UpdateHandlerBase { } } diff --git a/Telegrator/Filters/CallbackQueryFilters.cs b/Telegrator/Filters/CallbackQueryFilters.cs new file mode 100644 index 0000000..e49ec6c --- /dev/null +++ b/Telegrator/Filters/CallbackQueryFilters.cs @@ -0,0 +1,51 @@ +using Telegram.Bot.Types; +using Telegrator.Filters.Components; + +namespace Telegrator.Filters +{ + /// + /// Filter thet checks 's data + /// + public class CallbackDataFilter : Filter + { + private readonly string _data; + + /// + /// Initialize new instance of + /// + /// + public CallbackDataFilter(string data) + { + _data = data; + } + + /// + public override bool CanPass(FilterExecutionContext context) + { + return context.Input.Data == _data; + } + } + + /// + /// Filter that checks if belongs to a specific message + /// + public class CallbackInlineIdFilter : Filter + { + private readonly string _inlineMessageId; + + /// + /// Initialize new instance of + /// + /// + public CallbackInlineIdFilter(string inlineMessageId) + { + _inlineMessageId = inlineMessageId; + } + + /// + public override bool CanPass(FilterExecutionContext context) + { + return context.Input.InlineMessageId == _inlineMessageId; + } + } +} diff --git a/Telegrator/Filters/Components/AnonymousCompiledFilter.cs b/Telegrator/Filters/Components/AnonymousCompiledFilter.cs index 8267613..443c0e5 100644 --- a/Telegrator/Filters/Components/AnonymousCompiledFilter.cs +++ b/Telegrator/Filters/Components/AnonymousCompiledFilter.cs @@ -45,7 +45,12 @@ namespace Telegrator.Filters.Components foreach (IFilter filter in filters) { if (!filter.CanPass(context)) + { + if (filter is not AnonymousCompiledFilter && filter is not AnonymousTypeFilter) + LeveledDebug.FilterWriteLine("(E) {0} filter of {1} didnt pass!", filter.GetType().Name, context.Data["handler_name"]); + return false; + } context.CompletedFilters.Add(filter); } diff --git a/Telegrator/Filters/Components/AnonymousTypeFilter.cs b/Telegrator/Filters/Components/AnonymousTypeFilter.cs index 3ae4993..91442f3 100644 --- a/Telegrator/Filters/Components/AnonymousTypeFilter.cs +++ b/Telegrator/Filters/Components/AnonymousTypeFilter.cs @@ -47,7 +47,12 @@ namespace Telegrator.Filters.Components { FilterExecutionContext context = updateContext.CreateChild((T)filterringTarget); if (!filter.CanPass(context)) + { + if (filter is not AnonymousCompiledFilter && filter is not AnonymousTypeFilter) + LeveledDebug.FilterWriteLine("(E) {0} filter of {1} didnt pass!", filter.GetType().Name, context.Data["handler_name"]); + return false; + } context.CompletedFilters.Add(filter); return true; diff --git a/Telegrator/Filters/Components/CompiledFilter.cs b/Telegrator/Filters/Components/CompiledFilter.cs index bdb4fc8..3eb2666 100644 --- a/Telegrator/Filters/Components/CompiledFilter.cs +++ b/Telegrator/Filters/Components/CompiledFilter.cs @@ -37,7 +37,12 @@ foreach (IFilter filter in Filters) { if (!filter.CanPass(context)) + { + if (filter is not AnonymousCompiledFilter && filter is not AnonymousTypeFilter) + LeveledDebug.FilterWriteLine("(E) {0} filter of {1} didnt pass!", filter.GetType().Name, context.Data["handler_name"]); + return false; + } context.CompletedFilters.Add(filter); } diff --git a/Telegrator/Filters/Filter.cs b/Telegrator/Filters/Filter.cs index f2fdd86..99b486a 100644 --- a/Telegrator/Filters/Filter.cs +++ b/Telegrator/Filters/Filter.cs @@ -1,6 +1,4 @@ -using System.Reflection; -using Telegrator; -using Telegrator.Filters.Components; +using Telegrator.Filters.Components; namespace Telegrator.Filters { diff --git a/Telegrator/Filters/MentionedFilter.cs b/Telegrator/Filters/MentionedFilter.cs index d59233b..c9dd56e 100644 --- a/Telegrator/Filters/MentionedFilter.cs +++ b/Telegrator/Filters/MentionedFilter.cs @@ -28,7 +28,7 @@ namespace Telegrator.Filters /// The username to check for in the mention. public MentionedFilter(string mention) { - Mention = mention; + Mention = mention.TrimStart('@'); } /// diff --git a/Telegrator/Filters/MessageFilters.cs b/Telegrator/Filters/MessageFilters.cs index 35dffee..90616c6 100644 --- a/Telegrator/Filters/MessageFilters.cs +++ b/Telegrator/Filters/MessageFilters.cs @@ -18,7 +18,7 @@ namespace Telegrator.Filters /// public override bool CanPass(FilterExecutionContext context) { - MessageRepliedFilter? repliedFilter = context.CompletedFilters.Get().SingleOrDefault(); + FromReplyChainFilter? repliedFilter = context.CompletedFilters.Get().SingleOrDefault(); Target = repliedFilter?.Reply ?? context.Input; return CanPassNext(context); } diff --git a/Telegrator/Filters/MessageRepliedFilters.cs b/Telegrator/Filters/MessageRepliedFilters.cs index 4a84334..a1752a0 100644 --- a/Telegrator/Filters/MessageRepliedFilters.cs +++ b/Telegrator/Filters/MessageRepliedFilters.cs @@ -4,11 +4,51 @@ using Telegrator.Filters.Components; namespace Telegrator.Filters { /// - /// Abstract base class for filters that operate on replied messages. - /// Provides functionality to traverse reply chains and access replied message content. + /// Filter that checks if message has appropriate reply chain. + /// DOES NOT SHiFT MESSAGE FILTERS TARGET /// /// The depth of reply chain to traverse (default: 1). - public class MessageRepliedFilter(int replyDepth = 1) : Filter + public class MessageHasReplyFilter(int replyDepth = 1) : Filter + { + /// + /// Gets the replied message at the specified depth in the reply chain. + /// + public Message Reply { get; private set; } = null!; + + /// + /// Gets the depth of reply chain traversal. + /// + public int ReplyDepth { get; private set; } = replyDepth; + + /// + /// Determines if the message can pass through the filter by first validating + /// the reply chain and then applying specific filter logic. + /// + /// The filter execution context containing the message. + /// True if the message passes both reply validation and specific filter criteria; otherwise, false. + public override bool CanPass(FilterExecutionContext context) + { + Message reply = context.Input; + for (int i = ReplyDepth; i > 0; i--) + { + if (reply.ReplyToMessage is not { Id: > 0 } replyMessage) + return false; + + reply = replyMessage; + } + + Reply = reply; + return true; + } + } + + /// + /// Helper filter class for filters that operate on replied messages. + /// Provides functionality to traverse reply chains and access replied message content + /// and shifts any next message filter to filter the content of found reply. + /// + /// + public class FromReplyChainFilter(int replyDepth = 1) : Filter { /// /// Gets the replied message at the specified depth in the reply chain. @@ -44,7 +84,7 @@ namespace Telegrator.Filters /// /// Filter that checks if the replied message was sent by the bot itself. - /// ( ! ): REQUIRES before + /// ( ! ): REQUIRES before /// public class MeRepliedFilter : Filter { @@ -55,7 +95,7 @@ namespace Telegrator.Filters /// True if the replied message was sent by the bot; otherwise, false. public override bool CanPass(FilterExecutionContext context) { - MessageRepliedFilter repliedFilter = context.CompletedFilters.Get(0); + MessageHasReplyFilter repliedFilter = context.CompletedFilters.Get(0); return context.BotInfo.User == repliedFilter.Reply.From; } } diff --git a/Telegrator/Filters/MessageTextFilters.cs b/Telegrator/Filters/MessageTextFilters.cs index 3f396b6..2c7f6f3 100644 --- a/Telegrator/Filters/MessageTextFilters.cs +++ b/Telegrator/Filters/MessageTextFilters.cs @@ -32,7 +32,7 @@ namespace Telegrator.Filters if (!base.CanPass(context)) return false; - Message = context.Update.Message!; + Message = context.Input!; if (Message is not { Id: > 0 }) return false; diff --git a/Telegrator/Handlers/AnyUpdateHandler.cs b/Telegrator/Handlers/AnyUpdateHandler.cs index 39b5328..199e6bc 100644 --- a/Telegrator/Handlers/AnyUpdateHandler.cs +++ b/Telegrator/Handlers/AnyUpdateHandler.cs @@ -10,8 +10,8 @@ namespace Telegrator.Handlers /// Attribute that marks a handler to process any type of update. /// This handler will be triggered for all incoming updates regardless of their type. /// - /// The maximum number of concurrent executions allowed (default: -1 for unlimited). - public class AnyUpdateHandlerAttribute(int concurrency = -1) : UpdateHandlerAttribute(UpdateType.Unknown, concurrency) + /// + public class AnyUpdateHandlerAttribute(int importance = -1) : UpdateHandlerAttribute(UpdateType.Unknown, importance) { /// /// Always returns true, allowing any update to pass through this filter. diff --git a/Telegrator/Handlers/Building/Components/HandlerBuilderBase.cs b/Telegrator/Handlers/Building/Components/HandlerBuilderBase.cs index a592047..eb2d360 100644 --- a/Telegrator/Handlers/Building/Components/HandlerBuilderBase.cs +++ b/Telegrator/Handlers/Building/Components/HandlerBuilderBase.cs @@ -95,7 +95,7 @@ namespace Telegrator.Handlers.Building.Components /// The builder instance. public void SetConcurreny(int concurrency) { - Indexer = Indexer.UpdateConcurrency(concurrency); + Indexer = Indexer.UpdateImportance(concurrency); } /// diff --git a/Telegrator/Handlers/CallbackQueryHandler.cs b/Telegrator/Handlers/CallbackQueryHandler.cs index 5a415e4..48dce69 100644 --- a/Telegrator/Handlers/CallbackQueryHandler.cs +++ b/Telegrator/Handlers/CallbackQueryHandler.cs @@ -10,15 +10,15 @@ namespace Telegrator.Handlers /// Attribute that marks a handler to process callback query updates. /// This handler will be triggered when users interact with inline keyboards or other callback mechanisms. /// - /// The maximum number of concurrent executions allowed (default: 0 for unlimited). - public sealed class CallbackQueryHandlerAttribute(int concurrency = 0) : UpdateHandlerAttribute(UpdateType.CallbackQuery, concurrency) + /// + public sealed class CallbackQueryHandlerAttribute(int importance = 0) : UpdateHandlerAttribute(UpdateType.CallbackQuery, importance) { /// /// Always returns true, allowing any callback query update to pass through this filter. /// /// The filter execution context (unused). /// Always returns true to allow any callback query update. - public override bool CanPass(FilterExecutionContext context) => true; + public override bool CanPass(FilterExecutionContext context) => context.Input is { CallbackQuery: { } }; } /// diff --git a/Telegrator/Handlers/CommandHandler.cs b/Telegrator/Handlers/CommandHandler.cs index d70ca8d..36783f6 100644 --- a/Telegrator/Handlers/CommandHandler.cs +++ b/Telegrator/Handlers/CommandHandler.cs @@ -9,8 +9,7 @@ namespace Telegrator.Handlers /// Attribute that marks a handler to process command messages. /// This handler will be triggered when users send bot commands (messages starting with '/'). /// - /// The maximum number of concurrent executions allowed (default: 1). - public class CommandHandlerAttribute(int concurrency = 1) : UpdateHandlerAttribute(UpdateType.Message, concurrency) + public class CommandHandlerAttribute(int importance = 1) : UpdateHandlerAttribute(UpdateType.Message, importance) { /// /// Gets the command that was extracted from the message (without the '/' prefix and bot username). diff --git a/Telegrator/Handlers/MessageHandler.cs b/Telegrator/Handlers/MessageHandler.cs index b651db9..b5ccef8 100644 --- a/Telegrator/Handlers/MessageHandler.cs +++ b/Telegrator/Handlers/MessageHandler.cs @@ -12,8 +12,7 @@ namespace Telegrator.Handlers /// Attribute that marks a handler to process message updates. /// This handler will be triggered when users send messages in chats. /// - /// The maximum number of concurrent executions allowed (default: 0 for unlimited). - public class MessageHandlerAttribute(int concurrency = 0) : UpdateHandlerAttribute(UpdateType.Message, concurrency) + public class MessageHandlerAttribute(int importance = 0) : UpdateHandlerAttribute(UpdateType.Message, importance) { /// /// Checks if the update contains a valid message. diff --git a/Telegrator/LeveledDebug.cs b/Telegrator/LeveledDebug.cs new file mode 100644 index 0000000..0d598b4 --- /dev/null +++ b/Telegrator/LeveledDebug.cs @@ -0,0 +1,121 @@ +using System.Diagnostics; + +namespace Telegrator +{ + /// + /// Telegrator's Debug logger helper + /// + public static class LeveledDebug + { + /// + /// Writes debug message if Indent level has Router flag + /// + /// + public static void RouterWriteLine(string message) + { + if (Debug.IndentLevel.HasFlag(DebugLevel.Router)) + Debug.WriteLine(message); + } + + /// + /// Writes debug message if Indent level has Router flag + /// + /// + /// + public static void RouterWriteLine(string message, params object[] args) + { + if (Debug.IndentLevel.HasFlag(DebugLevel.Router)) + Debug.WriteLine(message, args); + } + + /// + /// Writes debug message if Indent level has Providers flag + /// + /// + public static void ProviderWriteLine(string message) + { + if (Debug.IndentLevel.HasFlag(DebugLevel.Providers)) + Debug.WriteLine(message); + } + + /// + /// Writes debug message if Indent level has Providers flag + /// + /// + /// + public static void ProviderWriteLine(string message, params object[] args) + { + if (Debug.IndentLevel.HasFlag(DebugLevel.Providers)) + Debug.WriteLine(message, args); + } + + /// + /// Writes debug message if Indent level has Filters flag + /// + /// + public static void FilterWriteLine(string message) + { + if (Debug.IndentLevel.HasFlag(DebugLevel.Filters)) + Debug.WriteLine(message); + } + + /// + /// Writes debug message if Indent level has Filters flag + /// + /// + /// + public static void FilterWriteLine(string message, params object[] args) + { + if (Debug.IndentLevel.HasFlag(DebugLevel.Filters)) + Debug.WriteLine(message, args); + } + + /// + /// Writes debug message if Indent level has Pool flag + /// + /// + public static void PoolWriteLine(string message) + { + if (Debug.IndentLevel.HasFlag(DebugLevel.HandlersPool)) + Debug.WriteLine(message); + } + + /// + /// Writes debug message if Indent level has Pool flag + /// + /// + /// + public static void PoolWriteLine(string message, params object[] args) + { + if (Debug.IndentLevel.HasFlag(DebugLevel.HandlersPool)) + Debug.WriteLine(message, args); + } + } + + /// + /// Levels of debug writing + /// + [Flags] + public enum DebugLevel + { + /// + /// Write debug messages from filters execution + /// + Filters = 0x1, + + /// + /// Write debug messages from handlers providers execution + /// + Providers = 0x2, + + /// + /// Write debug messages from update router's execution + /// + Router = 0x4, + + /// + /// Write debug messages from handlers pool execution + /// + HandlersPool = 0x8 + } +} diff --git a/Telegrator/MadiatorCore/Descriptors/DescribedHandlerInfo.cs b/Telegrator/MadiatorCore/Descriptors/DescribedHandlerInfo.cs index d98dcfc..8506fc3 100644 --- a/Telegrator/MadiatorCore/Descriptors/DescribedHandlerInfo.cs +++ b/Telegrator/MadiatorCore/Descriptors/DescribedHandlerInfo.cs @@ -3,7 +3,6 @@ using Telegram.Bot.Polling; using Telegram.Bot.Types; using Telegrator.Filters.Components; using Telegrator.Handlers.Components; -using Telegrator.MadiatorCore; namespace Telegrator.MadiatorCore.Descriptors { @@ -111,5 +110,9 @@ namespace Telegrator.MadiatorCore.Descriptors .ConfigureAwait(false); } } + + /// + public override string ToString() + => DisplayString ?? HandlerInstance.GetType().Name; } } diff --git a/Telegrator/MadiatorCore/Descriptors/DescriptorFiltersSet.cs b/Telegrator/MadiatorCore/Descriptors/DescriptorFiltersSet.cs index f903834..2d58018 100644 --- a/Telegrator/MadiatorCore/Descriptors/DescriptorFiltersSet.cs +++ b/Telegrator/MadiatorCore/Descriptors/DescriptorFiltersSet.cs @@ -46,16 +46,24 @@ namespace Telegrator.MadiatorCore.Descriptors if (UpdateValidator != null) { if (!UpdateValidator.CanPass(filterContext)) + { + LeveledDebug.FilterWriteLine("(E) UpdateValidator filter of {0} for Update ({2}) didnt pass!", filterContext.Data["handler_name"]); return false; + } + //LeveledDebug.FilterWriteLine("UpdateValidator of {0} for Update ({2}) passed", filterContext.Data["handler_name"]); filterContext.CompletedFilters.Add(UpdateValidator); } if (StateKeeperValidator != null) { if (!StateKeeperValidator.CanPass(filterContext)) + { + LeveledDebug.FilterWriteLine("(E) StateKeeperValidator filter of {0} for Update ({2}) didnt pass!", filterContext.Data["handler_name"]); return false; + } + //LeveledDebug.FilterWriteLine("StateKeeperValidator of {0} for Update ({2}) passed", filterContext.Data["handler_name"]); filterContext.CompletedFilters.Add(StateKeeperValidator); } @@ -64,8 +72,14 @@ namespace Telegrator.MadiatorCore.Descriptors foreach (IFilter filter in UpdateFilters) { if (!filter.CanPass(filterContext)) - return false; + { + if (filter is not AnonymousCompiledFilter && filter is not AnonymousTypeFilter) + LeveledDebug.FilterWriteLine("(E) {0} filter of {1} didnt pass!", filter.GetType().Name, filterContext.Data["handler_name"]); + return false; + } + + //LeveledDebug.FilterWriteLine("{0} filter of {1} for Update ({2}) passed", filter.GetType().Name, filterContext.Data["handler_name"]); filterContext.CompletedFilters.Add(filter); } } diff --git a/Telegrator/MadiatorCore/Descriptors/DescriptorIndexer.cs b/Telegrator/MadiatorCore/Descriptors/DescriptorIndexer.cs index 5ac4762..2573819 100644 --- a/Telegrator/MadiatorCore/Descriptors/DescriptorIndexer.cs +++ b/Telegrator/MadiatorCore/Descriptors/DescriptorIndexer.cs @@ -3,9 +3,9 @@ namespace Telegrator.MadiatorCore.Descriptors { /// - /// Represents an indexer for handler descriptors, containing concurrency and priority information. + /// Represents an indexer for handler descriptors, containing importance and priority information. /// - public readonly struct DescriptorIndexer(int routerIndex, int concurrency, int priority) : IComparable + public readonly struct DescriptorIndexer(int routerIndex, int importance, int priority) : IComparable { /// /// Index of this descriptor when it was added to router @@ -15,7 +15,7 @@ namespace Telegrator.MadiatorCore.Descriptors /// /// Of this handlert type /// - public readonly int Importance = concurrency; + public readonly int Importance = importance; /// /// The priority of the handler. @@ -28,7 +28,7 @@ namespace Telegrator.MadiatorCore.Descriptors /// /// The handler attribute. public DescriptorIndexer(int routerIndex, UpdateHandlerAttributeBase pollingHandler) - : this(routerIndex, pollingHandler.Concurrency, pollingHandler.Priority) { } + : this(routerIndex, pollingHandler.Importance, pollingHandler.Priority) { } /// /// Returns a new with updated priority. @@ -39,12 +39,12 @@ namespace Telegrator.MadiatorCore.Descriptors => new DescriptorIndexer(RouterIndex, Importance, priority); /// - /// Returns a new with updated concurrency. + /// Returns a new with updated importance. /// - /// The new concurrency value. + /// The new importance value. /// A new instance. - public DescriptorIndexer UpdateConcurrency(int concurrency) - => new DescriptorIndexer(RouterIndex, concurrency, Priority); + public DescriptorIndexer UpdateImportance(int importance) + => new DescriptorIndexer(RouterIndex, importance, Priority); /// /// Returns a new with updated RouterIndex. @@ -79,7 +79,7 @@ namespace Telegrator.MadiatorCore.Descriptors /// /// Returns a string representation of the indexer. /// - /// A string in the format (C:concurrency, P:priority). + /// A string in the format (C:importance, P:priority). public override string ToString() { return string.Format("(I:{0}, C:{1}, P:{2})", RouterIndex, Importance, Priority); diff --git a/Telegrator/MadiatorCore/Descriptors/HandlerDescriptor.cs b/Telegrator/MadiatorCore/Descriptors/HandlerDescriptor.cs index b0c984a..8de3142 100644 --- a/Telegrator/MadiatorCore/Descriptors/HandlerDescriptor.cs +++ b/Telegrator/MadiatorCore/Descriptors/HandlerDescriptor.cs @@ -139,6 +139,7 @@ namespace Telegrator.MadiatorCore.Descriptors UpdateType = handlerAttribute.Type; Indexer = handlerAttribute.GetIndexer(); Filters = new DescriptorFiltersSet(handlerAttribute, stateKeeperAttribute, filters); + DisplayString = HandlerInspector.GetDisplayName(handlerType); } /// @@ -402,5 +403,9 @@ namespace Telegrator.MadiatorCore.Descriptors ServiceKey = serviceKey ?? throw new ArgumentNullException(nameof(serviceKey)); InstanceFactory = instanceFactory ?? throw new ArgumentNullException(nameof(instanceFactory)); } + + /// + public override string ToString() + => DisplayString ?? HandlerType.Name; } } diff --git a/Telegrator/MadiatorCore/Descriptors/HandlerDescriptorList.cs b/Telegrator/MadiatorCore/Descriptors/HandlerDescriptorList.cs index 7bc110d..5c0303b 100644 --- a/Telegrator/MadiatorCore/Descriptors/HandlerDescriptorList.cs +++ b/Telegrator/MadiatorCore/Descriptors/HandlerDescriptorList.cs @@ -1,9 +1,7 @@ using System.Collections; using Telegram.Bot.Types; using Telegram.Bot.Types.Enums; -using Telegrator; using Telegrator.Configuration; -using Telegrator.MadiatorCore; namespace Telegrator.MadiatorCore.Descriptors { @@ -29,6 +27,11 @@ namespace Telegrator.MadiatorCore.Descriptors /// public UpdateType HandlingType => _handlingType; + /// + /// Gets count of registered handlers in list + /// + public int Count => _innerCollection.Count; + /// /// Gets or sets the at the specified index. /// diff --git a/Telegrator/MadiatorCore/Descriptors/HandlerInspector.cs b/Telegrator/MadiatorCore/Descriptors/HandlerInspector.cs index bac3ea7..2205d48 100644 --- a/Telegrator/MadiatorCore/Descriptors/HandlerInspector.cs +++ b/Telegrator/MadiatorCore/Descriptors/HandlerInspector.cs @@ -1,4 +1,5 @@ -using System.Reflection; +using System.ComponentModel; +using System.Reflection; using Telegram.Bot.Types; using Telegram.Bot.Types.Enums; using Telegrator.Attributes.Components; @@ -11,6 +12,16 @@ namespace Telegrator.MadiatorCore.Descriptors /// public static class HandlerInspector { + /// + /// Gets handler's display name + /// + /// + /// + public static string? GetDisplayName(MemberInfo handlerType) + { + return handlerType.GetCustomAttribute()?.DisplayName; + } + /// /// Gets the handler attribute from the specified member info. /// diff --git a/Telegrator/Polling/UpdateRouter.cs b/Telegrator/Polling/UpdateRouter.cs index 43fc3b4..1cb768f 100644 --- a/Telegrator/Polling/UpdateRouter.cs +++ b/Telegrator/Polling/UpdateRouter.cs @@ -1,7 +1,9 @@ -using Telegram.Bot; +using System; +using System.Text; +using Telegram.Bot; using Telegram.Bot.Polling; using Telegram.Bot.Types; -using Telegrator.Polling; +using Telegram.Bot.Types.Enums; using Telegrator.Configuration; using Telegrator.Handlers.Components; using Telegrator.MadiatorCore; @@ -92,6 +94,7 @@ namespace Telegrator.Polling /// A task representing the asynchronous error handling operation. public virtual Task HandleErrorAsync(ITelegramBotClient botClient, Exception exception, HandleErrorSource source, CancellationToken cancellationToken) { + LeveledDebug.RouterWriteLine("Handling exception {0}", exception.GetType().Name); ExceptionHandler?.HandleException(botClient, exception, source, cancellationToken); return Task.CompletedTask; } @@ -105,21 +108,60 @@ namespace Telegrator.Polling /// A task representing the asynchronous update handling operation. public virtual Task HandleUpdateAsync(ITelegramBotClient botClient, Update update, CancellationToken cancellationToken) { + // Logging + 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) { - // Getting handlers in update awaiting pool - IEnumerable handlers = AwaitingProvider.GetHandlers(this, botClient, update, cancellationToken); - if (handlers.Any() && Options.ExclusiveAwaitingHandlerRouting) - return handlers; + try + { + // Getting handlers in update awaiting pool + IEnumerable handlers = AwaitingProvider.GetHandlers(this, botClient, update, cancellationToken); + if (handlers.Any() && Options.ExclusiveAwaitingHandlerRouting) + return handlers; - return handlers.Concat(HandlersProvider.GetHandlers(this, botClient, update, cancellationToken)); + return handlers.Concat(HandlersProvider.GetHandlers(this, botClient, update, cancellationToken)); + } + catch (OperationCanceledException) + { + _ = 0xBAD + 0xC0DE; + return []; + } + catch (Exception ex) + { + ExceptionHandler?.HandleException(botClient, ex, HandleErrorSource.PollingError, cancellationToken); + return []; + } + } + + private static void LogUpdate(Update update) + { + switch (update.Type) + { + case UpdateType.Message: + { + Message msg = update.Message ?? throw new NullReferenceException(); + StringBuilder sb = new StringBuilder("Update.Message"); + + if (msg.From != null) + sb.AppendFormat(" from {0} ({1})", msg.From.Username, msg.From.Id); + + if (msg.Text != null) + sb.AppendFormat("'{0}'", msg.Text); + + LeveledDebug.RouterWriteLine(sb.ToString()); + break; + } + } } } } diff --git a/Telegrator/Providers/HandlersProvider.cs b/Telegrator/Providers/HandlersProvider.cs index fe9d1da..bb3d615 100644 --- a/Telegrator/Providers/HandlersProvider.cs +++ b/Telegrator/Providers/HandlersProvider.cs @@ -1,4 +1,5 @@ using System.Collections.ObjectModel; +using System.Diagnostics; using System.Reflection; using Telegram.Bot; using Telegram.Bot.Types; @@ -79,16 +80,23 @@ namespace Telegrator.Providers /// 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)) { - if (!HandlersDictionary.TryGetValue(UpdateType.Unknown, out descriptors)) - return []; + LeveledDebug.ProviderWriteLine("No registered, providing Any"); + HandlersDictionary.TryGetValue(UpdateType.Unknown, out descriptors); } - if (descriptors == null || !descriptors.Any()) + if (descriptors == null || descriptors.Count == 0) + { + LeveledDebug.ProviderWriteLine("No handlers provided"); return []; + } - return DescribeDescriptors(descriptors, updateRouter, client, update, cancellationToken); + 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; } /// @@ -103,16 +111,24 @@ namespace Telegrator.Providers /// A collection of described handler information public virtual IEnumerable DescribeDescriptors(HandlerDescriptorList descriptors, IUpdateRouter updateRouter, ITelegramBotClient client, Update update, CancellationToken cancellationToken = default) { - foreach (HandlerDescriptor descriptor in descriptors.Reverse()) + try { - cancellationToken.ThrowIfCancellationRequested(); - DescribedHandlerInfo? describedHandler = DescribeHandler(descriptor, updateRouter, client, update, cancellationToken); - if (describedHandler == null) - continue; + 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; + yield return describedHandler; + if (Options.ExecuteOnlyFirstFoundHanlder) + break; + } + } + finally + { + LeveledDebug.ProviderWriteLine("Describing for Update ({0}) finished", update.Id); } } @@ -129,7 +145,12 @@ namespace Telegrator.Providers public virtual DescribedHandlerInfo? DescribeHandler(HandlerDescriptor descriptor, IUpdateRouter updateRouter, ITelegramBotClient client, Update update, CancellationToken cancellationToken = default) { cancellationToken.ThrowIfCancellationRequested(); - FilterExecutionContext filterContext = new FilterExecutionContext(BotInfo, update, update); + Dictionary data = new Dictionary() + { + { "handler_name", descriptor.ToString() } + }; + + FilterExecutionContext filterContext = new FilterExecutionContext(BotInfo, update, update, data, []); if (!descriptor.Filters.Validate(filterContext)) return null; diff --git a/Telegrator/Telegrator.csproj b/Telegrator/Telegrator.csproj index 3ef1eff..ffff2b1 100644 --- a/Telegrator/Telegrator.csproj +++ b/Telegrator/Telegrator.csproj @@ -17,6 +17,7 @@ True True LICENSE + 1.0.1 diff --git a/Telegrator/TypesExtensions.cs b/Telegrator/TypesExtensions.cs index 2e7abd7..95bcfa5 100644 --- a/Telegrator/TypesExtensions.cs +++ b/Telegrator/TypesExtensions.cs @@ -1,6 +1,7 @@ using System.Collections; using System.Collections.ObjectModel; using System.Reflection; +using System.Runtime.CompilerServices; using Telegram.Bot.Types; using Telegram.Bot.Types.Enums; using Telegram.Bot.Types.Payments; @@ -902,4 +903,28 @@ namespace Telegrator } } + /// + /// Contains extension method for number types + /// + public static class NumbersExtensions + { + /// + /// Check if int value has int flag using bit compare + /// + /// + /// + /// + public static bool HasFlag(this int value, int flag) + => (value & flag) == flag; + + /// + /// Check if int value has enum flag using bit compare + /// + /// + /// + /// + /// + public static bool HasFlag(this int value, T flag) where T : Enum + => value.HasFlag(Convert.ToInt32(flag)); + } }