* Renamed "concurrency" parameters in handlers attributes to more relevant and intuitive "importance" parameter

* Added debug logging helper that use default Debug tracer to trace filter, providers, routers and pool execution
* Added debug logging for failing during handlers resolving, filters
* Added new "CreateEmptyBuilder" methods for TelegramBotHost class
* Addede ability to name handlers using "DisplayNameAttribute". This name is writed to descriptor's "DisplayString" property
* Fixed missing summaries inside Hosting library
This commit is contained in:
2025-07-26 00:01:46 +04:00
parent bcdce52a37
commit cec7c88b6a
42 changed files with 647 additions and 102 deletions
@@ -6,11 +6,12 @@ using Telegrator.Polling;
namespace Telegrator.Hosting.Polling
{
public class HostUpdateHandlersPool(IOptions<TelegramBotOptions> options, ILogger<HostUpdateHandlersPool> logger)
: UpdateHandlersPool(options.Value, options.Value.GlobalCancellationToken)
/// <inheritdoc/>
public class HostUpdateHandlersPool(IOptions<TelegramBotOptions> options, ILogger<HostUpdateHandlersPool> logger) : UpdateHandlersPool(options.Value, options.Value.GlobalCancellationToken)
{
private readonly ILogger<HostUpdateHandlersPool> _logger = logger;
/// <inheritdoc/>
protected override async Task ExecuteHandlerWrapper(DescribedHandlerInfo enqueuedHandler)
{
_logger.LogInformation("Handler \"{0}\" has entered execution pool", enqueuedHandler.DisplayString);
+18 -3
View File
@@ -10,23 +10,38 @@ using Telegrator.Polling;
namespace Telegrator.Hosting.Polling
{
/// <inheritdoc/>
public class HostUpdateRouter : UpdateRouter
{
/// <summary>
/// <see cref="ILogger"/> of this router
/// </summary>
protected readonly ILogger<HostUpdateRouter> Logger;
public HostUpdateRouter(IHandlersProvider handlersProvider, IAwaitingProvider awaitingProvider, IOptions<TelegramBotOptions> options, IUpdateHandlersPool handlersPool, ILogger<HostUpdateRouter> logger)
: base(handlersProvider, awaitingProvider, options.Value, handlersPool)
// Ehat a mess :/
/// <inheritdoc/>
public HostUpdateRouter(
IHandlersProvider handlersProvider,
IAwaitingProvider awaitingProvider,
IOptions<TelegramBotOptions> options,
IUpdateHandlersPool handlersPool,
ILogger<HostUpdateRouter> logger) : base(handlersProvider, awaitingProvider, options.Value, handlersPool)
{
Logger = logger;
ExceptionHandler = new HostExceptionHandler(logger);
}
/// <inheritdoc/>
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);
}
/// <summary>
/// Default exception handler of this router
/// </summary>
/// <param name="logger"></param>
private class HostExceptionHandler(ILogger<HostUpdateRouter> 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;
}
@@ -9,17 +9,26 @@ using Telegrator.Polling;
namespace Telegrator.Hosting.Polling
{
/// <summary>
/// Service for receiving updates for Hosted telegram bots
/// </summary>
/// <param name="botHost"></param>
/// <param name="botClient"></param>
/// <param name="updateRouter"></param>
/// <param name="options"></param>
/// <param name="logger"></param>
public class HostedUpdateReceiver(ITelegramBotHost botHost, ITelegramBotClient botClient, IUpdateRouter updateRouter, IOptions<ReceiverOptions> options, ILogger<HostedUpdateReceiver> logger) : BackgroundService
{
private readonly ReceiverOptions ReceiverOptions = options.Value;
private readonly IUpdateRouter UpdateRouter = updateRouter;
private readonly ReceiverOptions _receiverOptions = options.Value;
private readonly IUpdateRouter _updateRouter = updateRouter;
/// <inheritdoc/>
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);
}
}
}
@@ -9,8 +9,10 @@ using Telegrator.Providers;
namespace Telegrator.Hosting.Providers
{
/// <inheritdoc/>
public class HostAwaitingProvider(IOptions<TelegramBotOptions> options, ITelegramBotInfo botInfo, ILogger<HostAwaitingProvider> logger) : AwaitingProvider(options.Value, botInfo)
{
/// <inheritdoc/>
public override IEnumerable<DescribedHandlerInfo> GetHandlers(IUpdateRouter updateRouter, ITelegramBotClient client, Update update, CancellationToken cancellationToken = default)
{
IEnumerable<DescribedHandlerInfo> handlers = base.GetHandlers(updateRouter, client, update, cancellationToken).ToArray();
@@ -8,28 +8,35 @@ using Telegrator.Providers;
namespace Telegrator.Hosting.Providers
{
/// <summary>
/// Pre host building task
/// </summary>
/// <param name="builder"></param>
public delegate void PreBuildingRoutine(TelegramBotHostBuilder builder);
/// <inheritdoc/>
public class HostHandlersCollection(IServiceCollection hostServiceColletion, IHandlersCollectingOptions options) : HandlersCollection(options)
{
private readonly IServiceCollection Services = hostServiceColletion;
public readonly List<Action<TelegramBotHostBuilder>> PreBuilderRoutines = [];
/// <inheritdoc/>
protected override bool MustHaveParameterlessCtor => false;
/// <summary>
/// List of tasks that should be completed right before building the bot
/// </summary>
public readonly List<PreBuildingRoutine> PreBuilderRoutines = [];
/// <inheritdoc/>
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<TelegramBotHostBuilder> routineDelegate = methodInfo.CreateDelegate<Action<TelegramBotHostBuilder>>(null);
PreBuilderRoutines.Add(routineDelegate);
}
}
if (handlerType.IsPreBuildingRoutine(out MethodInfo? routineMethod))
PreBuilderRoutines.Add(routineMethod.CreateDelegate<PreBuildingRoutine>(null));
return base.AddHandler(handlerType);
}
/// <inheritdoc/>
public override IHandlersCollection AddDescriptor(HandlerDescriptor descriptor)
{
switch (descriptor.Type)
@@ -11,18 +11,25 @@ using Telegrator.Providers;
namespace Telegrator.Hosting.Providers
{
/// <inheritdoc/>
public class HostHandlersProvider : HandlersProvider
{
private readonly IServiceProvider Services;
private readonly ILogger<HostHandlersProvider> Logger;
public HostHandlersProvider(IHandlersCollection handlers, IOptions<TelegramBotOptions> options, ITelegramBotInfo botInfo, IServiceProvider serviceProvider, ILogger<HostHandlersProvider> logger)
: base(handlers, options.Value, botInfo)
/// <inheritdoc/>
public HostHandlersProvider(
IHandlersCollection handlers,
IOptions<TelegramBotOptions> options,
ITelegramBotInfo botInfo,
IServiceProvider serviceProvider,
ILogger<HostHandlersProvider> logger) : base(handlers, options.Value, botInfo)
{
Services = serviceProvider;
Logger = logger;
}
/// <inheritdoc/>
public override IEnumerable<DescribedHandlerInfo> GetHandlers(IUpdateRouter updateRouter, ITelegramBotClient client, Update update, CancellationToken cancellationToken = default)
{
IEnumerable<DescribedHandlerInfo> handlers = base.GetHandlers(updateRouter, client, update, cancellationToken).ToArray();
@@ -30,6 +37,7 @@ namespace Telegrator.Hosting.Providers
return handlers;
}
/// <inheritdoc/>
public override UpdateHandlerBase GetHandlerInstance(HandlerDescriptor descriptor, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
+37 -3
View File
@@ -10,6 +10,9 @@ using Telegrator.MadiatorCore.Descriptors;
namespace Telegrator.Hosting
{
/// <summary>
/// Represents a hosted telegram bot
/// </summary>
public class TelegramBotHost : ITelegramBotHost
{
private readonly IHost _innerHost;
@@ -32,7 +35,8 @@ namespace Telegrator.Hosting
/// <summary>
/// Initializes a new instance of the <see cref="TelegramBotHost"/> class.
/// </summary>
/// <param name="serviceProvider">The service provider.</param>
/// <param name="hostApplicationBuilder">The service provider.</param>
/// <param name="handlers"></param>
internal TelegramBotHost(HostApplicationBuilder hostApplicationBuilder, HostHandlersCollection handlers)
{
RegisterHostServices(hostApplicationBuilder, handlers);
@@ -44,22 +48,52 @@ namespace Telegrator.Hosting
LogHandlers(handlers);
}
/// <summary>
/// Creates new <see cref="TelegramBotHostBuilder"/> with default configuration, services and long-polling update receiving scheme
/// </summary>
/// <returns></returns>
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;
}
/// <summary>
/// Creates new <see cref="TelegramBotHostBuilder"/> with default services and long-polling update receiving scheme
/// </summary>
/// <returns></returns>
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;
}
/// <summary>
/// Creates new EMPTY <see cref="TelegramBotHostBuilder"/> WITHOUT any services or update receiving schemes
/// </summary>
/// <returns></returns>
public static TelegramBotHostBuilder CreateEmptyBuilder()
{
HostApplicationBuilder innerBuilder = Host.CreateEmptyApplicationBuilder(null);
return new TelegramBotHostBuilder(innerBuilder, null);
}
/// <summary>
/// Creates new EMPTY <see cref="TelegramBotHostBuilder"/> WITHOUT any services or update receiving schemes
/// </summary>
/// <returns></returns>
public static TelegramBotHostBuilder CreateEmptyBuilder(TelegramBotHostBuilderSettings? settings)
{
HostApplicationBuilder innerBuilder = Host.CreateEmptyApplicationBuilder(null);
return new TelegramBotHostBuilder(innerBuilder, settings);
}
/// <inheritdoc/>
public async Task StartAsync(CancellationToken cancellationToken = default)
{
+8 -3
View File
@@ -14,6 +14,9 @@ using Telegrator.MadiatorCore;
#pragma warning disable IDE0001
namespace Telegrator.Hosting
{
/// <summary>
/// Represents a hosted telegram bots and services builder that helps manage configuration, logging, lifetime, and more.
/// </summary>
public class TelegramBotHostBuilder : ITelegramBotHostBuilder
{
private readonly HostApplicationBuilder _innerBuilder;
@@ -38,10 +41,12 @@ namespace Telegrator.Hosting
/// <summary>
/// Initializes a new instance of the <see cref="TelegramBotHostBuilder"/> class.
/// </summary>
internal TelegramBotHostBuilder(TelegramBotHostBuilderSettings? settings = null)
/// <param name="hostApplicationBuilder"></param>
/// <param name="settings"></param>
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<TelegramBotOptions>(Configuration.GetSection(nameof(TelegramBotOptions)));
@@ -55,7 +60,7 @@ namespace Telegrator.Hosting
/// <returns></returns>
public TelegramBotHost Build()
{
foreach (var preBuildRoutine in _handlers.PreBuilderRoutines)
foreach (PreBuildingRoutine preBuildRoutine in _handlers.PreBuilderRoutines)
{
try
{
@@ -5,7 +5,7 @@ using Telegrator.Configuration;
namespace Telegrator.Hosting
{
/// <summary>
///
/// Settings os hosted Telegram bot
/// </summary>
public class TelegramBotHostBuilderSettings() : IHandlersCollectingOptions
{
@@ -15,6 +15,7 @@
<EnableNETAnalyzers>True</EnableNETAnalyzers>
<EnforceCodeStyleInBuild>True</EnforceCodeStyleInBuild>
<PackageLicenseFile>LICENSE</PackageLicenseFile>
<Version>1.0.1</Version>
</PropertyGroup>
<ItemGroup>
+61 -1
View File
@@ -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
{
/// <summary>
/// Contains extensions for <see cref="IServiceCollection"/>
/// Provides method to configure <see cref="ITelegramBotHost"/>
/// </summary>
public static class ServicesCollectionExtensions
{
/// <summary>
/// Registers a configuration instance that strongly-typed <typeparamref name="TOptions"/> will bind against using <see cref="ConfigureOptionsProxy{TOptions}"/>.
/// </summary>
/// <typeparam name="TOptions"></typeparam>
/// <param name="services"></param>
/// <param name="configuration"></param>
/// <param name="optionsProxy"></param>
/// <returns></returns>
public static IServiceCollection Configure<TOptions>(this IServiceCollection services, IConfiguration configuration, ConfigureOptionsProxy<TOptions> optionsProxy) where TOptions : class
{
optionsProxy.Configure(services, configuration);
return services;
}
/// <summary>
/// Registers <see cref="TelegramBotHost"/> default services
/// </summary>
/// <param name="services"></param>
/// <returns></returns>
public static IServiceCollection AddTelegramBotHostDefaults(this IServiceCollection services)
{
services.AddLogging(builder => builder.AddConsole());
@@ -34,6 +53,11 @@ namespace Telegrator.Hosting
return services;
}
/// <summary>
/// Registers <see cref="ITelegramBotClient"/> service with <see cref="HostedUpdateReceiver"/> to receive updates using long polling
/// </summary>
/// <param name="services"></param>
/// <returns></returns>
public static IServiceCollection AddTelegramReceiver(this IServiceCollection services)
{
services.AddHttpClient<ITelegramBotClient>("tgreceiver").RemoveAllLoggers().AddTypedClient(TypedTelegramBotClientFactory);
@@ -41,12 +65,26 @@ namespace Telegrator.Hosting
return services;
}
/// <summary>
/// <see cref="ITelegramBotClient"/> factory method
/// </summary>
/// <param name="httpClient"></param>
/// <param name="provider"></param>
/// <returns></returns>
private static ITelegramBotClient TypedTelegramBotClientFactory(HttpClient httpClient, IServiceProvider provider)
=> new TelegramBotClient(provider.GetRequiredService<IOptions<TelegramBotClientOptions>>().Value, httpClient);
}
/// <summary>
/// Provides useful methods to adjust <see cref="ITelegramBotHost"/>
/// </summary>
public static class TelegramBotHostExtensions
{
/// <summary>
/// Configures bots available commands depending on what handlers was registered
/// </summary>
/// <param name="botHost"></param>
/// <returns></returns>
public static ITelegramBotHost SetBotCommands(this ITelegramBotHost botHost)
{
ITelegramBotClient client = botHost.Services.GetRequiredService<ITelegramBotClient>();
@@ -55,4 +93,26 @@ namespace Telegrator.Hosting
return botHost;
}
}
/// <summary>
/// Provides extension methods for reflection and type inspection.
/// </summary>
public static class ReflectionExtensions
{
/// <summary>
/// Checks if a type implements the <see cref="IPreBuildingRoutine"/> interface.
/// </summary>
/// <param name="handlerType">The type to check.</param>
/// <param name="routineMethod"></param>
/// <returns>True if the type implements IPreBuildingRoutine; otherwise, false.</returns>
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;
}
}
}
@@ -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
{
/// <summary>
/// Abstract base attribute for filtering callback-based updates.
/// Supports various message types including regular messages, edited messages, channel posts, and business messages.
/// </summary>
/// <param name="filters">The filters to apply to messages</param>
public abstract class CallbackQueryAttribute(params IFilter<CallbackQuery>[] filters) : UpdateFilterAttribute<CallbackQuery>(filters)
{
/// <summary>
/// Gets the allowed update types that this filter can process.
/// </summary>
public override UpdateType[] AllowedTypes => [UpdateType.CallbackQuery];
/// <summary>
/// Extracts the message from various types of updates.
/// </summary>
/// <param name="update">The Telegram update</param>
/// <returns>The message from the update, or null if not present</returns>
public override CallbackQuery? GetFilterringTarget(Update update)
=> update.CallbackQuery;
}
/// <summary>
/// Attribute for filtering <see cref="CallbackQuery"/>'s data
/// </summary>
/// <param name="data"></param>
public class CallbackDataAttribute(string data)
: CallbackQueryAttribute(new CallbackDataFilter(data))
{ }
/// <summary>
/// Attribute to check if <see cref="CallbackQuery"/> belongs to a specific message by its ID
/// </summary>
public class CallbackInlineIdAttribute(string inlineMessageId)
: CallbackQueryAttribute(new CallbackInlineIdFilter(inlineMessageId))
{ }
}
@@ -47,7 +47,7 @@ namespace Telegrator.Annotations
/// </summary>
/// <param name="alliases">The command aliases to match against.</param>
public CommandAlliasAttribute(params string[] alliases)
: base(new CommandAlliasFilter(alliases)) => Alliases = alliases;
: base(new CommandAlliasFilter(alliases)) => Alliases = alliases.Select(c => c.TrimStart('/')).ToArray();
/// <summary>
/// Gets the filtering target (Message) from the update.
@@ -10,9 +10,18 @@ namespace Telegrator.Annotations
{ }
/// <summary>
/// Attribute for filtering messages in reply chain.
/// Attribute for checking message's reply chain.
/// </summary>
public class MessageRepliedAttribute(int replyDepth = 1)
: MessageFilterAttribute(new MessageRepliedFilter(replyDepth))
public class HasReplyAttribute(int replyDepth = 1)
: MessageFilterAttribute(new MessageHasReplyFilter(replyDepth))
{ }
/// <summary>
/// Helper filter class for filters that operate on replied messages.
/// Provides functionality to traverse reply chains and access replied message content.
/// </summary>
/// <param name="replyDepth"></param>
public class FromReplyChainAttribute(int replyDepth = 1)
: MessageFilterAttribute(new FromReplyChainFilter(replyDepth))
{ }
}
+4 -2
View File
@@ -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
/// <summary>
/// Creates new instance of <see cref="WelcomeAttribute"/>
/// </summary>
public WelcomeAttribute() : base(new MessageChatTypeFilter(ChatType.Private), new CommandAlliasFilter("start"))
/// <param name="onlyFirst"></param>
public WelcomeAttribute(bool onlyFirst = false) : base(new MessageChatTypeFilter(ChatType.Private), new CommandAlliasFilter("start"), Filter<Message>.If(ctx => !onlyFirst || ctx.Input.Id == 0))
{ }
}
}
@@ -26,9 +26,9 @@ namespace Telegrator.Attributes.Components
public UpdateType Type { get; private set; }
/// <summary>
/// Gets or sets concurrency of this <see cref="UpdateHandlerBase"/> in same <see cref="UpdateType"/> pool
/// Gets or sets importance of this <see cref="UpdateHandlerBase"/> in same <see cref="UpdateType"/> pool
/// </summary>
public int Concurrency { get; set; }
public int Importance { get; set; }
/// <summary>
/// Gets or sets priority of this <see cref="UpdateHandlerBase"/> in same type handlers pool
@@ -40,11 +40,11 @@ namespace Telegrator.Attributes.Components
/// </summary>
/// <param name="expectingHandlerType"></param>
/// <param name="updateType"></param>
/// <param name="concurrency"></param>
/// <param name="importance"></param>
/// <exception cref="ArgumentNullException"></exception>
/// <exception cref="ArgumentException"></exception>
/// <exception cref="Exception"></exception>
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;
}
/// <summary>
/// Gets an <see cref="DescriptorIndexer"/> of this <see cref="UpdateHandlerAttributeBase"/> from <see cref="Concurrency"/> and <see cref="Priority"/>
/// Gets an <see cref="DescriptorIndexer"/> of this <see cref="UpdateHandlerAttributeBase"/> from <see cref="Importance"/> and <see cref="Priority"/>
/// </summary>
/// <returns></returns>
public DescriptorIndexer GetIndexer()
@@ -6,13 +6,13 @@ namespace Telegrator.Attributes
{
/// <summary>
/// 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.
/// </summary>
/// <typeparam name="T">The type of the update handler that this attribute is applied to.</typeparam>
/// <param name="updateType">The type of update that this handler can process.</param>
/// <param name="concurrency">The concurrency level for this handler (default: 0 for unlimited).</param>
public abstract class UpdateHandlerAttribute<T>(UpdateType updateType, int concurrency = 0)
: UpdateHandlerAttributeBase([typeof(T)], updateType, concurrency) where T : UpdateHandlerBase
/// <param name="importance">The importance level for this handler (default: 0 for unlimited).</param>
public abstract class UpdateHandlerAttribute<T>(UpdateType updateType, int importance = 0)
: UpdateHandlerAttributeBase([typeof(T)], updateType, importance) where T : UpdateHandlerBase
{
}
}
@@ -0,0 +1,51 @@
using Telegram.Bot.Types;
using Telegrator.Filters.Components;
namespace Telegrator.Filters
{
/// <summary>
/// Filter thet checks <see cref="CallbackQuery"/>'s data
/// </summary>
public class CallbackDataFilter : Filter<CallbackQuery>
{
private readonly string _data;
/// <summary>
/// Initialize new instance of <see cref="CallbackDataFilter"/>
/// </summary>
/// <param name="data"></param>
public CallbackDataFilter(string data)
{
_data = data;
}
/// <inheritdoc/>
public override bool CanPass(FilterExecutionContext<CallbackQuery> context)
{
return context.Input.Data == _data;
}
}
/// <summary>
/// Filter that checks if <see cref="CallbackQuery"/> belongs to a specific message
/// </summary>
public class CallbackInlineIdFilter : Filter<CallbackQuery>
{
private readonly string _inlineMessageId;
/// <summary>
/// Initialize new instance of <see cref="CallbackInlineIdFilter"/>
/// </summary>
/// <param name="inlineMessageId"></param>
public CallbackInlineIdFilter(string inlineMessageId)
{
_inlineMessageId = inlineMessageId;
}
/// <inheritdoc/>
public override bool CanPass(FilterExecutionContext<CallbackQuery> context)
{
return context.Input.InlineMessageId == _inlineMessageId;
}
}
}
@@ -45,7 +45,12 @@ namespace Telegrator.Filters.Components
foreach (IFilter<T> 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);
}
@@ -47,7 +47,12 @@ namespace Telegrator.Filters.Components
{
FilterExecutionContext<T> 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;
@@ -37,7 +37,12 @@
foreach (IFilter<T> 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);
}
+1 -3
View File
@@ -1,6 +1,4 @@
using System.Reflection;
using Telegrator;
using Telegrator.Filters.Components;
using Telegrator.Filters.Components;
namespace Telegrator.Filters
{
+1 -1
View File
@@ -28,7 +28,7 @@ namespace Telegrator.Filters
/// <param name="mention">The username to check for in the mention.</param>
public MentionedFilter(string mention)
{
Mention = mention;
Mention = mention.TrimStart('@');
}
/// <summary>
+1 -1
View File
@@ -18,7 +18,7 @@ namespace Telegrator.Filters
/// <inheritdoc/>
public override bool CanPass(FilterExecutionContext<Message> context)
{
MessageRepliedFilter? repliedFilter = context.CompletedFilters.Get<MessageRepliedFilter>().SingleOrDefault();
FromReplyChainFilter? repliedFilter = context.CompletedFilters.Get<FromReplyChainFilter>().SingleOrDefault();
Target = repliedFilter?.Reply ?? context.Input;
return CanPassNext(context);
}
+45 -5
View File
@@ -4,11 +4,51 @@ using Telegrator.Filters.Components;
namespace Telegrator.Filters
{
/// <summary>
/// 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
/// </summary>
/// <param name="replyDepth">The depth of reply chain to traverse (default: 1).</param>
public class MessageRepliedFilter(int replyDepth = 1) : Filter<Message>
public class MessageHasReplyFilter(int replyDepth = 1) : Filter<Message>
{
/// <summary>
/// Gets the replied message at the specified depth in the reply chain.
/// </summary>
public Message Reply { get; private set; } = null!;
/// <summary>
/// Gets the depth of reply chain traversal.
/// </summary>
public int ReplyDepth { get; private set; } = replyDepth;
/// <summary>
/// Determines if the message can pass through the filter by first validating
/// the reply chain and then applying specific filter logic.
/// </summary>
/// <param name="context">The filter execution context containing the message.</param>
/// <returns>True if the message passes both reply validation and specific filter criteria; otherwise, false.</returns>
public override bool CanPass(FilterExecutionContext<Message> 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;
}
}
/// <summary>
/// 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.
/// </summary>
/// <param name="replyDepth"></param>
public class FromReplyChainFilter(int replyDepth = 1) : Filter<Message>
{
/// <summary>
/// Gets the replied message at the specified depth in the reply chain.
@@ -44,7 +84,7 @@ namespace Telegrator.Filters
/// <summary>
/// Filter that checks if the replied message was sent by the bot itself.
/// <para>( ! ): REQUIRES <see cref="MessageRepliedFilter"/> before</para>
/// <para>( ! ): REQUIRES <see cref="MessageHasReplyFilter"/> before</para>
/// </summary>
public class MeRepliedFilter : Filter<Message>
{
@@ -55,7 +95,7 @@ namespace Telegrator.Filters
/// <returns>True if the replied message was sent by the bot; otherwise, false.</returns>
public override bool CanPass(FilterExecutionContext<Message> context)
{
MessageRepliedFilter repliedFilter = context.CompletedFilters.Get<MessageRepliedFilter>(0);
MessageHasReplyFilter repliedFilter = context.CompletedFilters.Get<MessageHasReplyFilter>(0);
return context.BotInfo.User == repliedFilter.Reply.From;
}
}
+1 -1
View File
@@ -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;
+2 -2
View File
@@ -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.
/// </summary>
/// <param name="concurrency">The maximum number of concurrent executions allowed (default: -1 for unlimited).</param>
public class AnyUpdateHandlerAttribute(int concurrency = -1) : UpdateHandlerAttribute<AnyUpdateHandler>(UpdateType.Unknown, concurrency)
/// <param name="importance"></param>
public class AnyUpdateHandlerAttribute(int importance = -1) : UpdateHandlerAttribute<AnyUpdateHandler>(UpdateType.Unknown, importance)
{
/// <summary>
/// Always returns true, allowing any update to pass through this filter.
@@ -95,7 +95,7 @@ namespace Telegrator.Handlers.Building.Components
/// <returns>The builder instance.</returns>
public void SetConcurreny(int concurrency)
{
Indexer = Indexer.UpdateConcurrency(concurrency);
Indexer = Indexer.UpdateImportance(concurrency);
}
/// <summary>
+3 -3
View File
@@ -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.
/// </summary>
/// <param name="concurrency">The maximum number of concurrent executions allowed (default: 0 for unlimited).</param>
public sealed class CallbackQueryHandlerAttribute(int concurrency = 0) : UpdateHandlerAttribute<CallbackQueryHandler>(UpdateType.CallbackQuery, concurrency)
/// <param name="importance"></param>
public sealed class CallbackQueryHandlerAttribute(int importance = 0) : UpdateHandlerAttribute<CallbackQueryHandler>(UpdateType.CallbackQuery, importance)
{
/// <summary>
/// Always returns true, allowing any callback query update to pass through this filter.
/// </summary>
/// <param name="context">The filter execution context (unused).</param>
/// <returns>Always returns true to allow any callback query update.</returns>
public override bool CanPass(FilterExecutionContext<Update> context) => true;
public override bool CanPass(FilterExecutionContext<Update> context) => context.Input is { CallbackQuery: { } };
}
/// <summary>
+1 -2
View File
@@ -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 '/').
/// </summary>
/// <param name="concurrency">The maximum number of concurrent executions allowed (default: 1).</param>
public class CommandHandlerAttribute(int concurrency = 1) : UpdateHandlerAttribute<CommandHandler>(UpdateType.Message, concurrency)
public class CommandHandlerAttribute(int importance = 1) : UpdateHandlerAttribute<CommandHandler>(UpdateType.Message, importance)
{
/// <summary>
/// Gets the command that was extracted from the message (without the '/' prefix and bot username).
+1 -2
View File
@@ -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.
/// </summary>
/// <param name="concurrency">The maximum number of concurrent executions allowed (default: 0 for unlimited).</param>
public class MessageHandlerAttribute(int concurrency = 0) : UpdateHandlerAttribute<MessageHandler>(UpdateType.Message, concurrency)
public class MessageHandlerAttribute(int importance = 0) : UpdateHandlerAttribute<MessageHandler>(UpdateType.Message, importance)
{
/// <summary>
/// Checks if the update contains a valid message.
+121
View File
@@ -0,0 +1,121 @@
using System.Diagnostics;
namespace Telegrator
{
/// <summary>
/// Telegrator's Debug logger helper
/// </summary>
public static class LeveledDebug
{
/// <summary>
/// Writes debug message if Indent level has Router flag
/// </summary>
/// <param name="message"></param>
public static void RouterWriteLine(string message)
{
if (Debug.IndentLevel.HasFlag(DebugLevel.Router))
Debug.WriteLine(message);
}
/// <summary>
/// Writes debug message if Indent level has Router flag
/// </summary>
/// <param name="message"></param>
/// <param name="args"></param>
public static void RouterWriteLine(string message, params object[] args)
{
if (Debug.IndentLevel.HasFlag(DebugLevel.Router))
Debug.WriteLine(message, args);
}
/// <summary>
/// Writes debug message if Indent level has Providers flag
/// </summary>
/// <param name="message"></param>
public static void ProviderWriteLine(string message)
{
if (Debug.IndentLevel.HasFlag(DebugLevel.Providers))
Debug.WriteLine(message);
}
/// <summary>
/// Writes debug message if Indent level has Providers flag
/// </summary>
/// <param name="message"></param>
/// <param name="args"></param>
public static void ProviderWriteLine(string message, params object[] args)
{
if (Debug.IndentLevel.HasFlag(DebugLevel.Providers))
Debug.WriteLine(message, args);
}
/// <summary>
/// Writes debug message if Indent level has Filters flag
/// </summary>
/// <param name="message"></param>
public static void FilterWriteLine(string message)
{
if (Debug.IndentLevel.HasFlag(DebugLevel.Filters))
Debug.WriteLine(message);
}
/// <summary>
/// Writes debug message if Indent level has Filters flag
/// </summary>
/// <param name="message"></param>
/// <param name="args"></param>
public static void FilterWriteLine(string message, params object[] args)
{
if (Debug.IndentLevel.HasFlag(DebugLevel.Filters))
Debug.WriteLine(message, args);
}
/// <summary>
/// Writes debug message if Indent level has Pool flag
/// </summary>
/// <param name="message"></param>
public static void PoolWriteLine(string message)
{
if (Debug.IndentLevel.HasFlag(DebugLevel.HandlersPool))
Debug.WriteLine(message);
}
/// <summary>
/// Writes debug message if Indent level has Pool flag
/// </summary>
/// <param name="message"></param>
/// <param name="args"></param>
public static void PoolWriteLine(string message, params object[] args)
{
if (Debug.IndentLevel.HasFlag(DebugLevel.HandlersPool))
Debug.WriteLine(message, args);
}
}
/// <summary>
/// Levels of debug writing
/// </summary>
[Flags]
public enum DebugLevel
{
/// <summary>
/// Write debug messages from filters execution
/// </summary>
Filters = 0x1,
/// <summary>
/// Write debug messages from handlers providers execution
/// </summary>
Providers = 0x2,
/// <summary>
/// Write debug messages from update router's execution
/// </summary>
Router = 0x4,
/// <summary>
/// Write debug messages from handlers pool execution
/// </summary>
HandlersPool = 0x8
}
}
@@ -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);
}
}
/// <inheritdoc/>
public override string ToString()
=> DisplayString ?? HandlerInstance.GetType().Name;
}
}
@@ -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<Update> 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);
}
}
@@ -3,9 +3,9 @@
namespace Telegrator.MadiatorCore.Descriptors
{
/// <summary>
/// Represents an indexer for handler descriptors, containing concurrency and priority information.
/// Represents an indexer for handler descriptors, containing importance and priority information.
/// </summary>
public readonly struct DescriptorIndexer(int routerIndex, int concurrency, int priority) : IComparable<DescriptorIndexer>
public readonly struct DescriptorIndexer(int routerIndex, int importance, int priority) : IComparable<DescriptorIndexer>
{
/// <summary>
/// Index of this descriptor when it was added to router
@@ -15,7 +15,7 @@ namespace Telegrator.MadiatorCore.Descriptors
/// <summary>
/// Of this handlert type
/// </summary>
public readonly int Importance = concurrency;
public readonly int Importance = importance;
/// <summary>
/// The priority of the handler.
@@ -28,7 +28,7 @@ namespace Telegrator.MadiatorCore.Descriptors
/// <param name="routerIndex"></param>
/// <param name="pollingHandler">The handler attribute.</param>
public DescriptorIndexer(int routerIndex, UpdateHandlerAttributeBase pollingHandler)
: this(routerIndex, pollingHandler.Concurrency, pollingHandler.Priority) { }
: this(routerIndex, pollingHandler.Importance, pollingHandler.Priority) { }
/// <summary>
/// Returns a new <see cref="DescriptorIndexer"/> with updated priority.
@@ -39,12 +39,12 @@ namespace Telegrator.MadiatorCore.Descriptors
=> new DescriptorIndexer(RouterIndex, Importance, priority);
/// <summary>
/// Returns a new <see cref="DescriptorIndexer"/> with updated concurrency.
/// Returns a new <see cref="DescriptorIndexer"/> with updated importance.
/// </summary>
/// <param name="concurrency">The new concurrency value.</param>
/// <param name="importance">The new importance value.</param>
/// <returns>A new <see cref="DescriptorIndexer"/> instance.</returns>
public DescriptorIndexer UpdateConcurrency(int concurrency)
=> new DescriptorIndexer(RouterIndex, concurrency, Priority);
public DescriptorIndexer UpdateImportance(int importance)
=> new DescriptorIndexer(RouterIndex, importance, Priority);
/// <summary>
/// Returns a new <see cref="DescriptorIndexer"/> with updated RouterIndex.
@@ -79,7 +79,7 @@ namespace Telegrator.MadiatorCore.Descriptors
/// <summary>
/// Returns a string representation of the indexer.
/// </summary>
/// <returns>A string in the format (C:concurrency, P:priority).</returns>
/// <returns>A string in the format (C:importance, P:priority).</returns>
public override string ToString()
{
return string.Format("(I:{0}, C:{1}, P:{2})", RouterIndex, Importance, Priority);
@@ -139,6 +139,7 @@ namespace Telegrator.MadiatorCore.Descriptors
UpdateType = handlerAttribute.Type;
Indexer = handlerAttribute.GetIndexer();
Filters = new DescriptorFiltersSet(handlerAttribute, stateKeeperAttribute, filters);
DisplayString = HandlerInspector.GetDisplayName(handlerType);
}
/// <summary>
@@ -402,5 +403,9 @@ namespace Telegrator.MadiatorCore.Descriptors
ServiceKey = serviceKey ?? throw new ArgumentNullException(nameof(serviceKey));
InstanceFactory = instanceFactory ?? throw new ArgumentNullException(nameof(instanceFactory));
}
/// <inheritdoc/>
public override string ToString()
=> DisplayString ?? HandlerType.Name;
}
}
@@ -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
/// </summary>
public UpdateType HandlingType => _handlingType;
/// <summary>
/// Gets count of registered handlers in list
/// </summary>
public int Count => _innerCollection.Count;
/// <summary>
/// Gets or sets the <see cref="HandlerDescriptor"/> at the specified index.
/// </summary>
@@ -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
/// </summary>
public static class HandlerInspector
{
/// <summary>
/// Gets handler's display name
/// </summary>
/// <param name="handlerType"></param>
/// <returns></returns>
public static string? GetDisplayName(MemberInfo handlerType)
{
return handlerType.GetCustomAttribute<DisplayNameAttribute>()?.DisplayName;
}
/// <summary>
/// Gets the handler attribute from the specified member info.
/// </summary>
+44 -2
View File
@@ -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
/// <returns>A task representing the asynchronous error handling operation.</returns>
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,14 +108,21 @@ namespace Telegrator.Polling
/// <returns>A task representing the asynchronous update handling operation.</returns>
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<DescribedHandlerInfo> GetHandlers(ITelegramBotClient botClient, Update update, CancellationToken cancellationToken)
{
try
{
// Getting handlers in update awaiting pool
IEnumerable<DescribedHandlerInfo> handlers = AwaitingProvider.GetHandlers(this, botClient, update, cancellationToken);
@@ -121,5 +131,37 @@ namespace Telegrator.Polling
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;
}
}
}
}
}
+27 -6
View File
@@ -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
/// <returns>A collection of described handler information for the update</returns>
public virtual IEnumerable<DescribedHandlerInfo> GetHandlers(IUpdateRouter updateRouter, ITelegramBotClient client, Update update, CancellationToken cancellationToken = default)
{
LeveledDebug.ProviderWriteLine("Requested handlers for UpdateType.{0}", update.Type);
if (!HandlersDictionary.TryGetValue(update.Type, out HandlerDescriptorList? descriptors))
{
if (!HandlersDictionary.TryGetValue(UpdateType.Unknown, out 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 [];
}
if (descriptors == null || !descriptors.Any())
return [];
return DescribeDescriptors(descriptors, updateRouter, client, update, cancellationToken);
IEnumerable<DescribedHandlerInfo> described = DescribeDescriptors(descriptors, updateRouter, client, update, cancellationToken);
LeveledDebug.ProviderWriteLine("Described total of {0} handlers for Update ({1})", described.Count(), update.Id);
LeveledDebug.ProviderWriteLine("Described handlers : {0}", string.Join(", ", described));
return described;
}
/// <summary>
@@ -103,6 +111,9 @@ namespace Telegrator.Providers
/// <returns>A collection of described handler information</returns>
public virtual IEnumerable<DescribedHandlerInfo> DescribeDescriptors(HandlerDescriptorList descriptors, IUpdateRouter updateRouter, ITelegramBotClient client, Update update, CancellationToken cancellationToken = default)
{
try
{
LeveledDebug.ProviderWriteLine("Describing descriptors of descriptorsList.HandlingType.{0} for Update ({1})", descriptors.HandlingType, update.Id);
foreach (HandlerDescriptor descriptor in descriptors.Reverse())
{
cancellationToken.ThrowIfCancellationRequested();
@@ -115,6 +126,11 @@ namespace Telegrator.Providers
break;
}
}
finally
{
LeveledDebug.ProviderWriteLine("Describing for Update ({0}) finished", update.Id);
}
}
/// <summary>
/// Describes a single handler descriptor for a given update context.
@@ -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<Update> filterContext = new FilterExecutionContext<Update>(BotInfo, update, update);
Dictionary<string, object> data = new Dictionary<string, object>()
{
{ "handler_name", descriptor.ToString() }
};
FilterExecutionContext<Update> filterContext = new FilterExecutionContext<Update>(BotInfo, update, update, data, []);
if (!descriptor.Filters.Validate(filterContext))
return null;
+1
View File
@@ -17,6 +17,7 @@
<EnableNETAnalyzers>True</EnableNETAnalyzers>
<EnforceCodeStyleInBuild>True</EnforceCodeStyleInBuild>
<PackageLicenseFile>LICENSE</PackageLicenseFile>
<Version>1.0.1</Version>
</PropertyGroup>
<ItemGroup>
+25
View File
@@ -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
}
}
/// <summary>
/// Contains extension method for number types
/// </summary>
public static class NumbersExtensions
{
/// <summary>
/// Check if int value has int flag using bit compare
/// </summary>
/// <param name="value"></param>
/// <param name="flag"></param>
/// <returns></returns>
public static bool HasFlag(this int value, int flag)
=> (value & flag) == flag;
/// <summary>
/// Check if int value has enum flag using bit compare
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="value"></param>
/// <param name="flag"></param>
/// <returns></returns>
public static bool HasFlag<T>(this int value, T flag) where T : Enum
=> value.HasFlag(Convert.ToInt32(flag));
}
}