* 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 namespace Telegrator.Hosting.Polling
{ {
public class HostUpdateHandlersPool(IOptions<TelegramBotOptions> options, ILogger<HostUpdateHandlersPool> logger) /// <inheritdoc/>
: UpdateHandlersPool(options.Value, options.Value.GlobalCancellationToken) public class HostUpdateHandlersPool(IOptions<TelegramBotOptions> options, ILogger<HostUpdateHandlersPool> logger) : UpdateHandlersPool(options.Value, options.Value.GlobalCancellationToken)
{ {
private readonly ILogger<HostUpdateHandlersPool> _logger = logger; private readonly ILogger<HostUpdateHandlersPool> _logger = logger;
/// <inheritdoc/>
protected override async Task ExecuteHandlerWrapper(DescribedHandlerInfo enqueuedHandler) protected override async Task ExecuteHandlerWrapper(DescribedHandlerInfo enqueuedHandler)
{ {
_logger.LogInformation("Handler \"{0}\" has entered execution pool", enqueuedHandler.DisplayString); _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 namespace Telegrator.Hosting.Polling
{ {
/// <inheritdoc/>
public class HostUpdateRouter : UpdateRouter public class HostUpdateRouter : UpdateRouter
{ {
/// <summary>
/// <see cref="ILogger"/> of this router
/// </summary>
protected readonly ILogger<HostUpdateRouter> Logger; protected readonly ILogger<HostUpdateRouter> Logger;
public HostUpdateRouter(IHandlersProvider handlersProvider, IAwaitingProvider awaitingProvider, IOptions<TelegramBotOptions> options, IUpdateHandlersPool handlersPool, ILogger<HostUpdateRouter> logger) // Ehat a mess :/
: base(handlersProvider, awaitingProvider, options.Value, handlersPool) /// <inheritdoc/>
public HostUpdateRouter(
IHandlersProvider handlersProvider,
IAwaitingProvider awaitingProvider,
IOptions<TelegramBotOptions> options,
IUpdateHandlersPool handlersPool,
ILogger<HostUpdateRouter> logger) : base(handlersProvider, awaitingProvider, options.Value, handlersPool)
{ {
Logger = logger; Logger = logger;
ExceptionHandler = new HostExceptionHandler(logger); ExceptionHandler = new HostExceptionHandler(logger);
} }
/// <inheritdoc/>
public override Task HandleUpdateAsync(ITelegramBotClient botClient, Update update, CancellationToken cancellationToken) public override Task HandleUpdateAsync(ITelegramBotClient botClient, Update update, CancellationToken cancellationToken)
{ {
Logger.LogInformation("Received update of type \"{type}\"", update.Type); Logger.LogInformation("Received update of type \"{type}\"", update.Type);
return base.HandleUpdateAsync(botClient, update, cancellationToken); 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 private class HostExceptionHandler(ILogger<HostUpdateRouter> logger) : IRouterExceptionHandler
{ {
public void HandleException(ITelegramBotClient botClient, Exception exception, HandleErrorSource source, CancellationToken cancellationToken) public void HandleException(ITelegramBotClient botClient, Exception exception, HandleErrorSource source, CancellationToken cancellationToken)
@@ -34,7 +49,7 @@ namespace Telegrator.Hosting.Polling
if (exception is HandlerFaultedException handlerFaultedException) if (exception is HandlerFaultedException handlerFaultedException)
{ {
logger.LogError("\"{handler}\" handler's execution was faulted :\n{exception}", logger.LogError("\"{handler}\" handler's execution was faulted :\n{exception}",
handlerFaultedException.HandlerInfo.DisplayString, handlerFaultedException.HandlerInfo.ToString(),
handlerFaultedException.InnerException?.ToString() ?? "No inner exception"); handlerFaultedException.InnerException?.ToString() ?? "No inner exception");
return; return;
} }
@@ -9,17 +9,26 @@ using Telegrator.Polling;
namespace Telegrator.Hosting.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 public class HostedUpdateReceiver(ITelegramBotHost botHost, ITelegramBotClient botClient, IUpdateRouter updateRouter, IOptions<ReceiverOptions> options, ILogger<HostedUpdateReceiver> logger) : BackgroundService
{ {
private readonly ReceiverOptions ReceiverOptions = options.Value; private readonly ReceiverOptions _receiverOptions = options.Value;
private readonly IUpdateRouter UpdateRouter = updateRouter; private readonly IUpdateRouter _updateRouter = updateRouter;
/// <inheritdoc/>
protected override async Task ExecuteAsync(CancellationToken stoppingToken) protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{ {
logger.LogInformation("Starting receiving updates via long-polling"); logger.LogInformation("Starting receiving updates via long-polling");
ReceiverOptions.AllowedUpdates = botHost.UpdateRouter.HandlersProvider.AllowedTypes.ToArray(); _receiverOptions.AllowedUpdates = botHost.UpdateRouter.HandlersProvider.AllowedTypes.ToArray();
ReactiveUpdateReceiver updateReceiver = new ReactiveUpdateReceiver(botClient, ReceiverOptions); ReactiveUpdateReceiver updateReceiver = new ReactiveUpdateReceiver(botClient, _receiverOptions);
await updateReceiver.ReceiveAsync(UpdateRouter, stoppingToken).ConfigureAwait(false); await updateReceiver.ReceiveAsync(_updateRouter, stoppingToken).ConfigureAwait(false);
} }
} }
} }
@@ -9,8 +9,10 @@ using Telegrator.Providers;
namespace Telegrator.Hosting.Providers namespace Telegrator.Hosting.Providers
{ {
/// <inheritdoc/>
public class HostAwaitingProvider(IOptions<TelegramBotOptions> options, ITelegramBotInfo botInfo, ILogger<HostAwaitingProvider> logger) : AwaitingProvider(options.Value, botInfo) 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) public override IEnumerable<DescribedHandlerInfo> GetHandlers(IUpdateRouter updateRouter, ITelegramBotClient client, Update update, CancellationToken cancellationToken = default)
{ {
IEnumerable<DescribedHandlerInfo> handlers = base.GetHandlers(updateRouter, client, update, cancellationToken).ToArray(); IEnumerable<DescribedHandlerInfo> handlers = base.GetHandlers(updateRouter, client, update, cancellationToken).ToArray();
@@ -8,28 +8,35 @@ using Telegrator.Providers;
namespace Telegrator.Hosting.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) public class HostHandlersCollection(IServiceCollection hostServiceColletion, IHandlersCollectingOptions options) : HandlersCollection(options)
{ {
private readonly IServiceCollection Services = hostServiceColletion; private readonly IServiceCollection Services = hostServiceColletion;
public readonly List<Action<TelegramBotHostBuilder>> PreBuilderRoutines = [];
/// <inheritdoc/>
protected override bool MustHaveParameterlessCtor => false; 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) public override IHandlersCollection AddHandler(Type handlerType)
{ {
// if (handlerType.IsPreBuildingRoutine(out MethodInfo? routineMethod))
if (handlerType.GetInterface(nameof(IPreBuildingRoutine)) != null) PreBuilderRoutines.Add(routineMethod.CreateDelegate<PreBuildingRoutine>(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);
}
}
return base.AddHandler(handlerType); return base.AddHandler(handlerType);
} }
/// <inheritdoc/>
public override IHandlersCollection AddDescriptor(HandlerDescriptor descriptor) public override IHandlersCollection AddDescriptor(HandlerDescriptor descriptor)
{ {
switch (descriptor.Type) switch (descriptor.Type)
@@ -11,18 +11,25 @@ using Telegrator.Providers;
namespace Telegrator.Hosting.Providers namespace Telegrator.Hosting.Providers
{ {
/// <inheritdoc/>
public class HostHandlersProvider : HandlersProvider public class HostHandlersProvider : HandlersProvider
{ {
private readonly IServiceProvider Services; private readonly IServiceProvider Services;
private readonly ILogger<HostHandlersProvider> Logger; private readonly ILogger<HostHandlersProvider> Logger;
public HostHandlersProvider(IHandlersCollection handlers, IOptions<TelegramBotOptions> options, ITelegramBotInfo botInfo, IServiceProvider serviceProvider, ILogger<HostHandlersProvider> logger) /// <inheritdoc/>
: base(handlers, options.Value, botInfo) public HostHandlersProvider(
IHandlersCollection handlers,
IOptions<TelegramBotOptions> options,
ITelegramBotInfo botInfo,
IServiceProvider serviceProvider,
ILogger<HostHandlersProvider> logger) : base(handlers, options.Value, botInfo)
{ {
Services = serviceProvider; Services = serviceProvider;
Logger = logger; Logger = logger;
} }
/// <inheritdoc/>
public override IEnumerable<DescribedHandlerInfo> GetHandlers(IUpdateRouter updateRouter, ITelegramBotClient client, Update update, CancellationToken cancellationToken = default) public override IEnumerable<DescribedHandlerInfo> GetHandlers(IUpdateRouter updateRouter, ITelegramBotClient client, Update update, CancellationToken cancellationToken = default)
{ {
IEnumerable<DescribedHandlerInfo> handlers = base.GetHandlers(updateRouter, client, update, cancellationToken).ToArray(); IEnumerable<DescribedHandlerInfo> handlers = base.GetHandlers(updateRouter, client, update, cancellationToken).ToArray();
@@ -30,6 +37,7 @@ namespace Telegrator.Hosting.Providers
return handlers; return handlers;
} }
/// <inheritdoc/>
public override UpdateHandlerBase GetHandlerInstance(HandlerDescriptor descriptor, CancellationToken cancellationToken = default) public override UpdateHandlerBase GetHandlerInstance(HandlerDescriptor descriptor, CancellationToken cancellationToken = default)
{ {
cancellationToken.ThrowIfCancellationRequested(); cancellationToken.ThrowIfCancellationRequested();
+37 -3
View File
@@ -10,6 +10,9 @@ using Telegrator.MadiatorCore.Descriptors;
namespace Telegrator.Hosting namespace Telegrator.Hosting
{ {
/// <summary>
/// Represents a hosted telegram bot
/// </summary>
public class TelegramBotHost : ITelegramBotHost public class TelegramBotHost : ITelegramBotHost
{ {
private readonly IHost _innerHost; private readonly IHost _innerHost;
@@ -32,7 +35,8 @@ namespace Telegrator.Hosting
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="TelegramBotHost"/> class. /// Initializes a new instance of the <see cref="TelegramBotHost"/> class.
/// </summary> /// </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) internal TelegramBotHost(HostApplicationBuilder hostApplicationBuilder, HostHandlersCollection handlers)
{ {
RegisterHostServices(hostApplicationBuilder, handlers); RegisterHostServices(hostApplicationBuilder, handlers);
@@ -44,22 +48,52 @@ namespace Telegrator.Hosting
LogHandlers(handlers); 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() 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.AddTelegramBotHostDefaults();
builder.Services.AddTelegramReceiver(); builder.Services.AddTelegramReceiver();
return builder; 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) 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.AddTelegramBotHostDefaults();
builder.Services.AddTelegramReceiver(); builder.Services.AddTelegramReceiver();
return builder; 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/> /// <inheritdoc/>
public async Task StartAsync(CancellationToken cancellationToken = default) public async Task StartAsync(CancellationToken cancellationToken = default)
{ {
+8 -3
View File
@@ -14,6 +14,9 @@ using Telegrator.MadiatorCore;
#pragma warning disable IDE0001 #pragma warning disable IDE0001
namespace Telegrator.Hosting 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 public class TelegramBotHostBuilder : ITelegramBotHostBuilder
{ {
private readonly HostApplicationBuilder _innerBuilder; private readonly HostApplicationBuilder _innerBuilder;
@@ -38,10 +41,12 @@ namespace Telegrator.Hosting
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="TelegramBotHostBuilder"/> class. /// Initializes a new instance of the <see cref="TelegramBotHostBuilder"/> class.
/// </summary> /// </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(); _settings = settings ?? new TelegramBotHostBuilderSettings();
_innerBuilder = new HostApplicationBuilder(settings?.ToApplicationBuilderSettings());
_handlers = new HostHandlersCollection(Services, _settings); _handlers = new HostHandlersCollection(Services, _settings);
Services.Configure<TelegramBotOptions>(Configuration.GetSection(nameof(TelegramBotOptions))); Services.Configure<TelegramBotOptions>(Configuration.GetSection(nameof(TelegramBotOptions)));
@@ -55,7 +60,7 @@ namespace Telegrator.Hosting
/// <returns></returns> /// <returns></returns>
public TelegramBotHost Build() public TelegramBotHost Build()
{ {
foreach (var preBuildRoutine in _handlers.PreBuilderRoutines) foreach (PreBuildingRoutine preBuildRoutine in _handlers.PreBuilderRoutines)
{ {
try try
{ {
@@ -5,7 +5,7 @@ using Telegrator.Configuration;
namespace Telegrator.Hosting namespace Telegrator.Hosting
{ {
/// <summary> /// <summary>
/// /// Settings os hosted Telegram bot
/// </summary> /// </summary>
public class TelegramBotHostBuilderSettings() : IHandlersCollectingOptions public class TelegramBotHostBuilderSettings() : IHandlersCollectingOptions
{ {
@@ -15,6 +15,7 @@
<EnableNETAnalyzers>True</EnableNETAnalyzers> <EnableNETAnalyzers>True</EnableNETAnalyzers>
<EnforceCodeStyleInBuild>True</EnforceCodeStyleInBuild> <EnforceCodeStyleInBuild>True</EnforceCodeStyleInBuild>
<PackageLicenseFile>LICENSE</PackageLicenseFile> <PackageLicenseFile>LICENSE</PackageLicenseFile>
<Version>1.0.1</Version>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
+61 -1
View File
@@ -2,9 +2,11 @@
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using System.Runtime.CompilerServices;
using Telegram.Bot; using Telegram.Bot;
using Telegram.Bot.Types; using Telegram.Bot.Types;
using Telegrator;
using Telegrator.Configuration; using Telegrator.Configuration;
using Telegrator.Hosting.Components; using Telegrator.Hosting.Components;
using Telegrator.Hosting.Configuration; using Telegrator.Hosting.Configuration;
@@ -14,14 +16,31 @@ using Telegrator.MadiatorCore;
namespace Telegrator.Hosting namespace Telegrator.Hosting
{ {
/// <summary>
/// Contains extensions for <see cref="IServiceCollection"/>
/// Provides method to configure <see cref="ITelegramBotHost"/>
/// </summary>
public static class ServicesCollectionExtensions 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 public static IServiceCollection Configure<TOptions>(this IServiceCollection services, IConfiguration configuration, ConfigureOptionsProxy<TOptions> optionsProxy) where TOptions : class
{ {
optionsProxy.Configure(services, configuration); optionsProxy.Configure(services, configuration);
return services; return services;
} }
/// <summary>
/// Registers <see cref="TelegramBotHost"/> default services
/// </summary>
/// <param name="services"></param>
/// <returns></returns>
public static IServiceCollection AddTelegramBotHostDefaults(this IServiceCollection services) public static IServiceCollection AddTelegramBotHostDefaults(this IServiceCollection services)
{ {
services.AddLogging(builder => builder.AddConsole()); services.AddLogging(builder => builder.AddConsole());
@@ -34,6 +53,11 @@ namespace Telegrator.Hosting
return services; 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) public static IServiceCollection AddTelegramReceiver(this IServiceCollection services)
{ {
services.AddHttpClient<ITelegramBotClient>("tgreceiver").RemoveAllLoggers().AddTypedClient(TypedTelegramBotClientFactory); services.AddHttpClient<ITelegramBotClient>("tgreceiver").RemoveAllLoggers().AddTypedClient(TypedTelegramBotClientFactory);
@@ -41,12 +65,26 @@ namespace Telegrator.Hosting
return services; 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) private static ITelegramBotClient TypedTelegramBotClientFactory(HttpClient httpClient, IServiceProvider provider)
=> new TelegramBotClient(provider.GetRequiredService<IOptions<TelegramBotClientOptions>>().Value, httpClient); => new TelegramBotClient(provider.GetRequiredService<IOptions<TelegramBotClientOptions>>().Value, httpClient);
} }
/// <summary>
/// Provides useful methods to adjust <see cref="ITelegramBotHost"/>
/// </summary>
public static class TelegramBotHostExtensions 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) public static ITelegramBotHost SetBotCommands(this ITelegramBotHost botHost)
{ {
ITelegramBotClient client = botHost.Services.GetRequiredService<ITelegramBotClient>(); ITelegramBotClient client = botHost.Services.GetRequiredService<ITelegramBotClient>();
@@ -55,4 +93,26 @@ namespace Telegrator.Hosting
return botHost; 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> /// </summary>
/// <param name="alliases">The command aliases to match against.</param> /// <param name="alliases">The command aliases to match against.</param>
public CommandAlliasAttribute(params string[] alliases) public CommandAlliasAttribute(params string[] alliases)
: base(new CommandAlliasFilter(alliases)) => Alliases = alliases; : base(new CommandAlliasFilter(alliases)) => Alliases = alliases.Select(c => c.TrimStart('/')).ToArray();
/// <summary> /// <summary>
/// Gets the filtering target (Message) from the update. /// Gets the filtering target (Message) from the update.
@@ -10,9 +10,18 @@ namespace Telegrator.Annotations
{ } { }
/// <summary> /// <summary>
/// Attribute for filtering messages in reply chain. /// Attribute for checking message's reply chain.
/// </summary> /// </summary>
public class MessageRepliedAttribute(int replyDepth = 1) public class HasReplyAttribute(int replyDepth = 1)
: MessageFilterAttribute(new MessageRepliedFilter(replyDepth)) : 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; using Telegrator.Filters;
namespace Telegrator.Annotations namespace Telegrator.Annotations
@@ -12,7 +13,8 @@ namespace Telegrator.Annotations
/// <summary> /// <summary>
/// Creates new instance of <see cref="WelcomeAttribute"/> /// Creates new instance of <see cref="WelcomeAttribute"/>
/// </summary> /// </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; } public UpdateType Type { get; private set; }
/// <summary> /// <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> /// </summary>
public int Concurrency { get; set; } public int Importance { get; set; }
/// <summary> /// <summary>
/// Gets or sets priority of this <see cref="UpdateHandlerBase"/> in same type handlers pool /// Gets or sets priority of this <see cref="UpdateHandlerBase"/> in same type handlers pool
@@ -40,11 +40,11 @@ namespace Telegrator.Attributes.Components
/// </summary> /// </summary>
/// <param name="expectingHandlerType"></param> /// <param name="expectingHandlerType"></param>
/// <param name="updateType"></param> /// <param name="updateType"></param>
/// <param name="concurrency"></param> /// <param name="importance"></param>
/// <exception cref="ArgumentNullException"></exception> /// <exception cref="ArgumentNullException"></exception>
/// <exception cref="ArgumentException"></exception> /// <exception cref="ArgumentException"></exception>
/// <exception cref="Exception"></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) if (expectingHandlerType == null)
throw new ArgumentNullException(nameof(expectingHandlerType)); throw new ArgumentNullException(nameof(expectingHandlerType));
@@ -57,11 +57,11 @@ namespace Telegrator.Attributes.Components
ExpectingHandlerType = expectingHandlerType; ExpectingHandlerType = expectingHandlerType;
Type = updateType; Type = updateType;
Concurrency = concurrency; Importance = importance;
} }
/// <summary> /// <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> /// </summary>
/// <returns></returns> /// <returns></returns>
public DescriptorIndexer GetIndexer() public DescriptorIndexer GetIndexer()
@@ -6,13 +6,13 @@ namespace Telegrator.Attributes
{ {
/// <summary> /// <summary>
/// Abstract base attribute for marking update handler classes. /// 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> /// </summary>
/// <typeparam name="T">The type of the update handler that this attribute is applied to.</typeparam> /// <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="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> /// <param name="importance">The importance level for this handler (default: 0 for unlimited).</param>
public abstract class UpdateHandlerAttribute<T>(UpdateType updateType, int concurrency = 0) public abstract class UpdateHandlerAttribute<T>(UpdateType updateType, int importance = 0)
: UpdateHandlerAttributeBase([typeof(T)], updateType, concurrency) where T : UpdateHandlerBase : 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) foreach (IFilter<T> filter in filters)
{ {
if (!filter.CanPass(context)) 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; return false;
}
context.CompletedFilters.Add(filter); context.CompletedFilters.Add(filter);
} }
@@ -47,7 +47,12 @@ namespace Telegrator.Filters.Components
{ {
FilterExecutionContext<T> context = updateContext.CreateChild((T)filterringTarget); FilterExecutionContext<T> context = updateContext.CreateChild((T)filterringTarget);
if (!filter.CanPass(context)) 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; return false;
}
context.CompletedFilters.Add(filter); context.CompletedFilters.Add(filter);
return true; return true;
@@ -37,7 +37,12 @@
foreach (IFilter<T> filter in Filters) foreach (IFilter<T> filter in Filters)
{ {
if (!filter.CanPass(context)) 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; return false;
}
context.CompletedFilters.Add(filter); context.CompletedFilters.Add(filter);
} }
+1 -3
View File
@@ -1,6 +1,4 @@
using System.Reflection; using Telegrator.Filters.Components;
using Telegrator;
using Telegrator.Filters.Components;
namespace Telegrator.Filters 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> /// <param name="mention">The username to check for in the mention.</param>
public MentionedFilter(string mention) public MentionedFilter(string mention)
{ {
Mention = mention; Mention = mention.TrimStart('@');
} }
/// <summary> /// <summary>
+1 -1
View File
@@ -18,7 +18,7 @@ namespace Telegrator.Filters
/// <inheritdoc/> /// <inheritdoc/>
public override bool CanPass(FilterExecutionContext<Message> context) 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; Target = repliedFilter?.Reply ?? context.Input;
return CanPassNext(context); return CanPassNext(context);
} }
+45 -5
View File
@@ -4,11 +4,51 @@ using Telegrator.Filters.Components;
namespace Telegrator.Filters namespace Telegrator.Filters
{ {
/// <summary> /// <summary>
/// Abstract base class for filters that operate on replied messages. /// Filter that checks if message has appropriate reply chain.
/// Provides functionality to traverse reply chains and access replied message content. /// DOES NOT SHiFT MESSAGE FILTERS TARGET
/// </summary> /// </summary>
/// <param name="replyDepth">The depth of reply chain to traverse (default: 1).</param> /// <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> /// <summary>
/// Gets the replied message at the specified depth in the reply chain. /// Gets the replied message at the specified depth in the reply chain.
@@ -44,7 +84,7 @@ namespace Telegrator.Filters
/// <summary> /// <summary>
/// Filter that checks if the replied message was sent by the bot itself. /// 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> /// </summary>
public class MeRepliedFilter : Filter<Message> 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> /// <returns>True if the replied message was sent by the bot; otherwise, false.</returns>
public override bool CanPass(FilterExecutionContext<Message> context) 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; return context.BotInfo.User == repliedFilter.Reply.From;
} }
} }
+1 -1
View File
@@ -32,7 +32,7 @@ namespace Telegrator.Filters
if (!base.CanPass(context)) if (!base.CanPass(context))
return false; return false;
Message = context.Update.Message!; Message = context.Input!;
if (Message is not { Id: > 0 }) if (Message is not { Id: > 0 })
return false; return false;
+2 -2
View File
@@ -10,8 +10,8 @@ namespace Telegrator.Handlers
/// Attribute that marks a handler to process any type of update. /// Attribute that marks a handler to process any type of update.
/// This handler will be triggered for all incoming updates regardless of their type. /// This handler will be triggered for all incoming updates regardless of their type.
/// </summary> /// </summary>
/// <param name="concurrency">The maximum number of concurrent executions allowed (default: -1 for unlimited).</param> /// <param name="importance"></param>
public class AnyUpdateHandlerAttribute(int concurrency = -1) : UpdateHandlerAttribute<AnyUpdateHandler>(UpdateType.Unknown, concurrency) public class AnyUpdateHandlerAttribute(int importance = -1) : UpdateHandlerAttribute<AnyUpdateHandler>(UpdateType.Unknown, importance)
{ {
/// <summary> /// <summary>
/// Always returns true, allowing any update to pass through this filter. /// 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> /// <returns>The builder instance.</returns>
public void SetConcurreny(int concurrency) public void SetConcurreny(int concurrency)
{ {
Indexer = Indexer.UpdateConcurrency(concurrency); Indexer = Indexer.UpdateImportance(concurrency);
} }
/// <summary> /// <summary>
+3 -3
View File
@@ -10,15 +10,15 @@ namespace Telegrator.Handlers
/// Attribute that marks a handler to process callback query updates. /// 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. /// This handler will be triggered when users interact with inline keyboards or other callback mechanisms.
/// </summary> /// </summary>
/// <param name="concurrency">The maximum number of concurrent executions allowed (default: 0 for unlimited).</param> /// <param name="importance"></param>
public sealed class CallbackQueryHandlerAttribute(int concurrency = 0) : UpdateHandlerAttribute<CallbackQueryHandler>(UpdateType.CallbackQuery, concurrency) public sealed class CallbackQueryHandlerAttribute(int importance = 0) : UpdateHandlerAttribute<CallbackQueryHandler>(UpdateType.CallbackQuery, importance)
{ {
/// <summary> /// <summary>
/// Always returns true, allowing any callback query update to pass through this filter. /// Always returns true, allowing any callback query update to pass through this filter.
/// </summary> /// </summary>
/// <param name="context">The filter execution context (unused).</param> /// <param name="context">The filter execution context (unused).</param>
/// <returns>Always returns true to allow any callback query update.</returns> /// <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> /// <summary>
+1 -2
View File
@@ -9,8 +9,7 @@ namespace Telegrator.Handlers
/// Attribute that marks a handler to process command messages. /// Attribute that marks a handler to process command messages.
/// This handler will be triggered when users send bot commands (messages starting with '/'). /// This handler will be triggered when users send bot commands (messages starting with '/').
/// </summary> /// </summary>
/// <param name="concurrency">The maximum number of concurrent executions allowed (default: 1).</param> public class CommandHandlerAttribute(int importance = 1) : UpdateHandlerAttribute<CommandHandler>(UpdateType.Message, importance)
public class CommandHandlerAttribute(int concurrency = 1) : UpdateHandlerAttribute<CommandHandler>(UpdateType.Message, concurrency)
{ {
/// <summary> /// <summary>
/// Gets the command that was extracted from the message (without the '/' prefix and bot username). /// 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. /// Attribute that marks a handler to process message updates.
/// This handler will be triggered when users send messages in chats. /// This handler will be triggered when users send messages in chats.
/// </summary> /// </summary>
/// <param name="concurrency">The maximum number of concurrent executions allowed (default: 0 for unlimited).</param> public class MessageHandlerAttribute(int importance = 0) : UpdateHandlerAttribute<MessageHandler>(UpdateType.Message, importance)
public class MessageHandlerAttribute(int concurrency = 0) : UpdateHandlerAttribute<MessageHandler>(UpdateType.Message, concurrency)
{ {
/// <summary> /// <summary>
/// Checks if the update contains a valid message. /// 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 Telegram.Bot.Types;
using Telegrator.Filters.Components; using Telegrator.Filters.Components;
using Telegrator.Handlers.Components; using Telegrator.Handlers.Components;
using Telegrator.MadiatorCore;
namespace Telegrator.MadiatorCore.Descriptors namespace Telegrator.MadiatorCore.Descriptors
{ {
@@ -111,5 +110,9 @@ namespace Telegrator.MadiatorCore.Descriptors
.ConfigureAwait(false); .ConfigureAwait(false);
} }
} }
/// <inheritdoc/>
public override string ToString()
=> DisplayString ?? HandlerInstance.GetType().Name;
} }
} }
@@ -46,16 +46,24 @@ namespace Telegrator.MadiatorCore.Descriptors
if (UpdateValidator != null) if (UpdateValidator != null)
{ {
if (!UpdateValidator.CanPass(filterContext)) if (!UpdateValidator.CanPass(filterContext))
{
LeveledDebug.FilterWriteLine("(E) UpdateValidator filter of {0} for Update ({2}) didnt pass!", filterContext.Data["handler_name"]);
return false; return false;
}
//LeveledDebug.FilterWriteLine("UpdateValidator of {0} for Update ({2}) passed", filterContext.Data["handler_name"]);
filterContext.CompletedFilters.Add(UpdateValidator); filterContext.CompletedFilters.Add(UpdateValidator);
} }
if (StateKeeperValidator != null) if (StateKeeperValidator != null)
{ {
if (!StateKeeperValidator.CanPass(filterContext)) if (!StateKeeperValidator.CanPass(filterContext))
{
LeveledDebug.FilterWriteLine("(E) StateKeeperValidator filter of {0} for Update ({2}) didnt pass!", filterContext.Data["handler_name"]);
return false; return false;
}
//LeveledDebug.FilterWriteLine("StateKeeperValidator of {0} for Update ({2}) passed", filterContext.Data["handler_name"]);
filterContext.CompletedFilters.Add(StateKeeperValidator); filterContext.CompletedFilters.Add(StateKeeperValidator);
} }
@@ -64,8 +72,14 @@ namespace Telegrator.MadiatorCore.Descriptors
foreach (IFilter<Update> filter in UpdateFilters) foreach (IFilter<Update> filter in UpdateFilters)
{ {
if (!filter.CanPass(filterContext)) 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); filterContext.CompletedFilters.Add(filter);
} }
} }
@@ -3,9 +3,9 @@
namespace Telegrator.MadiatorCore.Descriptors namespace Telegrator.MadiatorCore.Descriptors
{ {
/// <summary> /// <summary>
/// Represents an indexer for handler descriptors, containing concurrency and priority information. /// Represents an indexer for handler descriptors, containing importance and priority information.
/// </summary> /// </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> /// <summary>
/// Index of this descriptor when it was added to router /// Index of this descriptor when it was added to router
@@ -15,7 +15,7 @@ namespace Telegrator.MadiatorCore.Descriptors
/// <summary> /// <summary>
/// Of this handlert type /// Of this handlert type
/// </summary> /// </summary>
public readonly int Importance = concurrency; public readonly int Importance = importance;
/// <summary> /// <summary>
/// The priority of the handler. /// The priority of the handler.
@@ -28,7 +28,7 @@ namespace Telegrator.MadiatorCore.Descriptors
/// <param name="routerIndex"></param> /// <param name="routerIndex"></param>
/// <param name="pollingHandler">The handler attribute.</param> /// <param name="pollingHandler">The handler attribute.</param>
public DescriptorIndexer(int routerIndex, UpdateHandlerAttributeBase pollingHandler) public DescriptorIndexer(int routerIndex, UpdateHandlerAttributeBase pollingHandler)
: this(routerIndex, pollingHandler.Concurrency, pollingHandler.Priority) { } : this(routerIndex, pollingHandler.Importance, pollingHandler.Priority) { }
/// <summary> /// <summary>
/// Returns a new <see cref="DescriptorIndexer"/> with updated priority. /// Returns a new <see cref="DescriptorIndexer"/> with updated priority.
@@ -39,12 +39,12 @@ namespace Telegrator.MadiatorCore.Descriptors
=> new DescriptorIndexer(RouterIndex, Importance, priority); => new DescriptorIndexer(RouterIndex, Importance, priority);
/// <summary> /// <summary>
/// Returns a new <see cref="DescriptorIndexer"/> with updated concurrency. /// Returns a new <see cref="DescriptorIndexer"/> with updated importance.
/// </summary> /// </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> /// <returns>A new <see cref="DescriptorIndexer"/> instance.</returns>
public DescriptorIndexer UpdateConcurrency(int concurrency) public DescriptorIndexer UpdateImportance(int importance)
=> new DescriptorIndexer(RouterIndex, concurrency, Priority); => new DescriptorIndexer(RouterIndex, importance, Priority);
/// <summary> /// <summary>
/// Returns a new <see cref="DescriptorIndexer"/> with updated RouterIndex. /// Returns a new <see cref="DescriptorIndexer"/> with updated RouterIndex.
@@ -79,7 +79,7 @@ namespace Telegrator.MadiatorCore.Descriptors
/// <summary> /// <summary>
/// Returns a string representation of the indexer. /// Returns a string representation of the indexer.
/// </summary> /// </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() public override string ToString()
{ {
return string.Format("(I:{0}, C:{1}, P:{2})", RouterIndex, Importance, Priority); return string.Format("(I:{0}, C:{1}, P:{2})", RouterIndex, Importance, Priority);
@@ -139,6 +139,7 @@ namespace Telegrator.MadiatorCore.Descriptors
UpdateType = handlerAttribute.Type; UpdateType = handlerAttribute.Type;
Indexer = handlerAttribute.GetIndexer(); Indexer = handlerAttribute.GetIndexer();
Filters = new DescriptorFiltersSet(handlerAttribute, stateKeeperAttribute, filters); Filters = new DescriptorFiltersSet(handlerAttribute, stateKeeperAttribute, filters);
DisplayString = HandlerInspector.GetDisplayName(handlerType);
} }
/// <summary> /// <summary>
@@ -402,5 +403,9 @@ namespace Telegrator.MadiatorCore.Descriptors
ServiceKey = serviceKey ?? throw new ArgumentNullException(nameof(serviceKey)); ServiceKey = serviceKey ?? throw new ArgumentNullException(nameof(serviceKey));
InstanceFactory = instanceFactory ?? throw new ArgumentNullException(nameof(instanceFactory)); InstanceFactory = instanceFactory ?? throw new ArgumentNullException(nameof(instanceFactory));
} }
/// <inheritdoc/>
public override string ToString()
=> DisplayString ?? HandlerType.Name;
} }
} }
@@ -1,9 +1,7 @@
using System.Collections; using System.Collections;
using Telegram.Bot.Types; using Telegram.Bot.Types;
using Telegram.Bot.Types.Enums; using Telegram.Bot.Types.Enums;
using Telegrator;
using Telegrator.Configuration; using Telegrator.Configuration;
using Telegrator.MadiatorCore;
namespace Telegrator.MadiatorCore.Descriptors namespace Telegrator.MadiatorCore.Descriptors
{ {
@@ -29,6 +27,11 @@ namespace Telegrator.MadiatorCore.Descriptors
/// </summary> /// </summary>
public UpdateType HandlingType => _handlingType; public UpdateType HandlingType => _handlingType;
/// <summary>
/// Gets count of registered handlers in list
/// </summary>
public int Count => _innerCollection.Count;
/// <summary> /// <summary>
/// Gets or sets the <see cref="HandlerDescriptor"/> at the specified index. /// Gets or sets the <see cref="HandlerDescriptor"/> at the specified index.
/// </summary> /// </summary>
@@ -1,4 +1,5 @@
using System.Reflection; using System.ComponentModel;
using System.Reflection;
using Telegram.Bot.Types; using Telegram.Bot.Types;
using Telegram.Bot.Types.Enums; using Telegram.Bot.Types.Enums;
using Telegrator.Attributes.Components; using Telegrator.Attributes.Components;
@@ -11,6 +12,16 @@ namespace Telegrator.MadiatorCore.Descriptors
/// </summary> /// </summary>
public static class HandlerInspector 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> /// <summary>
/// Gets the handler attribute from the specified member info. /// Gets the handler attribute from the specified member info.
/// </summary> /// </summary>
+49 -7
View File
@@ -1,7 +1,9 @@
using Telegram.Bot; using System;
using System.Text;
using Telegram.Bot;
using Telegram.Bot.Polling; using Telegram.Bot.Polling;
using Telegram.Bot.Types; using Telegram.Bot.Types;
using Telegrator.Polling; using Telegram.Bot.Types.Enums;
using Telegrator.Configuration; using Telegrator.Configuration;
using Telegrator.Handlers.Components; using Telegrator.Handlers.Components;
using Telegrator.MadiatorCore; using Telegrator.MadiatorCore;
@@ -92,6 +94,7 @@ namespace Telegrator.Polling
/// <returns>A task representing the asynchronous error handling operation.</returns> /// <returns>A task representing the asynchronous error handling operation.</returns>
public virtual Task HandleErrorAsync(ITelegramBotClient botClient, Exception exception, HandleErrorSource source, CancellationToken cancellationToken) 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); ExceptionHandler?.HandleException(botClient, exception, source, cancellationToken);
return Task.CompletedTask; return Task.CompletedTask;
} }
@@ -105,21 +108,60 @@ namespace Telegrator.Polling
/// <returns>A task representing the asynchronous update handling operation.</returns> /// <returns>A task representing the asynchronous update handling operation.</returns>
public virtual Task HandleUpdateAsync(ITelegramBotClient botClient, Update update, CancellationToken cancellationToken) 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 // Queuing handlers for execution
foreach (DescribedHandlerInfo handler in GetHandlers(botClient, update, cancellationToken)) foreach (DescribedHandlerInfo handler in GetHandlers(botClient, update, cancellationToken))
HandlersPool.Enqueue(handler); HandlersPool.Enqueue(handler);
LeveledDebug.RouterWriteLine("Receiving Update ({0}) finished", update.Id);
return Task.CompletedTask; return Task.CompletedTask;
} }
private IEnumerable<DescribedHandlerInfo> GetHandlers(ITelegramBotClient botClient, Update update, CancellationToken cancellationToken) private IEnumerable<DescribedHandlerInfo> GetHandlers(ITelegramBotClient botClient, Update update, CancellationToken cancellationToken)
{ {
// Getting handlers in update awaiting pool try
IEnumerable<DescribedHandlerInfo> handlers = AwaitingProvider.GetHandlers(this, botClient, update, cancellationToken); {
if (handlers.Any() && Options.ExclusiveAwaitingHandlerRouting) // Getting handlers in update awaiting pool
return handlers; IEnumerable<DescribedHandlerInfo> 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;
}
}
} }
} }
} }
+34 -13
View File
@@ -1,4 +1,5 @@
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Reflection; using System.Reflection;
using Telegram.Bot; using Telegram.Bot;
using Telegram.Bot.Types; using Telegram.Bot.Types;
@@ -79,16 +80,23 @@ namespace Telegrator.Providers
/// <returns>A collection of described handler information for the update</returns> /// <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) 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(update.Type, out HandlerDescriptorList? descriptors))
{ {
if (!HandlersDictionary.TryGetValue(UpdateType.Unknown, out descriptors)) LeveledDebug.ProviderWriteLine("No registered, providing Any");
return []; HandlersDictionary.TryGetValue(UpdateType.Unknown, out descriptors);
} }
if (descriptors == null || !descriptors.Any()) if (descriptors == null || descriptors.Count == 0)
{
LeveledDebug.ProviderWriteLine("No handlers provided");
return []; 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> /// <summary>
@@ -103,16 +111,24 @@ namespace Telegrator.Providers
/// <returns>A collection of described handler information</returns> /// <returns>A collection of described handler information</returns>
public virtual IEnumerable<DescribedHandlerInfo> DescribeDescriptors(HandlerDescriptorList descriptors, IUpdateRouter updateRouter, ITelegramBotClient client, Update update, CancellationToken cancellationToken = default) public virtual IEnumerable<DescribedHandlerInfo> DescribeDescriptors(HandlerDescriptorList descriptors, IUpdateRouter updateRouter, ITelegramBotClient client, Update update, CancellationToken cancellationToken = default)
{ {
foreach (HandlerDescriptor descriptor in descriptors.Reverse()) try
{ {
cancellationToken.ThrowIfCancellationRequested(); LeveledDebug.ProviderWriteLine("Describing descriptors of descriptorsList.HandlingType.{0} for Update ({1})", descriptors.HandlingType, update.Id);
DescribedHandlerInfo? describedHandler = DescribeHandler(descriptor, updateRouter, client, update, cancellationToken); foreach (HandlerDescriptor descriptor in descriptors.Reverse())
if (describedHandler == null) {
continue; cancellationToken.ThrowIfCancellationRequested();
DescribedHandlerInfo? describedHandler = DescribeHandler(descriptor, updateRouter, client, update, cancellationToken);
if (describedHandler == null)
continue;
yield return describedHandler; yield return describedHandler;
if (Options.ExecuteOnlyFirstFoundHanlder) if (Options.ExecuteOnlyFirstFoundHanlder)
break; 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) public virtual DescribedHandlerInfo? DescribeHandler(HandlerDescriptor descriptor, IUpdateRouter updateRouter, ITelegramBotClient client, Update update, CancellationToken cancellationToken = default)
{ {
cancellationToken.ThrowIfCancellationRequested(); 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)) if (!descriptor.Filters.Validate(filterContext))
return null; return null;
+1
View File
@@ -17,6 +17,7 @@
<EnableNETAnalyzers>True</EnableNETAnalyzers> <EnableNETAnalyzers>True</EnableNETAnalyzers>
<EnforceCodeStyleInBuild>True</EnforceCodeStyleInBuild> <EnforceCodeStyleInBuild>True</EnforceCodeStyleInBuild>
<PackageLicenseFile>LICENSE</PackageLicenseFile> <PackageLicenseFile>LICENSE</PackageLicenseFile>
<Version>1.0.1</Version>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
+25
View File
@@ -1,6 +1,7 @@
using System.Collections; using System.Collections;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.Reflection; using System.Reflection;
using System.Runtime.CompilerServices;
using Telegram.Bot.Types; using Telegram.Bot.Types;
using Telegram.Bot.Types.Enums; using Telegram.Bot.Types.Enums;
using Telegram.Bot.Types.Payments; 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));
}
} }