diff --git a/src/Telegrator.Hosting.Web/Hosting.Web/TelegramBotWebHost.cs b/src/Telegrator.Hosting.Web/Hosting.Web/TelegramBotWebHost.cs index 3517aa2..f5abd80 100644 --- a/src/Telegrator.Hosting.Web/Hosting.Web/TelegramBotWebHost.cs +++ b/src/Telegrator.Hosting.Web/Hosting.Web/TelegramBotWebHost.cs @@ -7,154 +7,153 @@ using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Telegrator.Core; -namespace Telegrator.Hosting.Web +namespace Telegrator.Hosting.Web; + +/// +/// Represents a web hosted telegram bot +/// +public class TelegramBotWebHost : IHost, IApplicationBuilder, IEndpointRouteBuilder, IAsyncDisposable { + private readonly WebApplication _innerApp; + private readonly IUpdateRouter _updateRouter; + private readonly ILogger _logger; + + private bool _disposed; + + /// + public IServiceProvider Services => _innerApp.Services; + + /// + public IUpdateRouter UpdateRouter => _updateRouter; + + /// + public ICollection DataSources => ((IEndpointRouteBuilder)_innerApp).DataSources; + /// - /// Represents a web hosted telegram bot + /// Allows consumers to be notified of application lifetime events. /// - public class TelegramBotWebHost : IHost, IApplicationBuilder, IEndpointRouteBuilder, IAsyncDisposable + public IHostApplicationLifetime Lifetime => _innerApp.Lifetime; + + /// + /// This application's logger + /// + public ILogger Logger => _logger; + + // Private interface fields + IServiceProvider IEndpointRouteBuilder.ServiceProvider => Services; + IServiceProvider IApplicationBuilder.ApplicationServices { get => Services; set => throw new NotImplementedException(); } + IFeatureCollection IApplicationBuilder.ServerFeatures => ((IApplicationBuilder)_innerApp).ServerFeatures; + IDictionary IApplicationBuilder.Properties => ((IApplicationBuilder)_innerApp).Properties; + + /// + /// Initializes a new instance of the class. + /// + /// The proxied instance of host builder. + public TelegramBotWebHost(WebApplicationBuilder webApplicationBuilder) { - private readonly WebApplication _innerApp; - private readonly IUpdateRouter _updateRouter; - private readonly ILogger _logger; + // Building proxy application + _innerApp = webApplicationBuilder.Build(); - private bool _disposed; + // Reruesting services for this host + _updateRouter = Services.GetRequiredService(); + _logger = Services.GetRequiredService>(); + } - /// - public IServiceProvider Services => _innerApp.Services; + /// + /// Creates new with default services and webhook update receiving scheme + /// + /// + public static TelegramBotWebHostBuilder CreateBuilder(WebApplicationOptions settings) + { + ArgumentNullException.ThrowIfNull(settings, nameof(settings)); + WebApplicationBuilder innerApp = WebApplication.CreateBuilder(settings); + TelegramBotWebHostBuilder builder = new TelegramBotWebHostBuilder(innerApp, settings); - /// - public IUpdateRouter UpdateRouter => _updateRouter; + builder.Services.AddTelegramBotHostDefaults(); + builder.Services.AddTelegramWebhook(); + return builder; + } - /// - public ICollection DataSources => ((IEndpointRouteBuilder)_innerApp).DataSources; + /// + /// Creates new SLIM with default services and webhook update receiving scheme + /// + /// + public static TelegramBotWebHostBuilder CreateSlimBuilder(WebApplicationOptions settings) + { + ArgumentNullException.ThrowIfNull(settings, nameof(settings)); + WebApplicationBuilder innerApp = WebApplication.CreateSlimBuilder(settings); + TelegramBotWebHostBuilder builder = new TelegramBotWebHostBuilder(innerApp, settings); - /// - /// Allows consumers to be notified of application lifetime events. - /// - public IHostApplicationLifetime Lifetime => _innerApp.Lifetime; + builder.Services.AddTelegramBotHostDefaults(); + builder.Services.AddTelegramWebhook(); + return builder; + } - /// - /// This application's logger - /// - public ILogger Logger => _logger; + /// + /// Creates new EMPTY WITHOUT any services or update receiving schemes + /// + /// + public static TelegramBotWebHostBuilder CreateEmptyBuilder(WebApplicationOptions settings) + { + ArgumentNullException.ThrowIfNull(settings, nameof(settings)); + WebApplicationBuilder innerApp = WebApplication.CreateEmptyBuilder(settings); + return new TelegramBotWebHostBuilder(innerApp, settings); + } - // Private interface fields - IServiceProvider IEndpointRouteBuilder.ServiceProvider => Services; - IServiceProvider IApplicationBuilder.ApplicationServices { get => Services; set => throw new NotImplementedException(); } - IFeatureCollection IApplicationBuilder.ServerFeatures => ((IApplicationBuilder)_innerApp).ServerFeatures; - IDictionary IApplicationBuilder.Properties => ((IApplicationBuilder)_innerApp).Properties; + /// + public async Task StartAsync(CancellationToken cancellationToken = default) + { + await _innerApp.StartAsync(cancellationToken); + } - /// - /// Initializes a new instance of the class. - /// - /// The proxied instance of host builder. - public TelegramBotWebHost(WebApplicationBuilder webApplicationBuilder) - { - // Building proxy application - _innerApp = webApplicationBuilder.Build(); + /// + public async Task StopAsync(CancellationToken cancellationToken = default) + { + await _innerApp.StopAsync(cancellationToken); + } - // Reruesting services for this host - _updateRouter = Services.GetRequiredService(); - _logger = Services.GetRequiredService>(); - } + /// + public IApplicationBuilder CreateApplicationBuilder() + => ((IEndpointRouteBuilder)_innerApp).CreateApplicationBuilder(); - /// - /// Creates new with default services and webhook update receiving scheme - /// - /// - public static TelegramBotWebHostBuilder CreateBuilder(WebApplicationOptions settings) - { - ArgumentNullException.ThrowIfNull(settings, nameof(settings)); - WebApplicationBuilder innerApp = WebApplication.CreateBuilder(settings); - TelegramBotWebHostBuilder builder = new TelegramBotWebHostBuilder(innerApp, settings); + /// + public IApplicationBuilder Use(Func middleware) + => _innerApp.Use(middleware); - builder.Services.AddTelegramBotHostDefaults(); - builder.Services.AddTelegramWebhook(); - return builder; - } + /// + public IApplicationBuilder New() + => ((IApplicationBuilder)_innerApp).New(); - /// - /// Creates new SLIM with default services and webhook update receiving scheme - /// - /// - public static TelegramBotWebHostBuilder CreateSlimBuilder(WebApplicationOptions settings) - { - ArgumentNullException.ThrowIfNull(settings, nameof(settings)); - WebApplicationBuilder innerApp = WebApplication.CreateSlimBuilder(settings); - TelegramBotWebHostBuilder builder = new TelegramBotWebHostBuilder(innerApp, settings); + /// + public RequestDelegate Build() + => ((IApplicationBuilder)_innerApp).Build(); - builder.Services.AddTelegramBotHostDefaults(); - builder.Services.AddTelegramWebhook(); - return builder; - } + /// + /// Disposes the host. + /// + public async ValueTask DisposeAsync() + { + if (_disposed) + return; - /// - /// Creates new EMPTY WITHOUT any services or update receiving schemes - /// - /// - public static TelegramBotWebHostBuilder CreateEmptyBuilder(WebApplicationOptions settings) - { - ArgumentNullException.ThrowIfNull(settings, nameof(settings)); - WebApplicationBuilder innerApp = WebApplication.CreateEmptyBuilder(settings); - return new TelegramBotWebHostBuilder(innerApp, settings); - } + await _innerApp.DisposeAsync(); - /// - public async Task StartAsync(CancellationToken cancellationToken = default) - { - await _innerApp.StartAsync(cancellationToken); - } + GC.SuppressFinalize(this); + _disposed = true; + } - /// - public async Task StopAsync(CancellationToken cancellationToken = default) - { - await _innerApp.StopAsync(cancellationToken); - } + /// + /// Disposes the host. + /// + public void Dispose() + { + if (_disposed) + return; - /// - public IApplicationBuilder CreateApplicationBuilder() - => ((IEndpointRouteBuilder)_innerApp).CreateApplicationBuilder(); + ValueTask disposeTask = _innerApp.DisposeAsync(); + disposeTask.AsTask().Wait(); - /// - public IApplicationBuilder Use(Func middleware) - => _innerApp.Use(middleware); - - /// - public IApplicationBuilder New() - => ((IApplicationBuilder)_innerApp).New(); - - /// - public RequestDelegate Build() - => ((IApplicationBuilder)_innerApp).Build(); - - /// - /// Disposes the host. - /// - public async ValueTask DisposeAsync() - { - if (_disposed) - return; - - await _innerApp.DisposeAsync(); - - GC.SuppressFinalize(this); - _disposed = true; - } - - /// - /// Disposes the host. - /// - public void Dispose() - { - if (_disposed) - return; - - ValueTask disposeTask = _innerApp.DisposeAsync(); - disposeTask.AsTask().Wait(); - - GC.SuppressFinalize(this); - _disposed = true; - } + GC.SuppressFinalize(this); + _disposed = true; } } diff --git a/src/Telegrator.Hosting.Web/Hosting.Web/TelegramBotWebHostBuilder.cs b/src/Telegrator.Hosting.Web/Hosting.Web/TelegramBotWebHostBuilder.cs index 16f8de7..c561bcd 100644 --- a/src/Telegrator.Hosting.Web/Hosting.Web/TelegramBotWebHostBuilder.cs +++ b/src/Telegrator.Hosting.Web/Hosting.Web/TelegramBotWebHostBuilder.cs @@ -7,109 +7,108 @@ using Microsoft.Extensions.Logging; using Telegrator.Core; #pragma warning disable IDE0001 -namespace Telegrator.Hosting.Web +namespace Telegrator.Hosting.Web; + +/// +/// Represents a web hosted telegram bots and services builder that helps manage configuration, logging, lifetime, and more. +/// +public class TelegramBotWebHostBuilder : IHostApplicationBuilder, ICollectingProvider { + private readonly WebApplicationBuilder _innerBuilder; + private readonly WebApplicationOptions _settings; + internal IHandlersCollection _handlers = null!; + + /// + public IHandlersCollection Handlers => _handlers; + + /// + public IConfigurationManager Configuration => _innerBuilder.Configuration; + + /// + public ILoggingBuilder Logging => _innerBuilder.Logging; + + /// + public IServiceCollection Services => _innerBuilder.Services; + + /// + public IHostEnvironment Environment => _innerBuilder.Environment; + + /// + public IDictionary Properties => ((IHostApplicationBuilder)_innerBuilder).Properties; + + /// + public IMetricsBuilder Metrics => _innerBuilder.Metrics; + /// - /// Represents a web hosted telegram bots and services builder that helps manage configuration, logging, lifetime, and more. + /// Initializes a new instance of the class. /// - public class TelegramBotWebHostBuilder : IHostApplicationBuilder, ICollectingProvider + /// + /// + public TelegramBotWebHostBuilder(WebApplicationBuilder webApplicationBuilder, WebApplicationOptions? settings = null) { - private readonly WebApplicationBuilder _innerBuilder; - private readonly WebApplicationOptions _settings; - internal IHandlersCollection _handlers = null!; + _innerBuilder = webApplicationBuilder ?? throw new ArgumentNullException(nameof(webApplicationBuilder)); + _settings = settings ?? throw new ArgumentNullException(nameof(settings)); - /// - public IHandlersCollection Handlers => _handlers; + this.AddTelegratorWeb(); + } - /// - public IConfigurationManager Configuration => _innerBuilder.Configuration; + /// + /// Initializes a new instance of the class. + /// + /// + /// + /// + public TelegramBotWebHostBuilder(WebApplicationBuilder webApplicationBuilder, TelegratorOptions? options, WebApplicationOptions? settings) + { + _innerBuilder = webApplicationBuilder ?? throw new ArgumentNullException(nameof(webApplicationBuilder)); + _settings = settings ?? throw new ArgumentNullException(nameof(settings)); - /// - public ILoggingBuilder Logging => _innerBuilder.Logging; + this.AddTelegratorWeb(options, null); + } - /// - public IServiceCollection Services => _innerBuilder.Services; + /// + /// Initializes a new instance of the class. + /// + /// + /// + /// + public TelegramBotWebHostBuilder(WebApplicationBuilder webApplicationBuilder, IHandlersCollection handlers, WebApplicationOptions settings) + { + _innerBuilder = webApplicationBuilder ?? throw new ArgumentNullException(nameof(webApplicationBuilder)); + _settings = settings ?? throw new ArgumentNullException(nameof(settings)); - /// - public IHostEnvironment Environment => _innerBuilder.Environment; + this.AddTelegratorWeb(null, handlers); + } - /// - public IDictionary Properties => ((IHostApplicationBuilder)_innerBuilder).Properties; + /// + /// Initializes a new instance of the class. + /// + /// + /// + /// + /// + public TelegramBotWebHostBuilder(WebApplicationBuilder webApplicationBuilder, IHandlersCollection handlers, TelegratorOptions? options, WebApplicationOptions settings) + { + _innerBuilder = webApplicationBuilder ?? throw new ArgumentNullException(nameof(webApplicationBuilder)); + _settings = settings ?? throw new ArgumentNullException(nameof(settings)); - /// - public IMetricsBuilder Metrics => _innerBuilder.Metrics; + this.AddTelegratorWeb(options, handlers); + } - /// - /// Initializes a new instance of the class. - /// - /// - /// - public TelegramBotWebHostBuilder(WebApplicationBuilder webApplicationBuilder, WebApplicationOptions? settings = null) - { - _innerBuilder = webApplicationBuilder ?? throw new ArgumentNullException(nameof(webApplicationBuilder)); - _settings = settings ?? throw new ArgumentNullException(nameof(settings)); + /// + /// Builds the host. + /// + /// + public TelegramBotWebHost Build() + { + TelegramBotWebHost host = new TelegramBotWebHost(_innerBuilder); + host.UseTelegrator(); + return host; + } - this.AddTelegratorWeb(); - } - - /// - /// Initializes a new instance of the class. - /// - /// - /// - /// - public TelegramBotWebHostBuilder(WebApplicationBuilder webApplicationBuilder, TelegratorOptions? options, WebApplicationOptions? settings) - { - _innerBuilder = webApplicationBuilder ?? throw new ArgumentNullException(nameof(webApplicationBuilder)); - _settings = settings ?? throw new ArgumentNullException(nameof(settings)); - - this.AddTelegratorWeb(options, null); - } - - /// - /// Initializes a new instance of the class. - /// - /// - /// - /// - public TelegramBotWebHostBuilder(WebApplicationBuilder webApplicationBuilder, IHandlersCollection handlers, WebApplicationOptions settings) - { - _innerBuilder = webApplicationBuilder ?? throw new ArgumentNullException(nameof(webApplicationBuilder)); - _settings = settings ?? throw new ArgumentNullException(nameof(settings)); - - this.AddTelegratorWeb(null, handlers); - } - - /// - /// Initializes a new instance of the class. - /// - /// - /// - /// - /// - public TelegramBotWebHostBuilder(WebApplicationBuilder webApplicationBuilder, IHandlersCollection handlers, TelegratorOptions? options, WebApplicationOptions settings) - { - _innerBuilder = webApplicationBuilder ?? throw new ArgumentNullException(nameof(webApplicationBuilder)); - _settings = settings ?? throw new ArgumentNullException(nameof(settings)); - - this.AddTelegratorWeb(options, handlers); - } - - /// - /// Builds the host. - /// - /// - public TelegramBotWebHost Build() - { - TelegramBotWebHost host = new TelegramBotWebHost(_innerBuilder); - host.UseTelegrator(); - return host; - } - - /// - public void ConfigureContainer(IServiceProviderFactory factory, Action? configure = null) where TContainerBuilder : notnull - { - ((IHostApplicationBuilder)_innerBuilder).ConfigureContainer(factory, configure); - } + /// + public void ConfigureContainer(IServiceProviderFactory factory, Action? configure = null) where TContainerBuilder : notnull + { + ((IHostApplicationBuilder)_innerBuilder).ConfigureContainer(factory, configure); } } diff --git a/src/Telegrator.Hosting.Web/Hosting.Web/WebhookerOptions.cs b/src/Telegrator.Hosting.Web/Hosting.Web/WebhookerOptions.cs index da76df5..c320575 100644 --- a/src/Telegrator.Hosting.Web/Hosting.Web/WebhookerOptions.cs +++ b/src/Telegrator.Hosting.Web/Hosting.Web/WebhookerOptions.cs @@ -1,35 +1,34 @@ using System.Diagnostics.CodeAnalysis; -namespace Telegrator.Hosting.Web +namespace Telegrator.Hosting.Web; + +/// +/// Configuration options for Telegram bot behavior and execution settings. +/// Controls various aspects of bot operation including concurrency, routing, webhook receiving, and execution policies. +/// +public class WebhookerOptions { /// - /// Configuration options for Telegram bot behavior and execution settings. - /// Controls various aspects of bot operation including concurrency, routing, webhook receiving, and execution policies. + /// Gets or sets HTTPS URL to send updates to. Use an empty string to remove webhook integration /// - public class WebhookerOptions - { - /// - /// Gets or sets HTTPS URL to send updates to. Use an empty string to remove webhook integration - /// - [StringSyntax(StringSyntaxAttribute.Uri)] - public string WebhookUri { get; set; } = string.Empty; + [StringSyntax(StringSyntaxAttribute.Uri)] + public string WebhookUri { get; set; } = string.Empty; - /// - /// A secret token to be sent in a header “X-Telegram-Bot-Api-Secret-Token” in every webhook request, 1-256 characters. - /// Only characters A-Z, a-z, 0-9, _ and - are allowed. - /// The header is useful to ensure that the request comes from a webhook set by you. - /// - public string? SecretToken { get; set; } = null; + /// + /// A secret token to be sent in a header “X-Telegram-Bot-Api-Secret-Token” in every webhook request, 1-256 characters. + /// Only characters A-Z, a-z, 0-9, _ and - are allowed. + /// The header is useful to ensure that the request comes from a webhook set by you. + /// + public string? SecretToken { get; set; } = null; - /// - /// The maximum allowed number of simultaneous HTTPS connections to the webhook for update delivery, 1-100. Defaults to 40. - /// Use lower values to limit the load on your bot's server, and higher values to increase your bot's throughput. - /// - public int MaxConnections { get; set; } = 40; + /// + /// The maximum allowed number of simultaneous HTTPS connections to the webhook for update delivery, 1-100. Defaults to 40. + /// Use lower values to limit the load on your bot's server, and higher values to increase your bot's throughput. + /// + public int MaxConnections { get; set; } = 40; - /// - /// Pass true to drop all pending updates - /// - public bool DropPendingUpdates { get; set; } = false; - } + /// + /// Pass true to drop all pending updates + /// + public bool DropPendingUpdates { get; set; } = false; } diff --git a/src/Telegrator.Hosting.Web/Mediation/HostedUpdateWebhooker.cs b/src/Telegrator.Hosting.Web/Mediation/HostedUpdateWebhooker.cs index 848e29a..369295f 100644 --- a/src/Telegrator.Hosting.Web/Mediation/HostedUpdateWebhooker.cs +++ b/src/Telegrator.Hosting.Web/Mediation/HostedUpdateWebhooker.cs @@ -10,88 +10,87 @@ using Telegram.Bot.Types; using Telegrator.Core; using Telegrator.Hosting.Web; -namespace Telegrator.Mediation +namespace Telegrator.Mediation; + +/// +/// Service for receiving updates for Hosted telegram bots via Webhooks +/// +public class HostedUpdateWebhooker : IHostedService { + private const string SecretTokenHeader = "X-Telegram-Bot-Api-Secret-Token"; + + private readonly IEndpointRouteBuilder _botHost; + private readonly ITelegramBotClient _botClient; + private readonly IUpdateRouter _updateRouter; + private readonly WebhookerOptions _options; + /// - /// Service for receiving updates for Hosted telegram bots via Webhooks + /// Initiallizes new instance of /// - public class HostedUpdateWebhooker : IHostedService + /// + /// + /// + /// + /// + public HostedUpdateWebhooker(IEndpointRouteBuilder botHost, ITelegramBotClient botClient, IUpdateRouter updateRouter, IOptions options) { - private const string SecretTokenHeader = "X-Telegram-Bot-Api-Secret-Token"; + if (string.IsNullOrEmpty(options.Value.WebhookUri)) + throw new ArgumentNullException(nameof(options), "Option \"WebhookUrl\" must be set to subscribe for update recieving"); - private readonly IEndpointRouteBuilder _botHost; - private readonly ITelegramBotClient _botClient; - private readonly IUpdateRouter _updateRouter; - private readonly WebhookerOptions _options; + _botHost = botHost; + _botClient = botClient; + _updateRouter = updateRouter; + _options = options.Value; + } - /// - /// Initiallizes new instance of - /// - /// - /// - /// - /// - /// - public HostedUpdateWebhooker(IEndpointRouteBuilder botHost, ITelegramBotClient botClient, IUpdateRouter updateRouter, IOptions options) + /// + public Task StartAsync(CancellationToken cancellationToken) + { + StartInternal(cancellationToken); + return Task.CompletedTask; + } + + private async void StartInternal(CancellationToken cancellationToken) + { + string pattern = new UriBuilder(_options.WebhookUri).Path; + _botHost.MapPost(pattern, (Delegate)ReceiveUpdate); + + await _botClient.SetWebhook( + url: _options.WebhookUri, + maxConnections: _options.MaxConnections, + allowedUpdates: _updateRouter.HandlersProvider.AllowedTypes, + dropPendingUpdates: _options.DropPendingUpdates, + secretToken: _options.SecretToken, + cancellationToken: cancellationToken); + } + + /// + public Task StopAsync(CancellationToken cancellationToken) + { + _botClient.DeleteWebhook(_options.DropPendingUpdates, cancellationToken); + return Task.CompletedTask; + } + + private async Task ReceiveUpdate(HttpContext ctx) + { + if (_options.SecretToken != null) { - if (string.IsNullOrEmpty(options.Value.WebhookUri)) - throw new ArgumentNullException(nameof(options), "Option \"WebhookUrl\" must be set to subscribe for update recieving"); - - _botHost = botHost; - _botClient = botClient; - _updateRouter = updateRouter; - _options = options.Value; - } - - /// - public Task StartAsync(CancellationToken cancellationToken) - { - StartInternal(cancellationToken); - return Task.CompletedTask; - } - - private async void StartInternal(CancellationToken cancellationToken) - { - string pattern = new UriBuilder(_options.WebhookUri).Path; - _botHost.MapPost(pattern, (Delegate)ReceiveUpdate); - - await _botClient.SetWebhook( - url: _options.WebhookUri, - maxConnections: _options.MaxConnections, - allowedUpdates: _updateRouter.HandlersProvider.AllowedTypes, - dropPendingUpdates: _options.DropPendingUpdates, - secretToken: _options.SecretToken, - cancellationToken: cancellationToken); - } - - /// - public Task StopAsync(CancellationToken cancellationToken) - { - _botClient.DeleteWebhook(_options.DropPendingUpdates, cancellationToken); - return Task.CompletedTask; - } - - private async Task ReceiveUpdate(HttpContext ctx) - { - if (_options.SecretToken != null) - { - if (!ctx.Request.Headers.TryGetValue(SecretTokenHeader, out StringValues strings)) - return Results.BadRequest(); - - string? secret = strings.SingleOrDefault(); - if (secret == null) - return Results.BadRequest(); - - if (_options.SecretToken != secret) - return Results.StatusCode(401); - } - - Update? update = await JsonSerializer.DeserializeAsync(ctx.Request.Body, JsonBotAPI.Options, ctx.RequestAborted); - if (update is not { Id: > 0 }) + if (!ctx.Request.Headers.TryGetValue(SecretTokenHeader, out StringValues strings)) return Results.BadRequest(); - await _updateRouter.HandleUpdateAsync(_botClient, update, ctx.RequestAborted); - return Results.Ok(); + string? secret = strings.SingleOrDefault(); + if (secret == null) + return Results.BadRequest(); + + if (_options.SecretToken != secret) + return Results.StatusCode(401); } + + Update? update = await JsonSerializer.DeserializeAsync(ctx.Request.Body, JsonBotAPI.Options, ctx.RequestAborted); + if (update is not { Id: > 0 }) + return Results.BadRequest(); + + await _updateRouter.HandleUpdateAsync(_botClient, update, ctx.RequestAborted); + return Results.Ok(); } } diff --git a/src/Telegrator.Hosting/Hosting/HostedTelegramBotInfo.cs b/src/Telegrator.Hosting/Hosting/HostedTelegramBotInfo.cs index 207abc5..e2432a9 100644 --- a/src/Telegrator.Hosting/Hosting/HostedTelegramBotInfo.cs +++ b/src/Telegrator.Hosting/Hosting/HostedTelegramBotInfo.cs @@ -3,28 +3,27 @@ using Telegram.Bot; using Telegram.Bot.Types; using Telegrator.Core; -namespace Telegrator.Hosting -{ - /// - /// Implementation of that provides bot information. - /// Contains metadata about the Telegram bot including user details and service provider for wider filterring abilities - /// - /// - /// - /// - public class HostedTelegramBotInfo(ITelegramBotClient client, IServiceProvider services, IConfiguration configuration) : ITelegramBotInfo - { - /// - public User User { get; } = client.GetMe().Result; - - /// - /// Provides access to services of this Hosted telegram bot - /// - public IServiceProvider Services { get; } = services; +namespace Telegrator.Hosting; - /// - /// Provides access to configuration of this Hosted telegram bot - /// - public IConfiguration Configuration { get; } = configuration; - } +/// +/// Implementation of that provides bot information. +/// Contains metadata about the Telegram bot including user details and service provider for wider filterring abilities +/// +/// +/// +/// +public class HostedTelegramBotInfo(ITelegramBotClient client, IServiceProvider services, IConfiguration configuration) : ITelegramBotInfo +{ + /// + public User User { get; } = client.GetMe().Result; + + /// + /// Provides access to services of this Hosted telegram bot + /// + public IServiceProvider Services { get; } = services; + + /// + /// Provides access to configuration of this Hosted telegram bot + /// + public IConfiguration Configuration { get; } = configuration; } diff --git a/src/Telegrator.Hosting/Hosting/TelegramBotHost.cs b/src/Telegrator.Hosting/Hosting/TelegramBotHost.cs index 3151ada..1072548 100644 --- a/src/Telegrator.Hosting/Hosting/TelegramBotHost.cs +++ b/src/Telegrator.Hosting/Hosting/TelegramBotHost.cs @@ -3,113 +3,112 @@ using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Telegrator.Core; -namespace Telegrator.Hosting +namespace Telegrator.Hosting; + +/// +/// Represents a hosted telegram bot +/// +public class TelegramBotHost : IHost, ITelegratorBot { + private readonly IHost _innerHost; + private readonly IUpdateRouter _updateRouter; + private readonly ILogger _logger; + + private bool _disposed; + + /// + public IServiceProvider Services => _innerHost.Services; + + /// + public IUpdateRouter UpdateRouter => _updateRouter; + /// - /// Represents a hosted telegram bot + /// This application's logger /// - public class TelegramBotHost : IHost, ITelegratorBot + public ILogger Logger => _logger; + + /// + /// Initializes a new instance of the class. + /// + /// The proxied instance of host builder. + public TelegramBotHost(HostApplicationBuilder hostApplicationBuilder) { - private readonly IHost _innerHost; - private readonly IUpdateRouter _updateRouter; - private readonly ILogger _logger; + // Registering this host in services for easy access + hostApplicationBuilder.Services.AddSingleton(this); - private bool _disposed; + // Building proxy hoster + _innerHost = hostApplicationBuilder.Build(); - /// - public IServiceProvider Services => _innerHost.Services; + // Reruesting services for this host + _updateRouter = Services.GetRequiredService(); + _logger = Services.GetRequiredService>(); + } - /// - public IUpdateRouter UpdateRouter => _updateRouter; + /// + /// Creates new with default configuration, services and long-polling update receiving scheme + /// + /// + public static TelegramBotHostBuilder CreateBuilder() + { + HostApplicationBuilder innerBuilder = new HostApplicationBuilder(settings: null); + TelegramBotHostBuilder builder = new TelegramBotHostBuilder(innerBuilder, null); + return builder; + } - /// - /// This application's logger - /// - public ILogger Logger => _logger; + /// + /// Creates new with default services and long-polling update receiving scheme + /// + /// + public static TelegramBotHostBuilder CreateBuilder(HostApplicationBuilderSettings? settings) + { + HostApplicationBuilder innerBuilder = new HostApplicationBuilder(settings); + TelegramBotHostBuilder builder = new TelegramBotHostBuilder(innerBuilder, settings); + return builder; + } - /// - /// Initializes a new instance of the class. - /// - /// The proxied instance of host builder. - public TelegramBotHost(HostApplicationBuilder hostApplicationBuilder) - { - // Registering this host in services for easy access - hostApplicationBuilder.Services.AddSingleton(this); + /// + /// Creates new EMPTY WITHOUT any services or update receiving schemes + /// + /// + public static TelegramBotHostBuilder CreateEmptyBuilder() + { + HostApplicationBuilder innerBuilder = Host.CreateEmptyApplicationBuilder(null); + return new TelegramBotHostBuilder(innerBuilder, null); + } - // Building proxy hoster - _innerHost = hostApplicationBuilder.Build(); + /// + /// Creates new EMPTY WITHOUT any services or update receiving schemes + /// + /// + public static TelegramBotHostBuilder CreateEmptyBuilder(HostApplicationBuilderSettings? settings) + { + HostApplicationBuilder innerBuilder = Host.CreateEmptyApplicationBuilder(null); + return new TelegramBotHostBuilder(innerBuilder, settings); + } - // Reruesting services for this host - _updateRouter = Services.GetRequiredService(); - _logger = Services.GetRequiredService>(); - } + /// + public async Task StartAsync(CancellationToken cancellationToken = default) + { + await _innerHost.StartAsync(cancellationToken); + } - /// - /// Creates new with default configuration, services and long-polling update receiving scheme - /// - /// - public static TelegramBotHostBuilder CreateBuilder() - { - HostApplicationBuilder innerBuilder = new HostApplicationBuilder(settings: null); - TelegramBotHostBuilder builder = new TelegramBotHostBuilder(innerBuilder, null); - return builder; - } + /// + public async Task StopAsync(CancellationToken cancellationToken = default) + { + await _innerHost.StopAsync(cancellationToken); + } - /// - /// Creates new with default services and long-polling update receiving scheme - /// - /// - public static TelegramBotHostBuilder CreateBuilder(HostApplicationBuilderSettings? settings) - { - HostApplicationBuilder innerBuilder = new HostApplicationBuilder(settings); - TelegramBotHostBuilder builder = new TelegramBotHostBuilder(innerBuilder, settings); - return builder; - } + /// + /// Disposes the host. + /// + public void Dispose() + { + if (_disposed) + return; - /// - /// Creates new EMPTY WITHOUT any services or update receiving schemes - /// - /// - public static TelegramBotHostBuilder CreateEmptyBuilder() - { - HostApplicationBuilder innerBuilder = Host.CreateEmptyApplicationBuilder(null); - return new TelegramBotHostBuilder(innerBuilder, null); - } + _innerHost.Dispose(); - /// - /// Creates new EMPTY WITHOUT any services or update receiving schemes - /// - /// - public static TelegramBotHostBuilder CreateEmptyBuilder(HostApplicationBuilderSettings? settings) - { - HostApplicationBuilder innerBuilder = Host.CreateEmptyApplicationBuilder(null); - return new TelegramBotHostBuilder(innerBuilder, settings); - } - - /// - public async Task StartAsync(CancellationToken cancellationToken = default) - { - await _innerHost.StartAsync(cancellationToken); - } - - /// - public async Task StopAsync(CancellationToken cancellationToken = default) - { - await _innerHost.StopAsync(cancellationToken); - } - - /// - /// Disposes the host. - /// - public void Dispose() - { - if (_disposed) - return; - - _innerHost.Dispose(); - - GC.SuppressFinalize(this); - _disposed = true; - } + GC.SuppressFinalize(this); + _disposed = true; } } diff --git a/src/Telegrator.Hosting/Hosting/TelegramBotHostBuilder.cs b/src/Telegrator.Hosting/Hosting/TelegramBotHostBuilder.cs index ce12df9..c357e7b 100644 --- a/src/Telegrator.Hosting/Hosting/TelegramBotHostBuilder.cs +++ b/src/Telegrator.Hosting/Hosting/TelegramBotHostBuilder.cs @@ -3,114 +3,111 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Diagnostics.Metrics; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; using Telegrator.Core; -using Telegrator.Providers; #pragma warning disable IDE0001 -namespace Telegrator.Hosting +namespace Telegrator.Hosting; + +/// +/// Represents a hosted telegram bots and services builder that helps manage configuration, logging, lifetime, and more. +/// +public class TelegramBotHostBuilder : IHostApplicationBuilder, ICollectingProvider { + private readonly HostApplicationBuilder _innerBuilder; + private readonly HostApplicationBuilderSettings _settings; + internal IHandlersCollection _handlers = null!; + + /// + public IHandlersCollection Handlers => _handlers; + + /// + public IServiceCollection Services => _innerBuilder.Services; + + /// + public IConfigurationManager Configuration => _innerBuilder.Configuration; + + /// + public ILoggingBuilder Logging => _innerBuilder.Logging; + + /// + public IHostEnvironment Environment => _innerBuilder.Environment; + + /// + public IDictionary Properties => ((IHostApplicationBuilder)_innerBuilder).Properties; + + /// + public IMetricsBuilder Metrics => _innerBuilder.Metrics; + /// - /// Represents a hosted telegram bots and services builder that helps manage configuration, logging, lifetime, and more. + /// Initializes a new instance of the class. /// - public class TelegramBotHostBuilder : IHostApplicationBuilder, ICollectingProvider + /// + /// + public TelegramBotHostBuilder(HostApplicationBuilder hostApplicationBuilder, HostApplicationBuilderSettings? settings = null) { - private readonly HostApplicationBuilder _innerBuilder; - private readonly HostApplicationBuilderSettings _settings; - internal IHandlersCollection _handlers = null!; + _innerBuilder = hostApplicationBuilder ?? throw new ArgumentNullException(nameof(hostApplicationBuilder)); + _settings = settings ?? new HostApplicationBuilderSettings(); - /// - public IHandlersCollection Handlers => _handlers; + this.AddTelegrator(); + } - /// - public IServiceCollection Services => _innerBuilder.Services; + /// + /// Initializes a new instance of the class. + /// + /// + /// + /// + public TelegramBotHostBuilder(HostApplicationBuilder hostApplicationBuilder, TelegratorOptions? options, HostApplicationBuilderSettings? settings) + { + _innerBuilder = hostApplicationBuilder ?? throw new ArgumentNullException(nameof(hostApplicationBuilder)); + _settings = settings ?? new HostApplicationBuilderSettings(); - /// - public IConfigurationManager Configuration => _innerBuilder.Configuration; + this.AddTelegrator(options, null); + } - /// - public ILoggingBuilder Logging => _innerBuilder.Logging; + /// + /// Initializes a new instance of the class. + /// + /// + /// + /// + public TelegramBotHostBuilder(HostApplicationBuilder hostApplicationBuilder, IHandlersCollection handlers, HostApplicationBuilderSettings? settings) + { + _innerBuilder = hostApplicationBuilder ?? throw new ArgumentNullException(nameof(hostApplicationBuilder)); + _settings = settings ?? new HostApplicationBuilderSettings(); - /// - public IHostEnvironment Environment => _innerBuilder.Environment; + this.AddTelegrator(null, handlers); + } - /// - public IDictionary Properties => ((IHostApplicationBuilder)_innerBuilder).Properties; + /// + /// Initializes a new instance of the class. + /// + /// + /// + /// + /// + public TelegramBotHostBuilder(HostApplicationBuilder hostApplicationBuilder, IHandlersCollection handlers, TelegratorOptions? options, HostApplicationBuilderSettings? settings) + { + _innerBuilder = hostApplicationBuilder ?? throw new ArgumentNullException(nameof(hostApplicationBuilder)); + _settings = settings ?? new HostApplicationBuilderSettings(); - /// - public IMetricsBuilder Metrics => _innerBuilder.Metrics; + this.AddTelegrator(options, handlers); + } - /// - /// Initializes a new instance of the class. - /// - /// - /// - public TelegramBotHostBuilder(HostApplicationBuilder hostApplicationBuilder, HostApplicationBuilderSettings? settings = null) - { - _innerBuilder = hostApplicationBuilder ?? throw new ArgumentNullException(nameof(hostApplicationBuilder)); - _settings = settings ?? new HostApplicationBuilderSettings(); + /// + /// Builds the host. + /// + /// + public TelegramBotHost Build() + { + TelegramBotHost host = new TelegramBotHost(_innerBuilder); + host.UseTelegrator(); + return host; + } - this.AddTelegrator(); - } - - /// - /// Initializes a new instance of the class. - /// - /// - /// - /// - public TelegramBotHostBuilder(HostApplicationBuilder hostApplicationBuilder, TelegratorOptions? options, HostApplicationBuilderSettings? settings) - { - _innerBuilder = hostApplicationBuilder ?? throw new ArgumentNullException(nameof(hostApplicationBuilder)); - _settings = settings ?? new HostApplicationBuilderSettings(); - - this.AddTelegrator(options, null); - } - - /// - /// Initializes a new instance of the class. - /// - /// - /// - /// - public TelegramBotHostBuilder(HostApplicationBuilder hostApplicationBuilder, IHandlersCollection handlers, HostApplicationBuilderSettings? settings) - { - _innerBuilder = hostApplicationBuilder ?? throw new ArgumentNullException(nameof(hostApplicationBuilder)); - _settings = settings ?? new HostApplicationBuilderSettings(); - - this.AddTelegrator(null, handlers); - } - - /// - /// Initializes a new instance of the class. - /// - /// - /// - /// - /// - public TelegramBotHostBuilder(HostApplicationBuilder hostApplicationBuilder, IHandlersCollection handlers, TelegratorOptions? options, HostApplicationBuilderSettings? settings) - { - _innerBuilder = hostApplicationBuilder ?? throw new ArgumentNullException(nameof(hostApplicationBuilder)); - _settings = settings ?? new HostApplicationBuilderSettings(); - - this.AddTelegrator(options, handlers); - } - - /// - /// Builds the host. - /// - /// - public TelegramBotHost Build() - { - TelegramBotHost host = new TelegramBotHost(_innerBuilder); - host.UseTelegrator(); - return host; - } - - /// - public void ConfigureContainer(IServiceProviderFactory factory, Action? configure = null) where TContainerBuilder : notnull - { - this.ConfigureContainer(factory, configure); - } + /// + public void ConfigureContainer(IServiceProviderFactory factory, Action? configure = null) where TContainerBuilder : notnull + { + this.ConfigureContainer(factory, configure); } } diff --git a/src/Telegrator.Hosting/Logging/MicrosoftLoggingAdapter.cs b/src/Telegrator.Hosting/Logging/MicrosoftLoggingAdapter.cs index 0454595..d0292ab 100644 --- a/src/Telegrator.Hosting/Logging/MicrosoftLoggingAdapter.cs +++ b/src/Telegrator.Hosting/Logging/MicrosoftLoggingAdapter.cs @@ -1,45 +1,42 @@ -using Microsoft.Extensions.Logging; +namespace Telegrator.Logging; -namespace Telegrator.Logging +/// +/// Adapter for Microsoft.Extensions.Logging to work with Telegrator logging system. +/// This allows seamless integration with ASP.NET Core logging infrastructure. +/// +public class MicrosoftLoggingAdapter : ITelegratorLogger { + private readonly Microsoft.Extensions.Logging.ILogger _logger; + /// - /// Adapter for Microsoft.Extensions.Logging to work with Telegrator logging system. - /// This allows seamless integration with ASP.NET Core logging infrastructure. + /// Initializes a new instance of MicrosoftLoggingAdapter. /// - public class MicrosoftLoggingAdapter : ITelegratorLogger + /// The Microsoft.Extensions.Logging logger instance. + public MicrosoftLoggingAdapter(Microsoft.Extensions.Logging.ILogger logger) { - private readonly ILogger _logger; + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + } - /// - /// Initializes a new instance of MicrosoftLoggingAdapter. - /// - /// The Microsoft.Extensions.Logging logger instance. - public MicrosoftLoggingAdapter(ILogger logger) + /// + public void Log(LogLevel level, string message, Exception? exception = null) + { + var msLogLevel = level switch { - _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + LogLevel.Trace => Microsoft.Extensions.Logging.LogLevel.Trace, + LogLevel.Debug => Microsoft.Extensions.Logging.LogLevel.Debug, + LogLevel.Information => Microsoft.Extensions.Logging.LogLevel.Information, + LogLevel.Warning => Microsoft.Extensions.Logging.LogLevel.Warning, + LogLevel.Error => Microsoft.Extensions.Logging.LogLevel.Error, + _ => Microsoft.Extensions.Logging.LogLevel.Information + }; + + if (exception != null) + { + _logger.Log(msLogLevel, default, message, exception, (str, exc) => string.Format("{0} : {1}", str, exc)); } - - /// - public void Log(LogLevel level, string message, Exception? exception = null) + else { - var msLogLevel = level switch - { - Telegrator.Logging.LogLevel.Trace => Microsoft.Extensions.Logging.LogLevel.Trace, - Telegrator.Logging.LogLevel.Debug => Microsoft.Extensions.Logging.LogLevel.Debug, - Telegrator.Logging.LogLevel.Information => Microsoft.Extensions.Logging.LogLevel.Information, - Telegrator.Logging.LogLevel.Warning => Microsoft.Extensions.Logging.LogLevel.Warning, - Telegrator.Logging.LogLevel.Error => Microsoft.Extensions.Logging.LogLevel.Error, - _ => Microsoft.Extensions.Logging.LogLevel.Information - }; - - if (exception != null) - { - _logger.Log(msLogLevel, default, message, exception, (str, exc) => string.Format("{0} : {1}", str, exc)); - } - else - { - _logger.Log(msLogLevel, default, message, null, (str, _) => str); - } + _logger.Log(msLogLevel, default, message, null, (str, _) => str); } } -} \ No newline at end of file +} diff --git a/src/Telegrator.Hosting/Polling/HostUpdateRouter.cs b/src/Telegrator.Hosting/Polling/HostUpdateRouter.cs index 742a60f..a4cbcca 100644 --- a/src/Telegrator.Hosting/Polling/HostUpdateRouter.cs +++ b/src/Telegrator.Hosting/Polling/HostUpdateRouter.cs @@ -7,54 +7,53 @@ using Telegrator.Core; using Telegrator.Core.States; using Telegrator.Mediation; -namespace Telegrator.Polling +namespace Telegrator.Polling; + +/// +public class HostUpdateRouter : UpdateRouter { + /// + /// of this router + /// + protected readonly ILogger Logger; + /// - public class HostUpdateRouter : UpdateRouter + public HostUpdateRouter( + IHandlersProvider handlersProvider, + IAwaitingProvider awaitingProvider, + IStateStorage stateStorage, + IOptions options, + ITelegramBotInfo botInfo, + ILogger logger) : base(handlersProvider, awaitingProvider, stateStorage, options.Value, botInfo) { - /// - /// of this router - /// - protected readonly ILogger Logger; + Logger = logger; + ExceptionHandler = new DefaultRouterExceptionHandler(HandleException); + } - /// - public HostUpdateRouter( - IHandlersProvider handlersProvider, - IAwaitingProvider awaitingProvider, - IStateStorage stateStorage, - IOptions options, - ITelegramBotInfo botInfo, - ILogger logger) : base(handlersProvider, awaitingProvider, stateStorage, options.Value, botInfo) + /// + public override Task HandleUpdateAsync(ITelegramBotClient botClient, Update update, CancellationToken cancellationToken) + { + //Logger.LogInformation("Received update of type \"{type}\"", update.Type); + return base.HandleUpdateAsync(botClient, update, cancellationToken); + } + + /// + /// Default exception handler of this router + /// + /// + /// + /// + /// + public void HandleException(ITelegramBotClient botClient, Exception exception, HandleErrorSource source, CancellationToken cancellationToken) + { + if (exception is HandlerFaultedException handlerFaultedException) { - Logger = logger; - ExceptionHandler = new DefaultRouterExceptionHandler(HandleException); + Logger.LogError("\"{handler}\" handler's execution was faulted :\n{exception}", + handlerFaultedException.HandlerInfo.ToString(), + handlerFaultedException.InnerException?.ToString() ?? "No inner exception"); + return; } - /// - public override Task HandleUpdateAsync(ITelegramBotClient botClient, Update update, CancellationToken cancellationToken) - { - //Logger.LogInformation("Received update of type \"{type}\"", update.Type); - return base.HandleUpdateAsync(botClient, update, cancellationToken); - } - - /// - /// Default exception handler of this router - /// - /// - /// - /// - /// - public void HandleException(ITelegramBotClient botClient, Exception exception, HandleErrorSource source, CancellationToken cancellationToken) - { - if (exception is HandlerFaultedException handlerFaultedException) - { - Logger.LogError("\"{handler}\" handler's execution was faulted :\n{exception}", - handlerFaultedException.HandlerInfo.ToString(), - handlerFaultedException.InnerException?.ToString() ?? "No inner exception"); - return; - } - - Logger.LogError("Exception was thrown during update routing faulted :\n{exception}", exception.ToString()); - } + Logger.LogError("Exception was thrown during update routing faulted :\n{exception}", exception.ToString()); } } diff --git a/src/Telegrator.Hosting/Polling/HostedUpdateReceiver.cs b/src/Telegrator.Hosting/Polling/HostedUpdateReceiver.cs index d67cd29..deab9c2 100644 --- a/src/Telegrator.Hosting/Polling/HostedUpdateReceiver.cs +++ b/src/Telegrator.Hosting/Polling/HostedUpdateReceiver.cs @@ -6,27 +6,26 @@ using Telegram.Bot.Polling; using Telegrator.Core; using Telegrator.Mediation; -namespace Telegrator.Polling -{ - /// - /// Service for receiving updates for Hosted telegram bots - /// - /// - /// - /// - /// - public class HostedUpdateReceiver(ITelegramBotClient botClient, IUpdateRouter updateRouter, IOptions options, ILogger logger) : BackgroundService - { - private readonly ReceiverOptions _receiverOptions = options.Value; - private readonly IUpdateRouter _updateRouter = updateRouter; +namespace Telegrator.Polling; - /// - protected override async Task ExecuteAsync(CancellationToken stoppingToken) - { - logger.LogInformation("Starting receiving updates via long-polling"); - _receiverOptions.AllowedUpdates = _updateRouter.HandlersProvider.AllowedTypes.ToArray(); - DefaultUpdateReceiver updateReceiver = new DefaultUpdateReceiver(botClient, _receiverOptions); - await updateReceiver.ReceiveAsync(_updateRouter, stoppingToken).ConfigureAwait(false); - } +/// +/// Service for receiving updates for Hosted telegram bots +/// +/// +/// +/// +/// +public class HostedUpdateReceiver(ITelegramBotClient botClient, IUpdateRouter updateRouter, IOptions options, ILogger logger) : BackgroundService +{ + private readonly ReceiverOptions _receiverOptions = options.Value; + private readonly IUpdateRouter _updateRouter = updateRouter; + + /// + protected override async Task ExecuteAsync(CancellationToken stoppingToken) + { + logger.LogInformation("Starting receiving updates via long-polling"); + _receiverOptions.AllowedUpdates = _updateRouter.HandlersProvider.AllowedTypes.ToArray(); + DefaultUpdateReceiver updateReceiver = new DefaultUpdateReceiver(botClient, _receiverOptions); + await updateReceiver.ReceiveAsync(_updateRouter, stoppingToken).ConfigureAwait(false); } } diff --git a/src/Telegrator.Hosting/Providers/HostAwaitingProvider.cs b/src/Telegrator.Hosting/Providers/HostAwaitingProvider.cs index b622819..6a72de9 100644 --- a/src/Telegrator.Hosting/Providers/HostAwaitingProvider.cs +++ b/src/Telegrator.Hosting/Providers/HostAwaitingProvider.cs @@ -1,11 +1,9 @@ -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; +using Microsoft.Extensions.Options; -namespace Telegrator.Providers +namespace Telegrator.Providers; + +/// +public class HostAwaitingProvider(IOptions options) : AwaitingProvider(options.Value) { - /// - public class HostAwaitingProvider(IOptions options, ILogger logger) : AwaitingProvider(options.Value) - { - private readonly ILogger _logger = logger; - } + } diff --git a/src/Telegrator.Hosting/Providers/HostHandlersCollection.cs b/src/Telegrator.Hosting/Providers/HostHandlersCollection.cs index 39fcaba..8628050 100644 --- a/src/Telegrator.Hosting/Providers/HostHandlersCollection.cs +++ b/src/Telegrator.Hosting/Providers/HostHandlersCollection.cs @@ -2,61 +2,60 @@ using Telegrator.Core; using Telegrator.Core.Descriptors; -namespace Telegrator.Providers +namespace Telegrator.Providers; + +/// +public class HostHandlersCollection(IServiceCollection hostServiceColletion, TelegratorOptions options) : HandlersCollection(options) { + private readonly IServiceCollection Services = hostServiceColletion; + /// - public class HostHandlersCollection(IServiceCollection hostServiceColletion, TelegratorOptions options) : HandlersCollection(options) + protected override bool MustHaveParameterlessCtor => false; + + /// + public override IHandlersCollection AddDescriptor(HandlerDescriptor descriptor) { - private readonly IServiceCollection Services = hostServiceColletion; - - /// - protected override bool MustHaveParameterlessCtor => false; - - /// - public override IHandlersCollection AddDescriptor(HandlerDescriptor descriptor) + switch (descriptor.Type) { - switch (descriptor.Type) - { - case DescriptorType.General: - { - if (descriptor.InstanceFactory != null) - Services.AddScoped(descriptor.HandlerType, _ => descriptor.InstanceFactory.Invoke()); - else - Services.AddScoped(descriptor.HandlerType); + case DescriptorType.General: + { + if (descriptor.InstanceFactory != null) + Services.AddScoped(descriptor.HandlerType, _ => descriptor.InstanceFactory.Invoke()); + else + Services.AddScoped(descriptor.HandlerType); - break; - } + break; + } - case DescriptorType.Keyed: - { - if (descriptor.InstanceFactory != null) - Services.AddKeyedScoped(descriptor.HandlerType, descriptor.ServiceKey, (_, _) => descriptor.InstanceFactory.Invoke()); - else - Services.AddKeyedScoped(descriptor.HandlerType, descriptor.ServiceKey); + case DescriptorType.Keyed: + { + if (descriptor.InstanceFactory != null) + Services.AddKeyedScoped(descriptor.HandlerType, descriptor.ServiceKey, (_, _) => descriptor.InstanceFactory.Invoke()); + else + Services.AddKeyedScoped(descriptor.HandlerType, descriptor.ServiceKey); - break; - } + break; + } - case DescriptorType.Singleton: - { - Services.AddSingleton(descriptor.HandlerType, descriptor.SingletonInstance ?? (descriptor.InstanceFactory != null - ? descriptor.InstanceFactory.Invoke() - : throw new Exception())); + case DescriptorType.Singleton: + { + Services.AddSingleton(descriptor.HandlerType, descriptor.SingletonInstance ?? (descriptor.InstanceFactory != null + ? descriptor.InstanceFactory.Invoke() + : throw new Exception())); - break; - } + break; + } - case DescriptorType.Implicit: - { - Services.AddKeyedSingleton(descriptor.HandlerType, descriptor.ServiceKey, descriptor.SingletonInstance ?? (descriptor.InstanceFactory != null - ? descriptor.InstanceFactory.Invoke() - : throw new Exception())); - - break; - } - } - - return base.AddDescriptor(descriptor); + case DescriptorType.Implicit: + { + Services.AddKeyedSingleton(descriptor.HandlerType, descriptor.ServiceKey, descriptor.SingletonInstance ?? (descriptor.InstanceFactory != null + ? descriptor.InstanceFactory.Invoke() + : throw new Exception())); + + break; + } } + + return base.AddDescriptor(descriptor); } } diff --git a/src/Telegrator.Hosting/Providers/HostHandlersProvider.cs b/src/Telegrator.Hosting/Providers/HostHandlersProvider.cs index 65f3091..0c82a52 100644 --- a/src/Telegrator.Hosting/Providers/HostHandlersProvider.cs +++ b/src/Telegrator.Hosting/Providers/HostHandlersProvider.cs @@ -1,45 +1,40 @@ using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Telegrator.Core; using Telegrator.Core.Descriptors; using Telegrator.Core.Handlers; -namespace Telegrator.Providers +namespace Telegrator.Providers; + +/// +public class HostHandlersProvider : HandlersProvider { + private readonly IServiceProvider Services; + /// - public class HostHandlersProvider : HandlersProvider + public HostHandlersProvider( + IHandlersCollection handlers, + IOptions options, + IServiceProvider serviceProvider) : base(handlers, options.Value) { - private readonly IServiceProvider Services; - private readonly ILogger Logger; + Services = serviceProvider; + } - /// - public HostHandlersProvider( - IHandlersCollection handlers, - IOptions options, - IServiceProvider serviceProvider, - ILogger logger) : base(handlers, options.Value) - { - Services = serviceProvider; - Logger = logger; - } + /// + public override UpdateHandlerBase GetHandlerInstance(HandlerDescriptor descriptor, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + IServiceScope scope = Services.CreateScope(); - /// - public override UpdateHandlerBase GetHandlerInstance(HandlerDescriptor descriptor, CancellationToken cancellationToken = default) - { - cancellationToken.ThrowIfCancellationRequested(); - IServiceScope scope = Services.CreateScope(); + object handlerInstance = descriptor.ServiceKey == null + ? scope.ServiceProvider.GetRequiredService(descriptor.HandlerType) + : scope.ServiceProvider.GetRequiredKeyedService(descriptor.HandlerType, descriptor.ServiceKey); - object handlerInstance = descriptor.ServiceKey == null - ? scope.ServiceProvider.GetRequiredService(descriptor.HandlerType) - : scope.ServiceProvider.GetRequiredKeyedService(descriptor.HandlerType, descriptor.ServiceKey); + if (handlerInstance is not UpdateHandlerBase updateHandler) + throw new InvalidOperationException("Failed to resolve " + descriptor.HandlerType + " as UpdateHandlerBase"); - if (handlerInstance is not UpdateHandlerBase updateHandler) - throw new InvalidOperationException("Failed to resolve " + descriptor.HandlerType + " as UpdateHandlerBase"); - - descriptor.LazyInitialization?.Invoke(updateHandler); - updateHandler.LifetimeToken.OnLifetimeEnded += _ => scope.Dispose(); - return updateHandler; - } + descriptor.LazyInitialization?.Invoke(updateHandler); + updateHandler.LifetimeToken.OnLifetimeEnded += _ => scope.Dispose(); + return updateHandler; } } diff --git a/src/Telegrator.Localized/LocalizedMessageHandlerExtensions.cs b/src/Telegrator.Localized/LocalizedMessageHandlerExtensions.cs index 9505566..ffd3b44 100644 --- a/src/Telegrator.Localized/LocalizedMessageHandlerExtensions.cs +++ b/src/Telegrator.Localized/LocalizedMessageHandlerExtensions.cs @@ -4,14 +4,13 @@ using System.Threading.Tasks; using Telegram.Bot.Types; using Telegrator.Handlers; -namespace Telegrator.Localized +namespace Telegrator.Localized; + +public static class LocalizedMessageHandlerExtensions { - public static class LocalizedMessageHandlerExtensions + public static async Task ResponseLocalized(this ILocalizedHandler localizedHandler, string localizedReplyIdentifier, params IEnumerable formatArgs) { - public static async Task ResponseLocalized(this ILocalizedHandler localizedHandler, string localizedReplyIdentifier, params IEnumerable formatArgs) - { - LocalizedString localizedString = localizedHandler.LocalizationProvider[localizedReplyIdentifier, formatArgs]; - return await localizedHandler.Container.Responce(localizedString.Value); - } + LocalizedString localizedString = localizedHandler.LocalizationProvider[localizedReplyIdentifier, formatArgs]; + return await localizedHandler.Container.Responce(localizedString.Value); } } diff --git a/src/Telegrator/Annotations/CallbackQueryAttributes.cs b/src/Telegrator/Annotations/CallbackQueryAttributes.cs index cc60a71..a864f69 100644 --- a/src/Telegrator/Annotations/CallbackQueryAttributes.cs +++ b/src/Telegrator/Annotations/CallbackQueryAttributes.cs @@ -4,41 +4,40 @@ using Telegrator.Attributes; using Telegrator.Core.Filters; using Telegrator.Filters; -namespace Telegrator.Annotations +namespace Telegrator.Annotations; + +/// +/// Abstract base attribute for filtering callback-based updates. +/// Supports various message types including regular messages, edited messages, channel posts, and business messages. +/// +/// The filters to apply to messages +public abstract class CallbackQueryAttribute(params IFilter[] filters) : UpdateFilterAttribute(filters) { /// - /// Abstract base attribute for filtering callback-based updates. - /// Supports various message types including regular messages, edited messages, channel posts, and business messages. + /// Gets the allowed update types that this filter can process. /// - /// The filters to apply to messages - public abstract class CallbackQueryAttribute(params IFilter[] filters) : UpdateFilterAttribute(filters) - { - /// - /// Gets the allowed update types that this filter can process. - /// - public override UpdateType[] AllowedTypes => [UpdateType.CallbackQuery]; - - /// - /// Extracts the message from various types of updates. - /// - /// The Telegram update - /// The message from the update, or null if not present - public override CallbackQuery? GetFilterringTarget(Update update) - => update.CallbackQuery; - } + public override UpdateType[] AllowedTypes => [UpdateType.CallbackQuery]; /// - /// Attribute for filtering 's data + /// Extracts the message from various types of updates. /// - /// - public class CallbackDataAttribute(string data) - : CallbackQueryAttribute(new CallbackDataFilter(data)) - { } - - /// - /// Attribute to check if belongs to a specific message by its ID - /// - public class CallbackInlineIdAttribute(string inlineMessageId) - : CallbackQueryAttribute(new CallbackInlineIdFilter(inlineMessageId)) - { } + /// The Telegram update + /// The message from the update, or null if not present + public override CallbackQuery? GetFilterringTarget(Update update) + => update.CallbackQuery; } + +/// +/// Attribute for filtering 's data +/// +/// +public class CallbackDataAttribute(string data) + : CallbackQueryAttribute(new CallbackDataFilter(data)) +{ } + +/// +/// Attribute to check if belongs to a specific message by its ID +/// +public class CallbackInlineIdAttribute(string inlineMessageId) + : CallbackQueryAttribute(new CallbackInlineIdFilter(inlineMessageId)) +{ } diff --git a/src/Telegrator/Annotations/CommandAlliasAttribute.cs b/src/Telegrator/Annotations/CommandAlliasAttribute.cs index 6766028..428c603 100644 --- a/src/Telegrator/Annotations/CommandAlliasAttribute.cs +++ b/src/Telegrator/Annotations/CommandAlliasAttribute.cs @@ -3,58 +3,57 @@ using Telegram.Bot.Types.Enums; using Telegrator.Filters; using Telegrator.Attributes; -namespace Telegrator.Annotations +namespace Telegrator.Annotations; + +/// +/// Attribute for filtering messages based on command aliases. +/// Allows handlers to respond to multiple command variations using a single attribute. +/// +public class CommandAlliasAttribute : UpdateFilterAttribute { /// - /// Attribute for filtering messages based on command aliases. - /// Allows handlers to respond to multiple command variations using a single attribute. + /// Gets the allowed update types for this filter. /// - public class CommandAlliasAttribute : UpdateFilterAttribute + public override UpdateType[] AllowedTypes => [UpdateType.Message]; + + /// + /// The description of the command (defaults to "no description provided"). + /// + private string _description = "no description provided"; + + /// + /// Gets the array of command aliases that this filter will match. + /// + public string[] Alliases { - /// - /// Gets the allowed update types for this filter. - /// - public override UpdateType[] AllowedTypes => [UpdateType.Message]; - - /// - /// The description of the command (defaults to "no description provided"). - /// - private string _description = "no description provided"; - - /// - /// Gets the array of command aliases that this filter will match. - /// - public string[] Alliases - { - get; - private set; - } - - /// - /// Gets or sets the description of the command. - /// Must be between 0 and 256 characters in length. - /// - /// Thrown when the description length is outside the allowed range. - public string Description - { - get => _description; - set => _description = value is { Length: <= 256 and >= 0 } - ? value : throw new ArgumentOutOfRangeException(nameof(value)); - } - - /// - /// Initializes a new instance of the CommandAlliasAttribute with the specified command aliases. - /// - /// The command aliases to match against. - public CommandAlliasAttribute(params string[] alliases) - : base(new CommandAlliasFilter(alliases.Select(c => c.TrimStart('/')).ToArray())) - => Alliases = alliases.Select(c => c.TrimStart('/')).ToArray(); - - /// - /// Gets the filtering target (Message) from the update. - /// - /// The Telegram update. - /// The message from the update, or null if not present. - public override Message? GetFilterringTarget(Update update) => update.Message; + get; + private set; } + + /// + /// Gets or sets the description of the command. + /// Must be between 0 and 256 characters in length. + /// + /// Thrown when the description length is outside the allowed range. + public string Description + { + get => _description; + set => _description = value is { Length: <= 256 and >= 0 } + ? value : throw new ArgumentOutOfRangeException(nameof(value)); + } + + /// + /// Initializes a new instance of the CommandAlliasAttribute with the specified command aliases. + /// + /// The command aliases to match against. + public CommandAlliasAttribute(params string[] alliases) + : base(new CommandAlliasFilter(alliases.Select(c => c.TrimStart('/')).ToArray())) + => Alliases = alliases.Select(c => c.TrimStart('/')).ToArray(); + + /// + /// Gets the filtering target (Message) from the update. + /// + /// The Telegram update. + /// The message from the update, or null if not present. + public override Message? GetFilterringTarget(Update update) => update.Message; } diff --git a/src/Telegrator/Annotations/CommandArgumentAttributes.cs b/src/Telegrator/Annotations/CommandArgumentAttributes.cs index 6c1ec1a..0cf127b 100644 --- a/src/Telegrator/Annotations/CommandArgumentAttributes.cs +++ b/src/Telegrator/Annotations/CommandArgumentAttributes.cs @@ -1,63 +1,62 @@ using System.Text.RegularExpressions; using Telegrator.Filters; -namespace Telegrator.Annotations -{ - /// - /// Attribute for filtering messages where a command has arguments count >= . - /// - /// - public class ArgumentCountAttribute(int count) - : MessageFilterAttribute(new ArgumentCountFilter(count)) - { } +namespace Telegrator.Annotations; - /// - /// Attribute for filtering messages where a command argument starts with the specified content. - /// - /// The content that the command argument should start with. - /// The string comparison type to use for the check. - /// The index of the argument to check (0-based). - public class ArgumentStartsWithAttribute(string content, StringComparison comparison = StringComparison.InvariantCulture, int index = 0) - : MessageFilterAttribute(new ArgumentStartsWithFilter(content, comparison, index)) - { } +/// +/// Attribute for filtering messages where a command has arguments count >= . +/// +/// +public class ArgumentCountAttribute(int count) + : MessageFilterAttribute(new ArgumentCountFilter(count)) +{ } - /// - /// Attribute for filtering messages where a command argument ends with the specified content. - /// - /// The content that the command argument should end with. - /// The string comparison type to use for the check. - /// The index of the argument to check (0-based). - public class ArgumentEndsWithAttribute(string content, StringComparison comparison = StringComparison.InvariantCulture, int index = 0) - : MessageFilterAttribute(new ArgumentEndsWithFilter(content, comparison, index)) - { } +/// +/// Attribute for filtering messages where a command argument starts with the specified content. +/// +/// The content that the command argument should start with. +/// The string comparison type to use for the check. +/// The index of the argument to check (0-based). +public class ArgumentStartsWithAttribute(string content, StringComparison comparison = StringComparison.InvariantCulture, int index = 0) + : MessageFilterAttribute(new ArgumentStartsWithFilter(content, comparison, index)) +{ } - /// - /// Attribute for filtering messages where a command argument contains the specified content. - /// - /// The content that the command argument should contain. - /// The string comparison type to use for the check. - /// The index of the argument to check (0-based). - public class ArgumentContainsAttribute(string content, StringComparison comparison = StringComparison.InvariantCulture, int index = 0) - : MessageFilterAttribute(new ArgumentContainsFilter(content, comparison, index)) - { } +/// +/// Attribute for filtering messages where a command argument ends with the specified content. +/// +/// The content that the command argument should end with. +/// The string comparison type to use for the check. +/// The index of the argument to check (0-based). +public class ArgumentEndsWithAttribute(string content, StringComparison comparison = StringComparison.InvariantCulture, int index = 0) + : MessageFilterAttribute(new ArgumentEndsWithFilter(content, comparison, index)) +{ } - /// - /// Attribute for filtering messages where a command argument equals the specified content. - /// - /// The content that the command argument should equal. - /// The string comparison type to use for the check. - /// The index of the argument to check (0-based). - public class ArgumentEqualsAttribute(string content, StringComparison comparison = StringComparison.InvariantCulture, int index = 0) - : MessageFilterAttribute(new ArgumentEqualsFilter(content, comparison, index)) - { } +/// +/// Attribute for filtering messages where a command argument contains the specified content. +/// +/// The content that the command argument should contain. +/// The string comparison type to use for the check. +/// The index of the argument to check (0-based). +public class ArgumentContainsAttribute(string content, StringComparison comparison = StringComparison.InvariantCulture, int index = 0) + : MessageFilterAttribute(new ArgumentContainsFilter(content, comparison, index)) +{ } - /// - /// Attribute for filtering messages where a command argument matches a regular expression pattern. - /// - /// The regular expression pattern to match against the command argument. - /// The regex options to use for the pattern matching. - /// The index of the argument to check (0-based). - public class ArgumentRegexAttribute(string pattern, RegexOptions options = RegexOptions.None, int index = 0) - : MessageFilterAttribute(new ArgumentRegexFilter(pattern, options, index: index)) - { } -} +/// +/// Attribute for filtering messages where a command argument equals the specified content. +/// +/// The content that the command argument should equal. +/// The string comparison type to use for the check. +/// The index of the argument to check (0-based). +public class ArgumentEqualsAttribute(string content, StringComparison comparison = StringComparison.InvariantCulture, int index = 0) + : MessageFilterAttribute(new ArgumentEqualsFilter(content, comparison, index)) +{ } + +/// +/// Attribute for filtering messages where a command argument matches a regular expression pattern. +/// +/// The regular expression pattern to match against the command argument. +/// The regex options to use for the pattern matching. +/// The index of the argument to check (0-based). +public class ArgumentRegexAttribute(string pattern, RegexOptions options = RegexOptions.None, int index = 0) + : MessageFilterAttribute(new ArgumentRegexFilter(pattern, options, index: index)) +{ } diff --git a/src/Telegrator/Annotations/DontCollectAttribute.cs b/src/Telegrator/Annotations/DontCollectAttribute.cs index 77bd276..871939d 100644 --- a/src/Telegrator/Annotations/DontCollectAttribute.cs +++ b/src/Telegrator/Annotations/DontCollectAttribute.cs @@ -1,12 +1,11 @@ -namespace Telegrator.Annotations -{ - /// - /// Attribute that prevents a class from being automatically collected by the handler collection system. - /// When applied to a class, it will be excluded from domain-wide handler collection operations. - /// - [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)] - public class DontCollectAttribute : Attribute - { +namespace Telegrator.Annotations; + +/// +/// Attribute that prevents a class from being automatically collected by the handler collection system. +/// When applied to a class, it will be excluded from domain-wide handler collection operations. +/// +[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)] +public class DontCollectAttribute : Attribute +{ - } } diff --git a/src/Telegrator/Annotations/EnvironmentFilterAttributes.cs b/src/Telegrator/Annotations/EnvironmentFilterAttributes.cs index 86f4475..aa59c84 100644 --- a/src/Telegrator/Annotations/EnvironmentFilterAttributes.cs +++ b/src/Telegrator/Annotations/EnvironmentFilterAttributes.cs @@ -4,82 +4,81 @@ using Telegrator.Filters; using Telegrator.Attributes; using Telegrator.Core.Filters; -namespace Telegrator.Annotations +namespace Telegrator.Annotations; + +/// +/// Abstract base attribute for filtering updates based on environment conditions. +/// Can process all types of updates and provides environment-specific filtering logic. +/// +/// The environment filters to apply +public abstract class EnvironmentFilterAttribute(params IFilter[] filters) : UpdateFilterAttribute(filters) { /// - /// Abstract base attribute for filtering updates based on environment conditions. - /// Can process all types of updates and provides environment-specific filtering logic. + /// Gets the allowed update types that this filter can process. + /// Environment filters can process all update types. /// - /// The environment filters to apply - public abstract class EnvironmentFilterAttribute(params IFilter[] filters) : UpdateFilterAttribute(filters) - { - /// - /// Gets the allowed update types that this filter can process. - /// Environment filters can process all update types. - /// - public override UpdateType[] AllowedTypes => Update.AllTypes; - - /// - /// Gets the update as the filtering target. - /// Environment filters work with the entire update object. - /// - /// The Telegram update - /// The update object itself - public override Update? GetFilterringTarget(Update update) - => update; - } + public override UpdateType[] AllowedTypes => Update.AllTypes; /// - /// Attribute for filtering updates that occur in debug environment. - /// Only allows updates when the application is running in debug mode. + /// Gets the update as the filtering target. + /// Environment filters work with the entire update object. /// - public class IsDebugEnvironmentAttribute() - : EnvironmentFilterAttribute(new IsDebugEnvironmentFilter()) - { } - - /// - /// Attribute for filtering updates that occur in release environment. - /// Only allows updates when the application is running in release mode. - /// - public class IsReleaseEnvironmentAttribute() - : EnvironmentFilterAttribute(new IsReleaseEnvironmentFilter()) - { } - - /// - /// Attribute for filtering updates based on environment variable values. - /// - public class EnvironmentVariableAttribute : EnvironmentFilterAttribute - { - /// - /// Initializes the attribute to filter based on an environment variable with a specific value and comparison method. - /// - /// The name of the environment variable - /// The expected value of the environment variable - /// The string comparison method - public EnvironmentVariableAttribute(string variable, string? value, StringComparison comparison) - : base(new EnvironmentVariableFilter(variable, value, comparison)) { } - - /// - /// Initializes the attribute to filter based on an environment variable with a specific value. - /// - /// The name of the environment variable - /// The expected value of the environment variable - public EnvironmentVariableAttribute(string variable, string? value) - : base(new EnvironmentVariableFilter(variable, value)) { } - - /// - /// Initializes the attribute to filter based on the existence of an environment variable. - /// - /// The name of the environment variable - public EnvironmentVariableAttribute(string variable) - : base(new EnvironmentVariableFilter(variable)) { } - - /// - /// Initializes the attribute to filter based on an environment variable with a specific comparison method. - /// - /// The name of the environment variable - /// The string comparison method - public EnvironmentVariableAttribute(string variable, StringComparison comparison) - : base(new EnvironmentVariableFilter(variable, comparison)) { } - } + /// The Telegram update + /// The update object itself + public override Update? GetFilterringTarget(Update update) + => update; +} + +/// +/// Attribute for filtering updates that occur in debug environment. +/// Only allows updates when the application is running in debug mode. +/// +public class IsDebugEnvironmentAttribute() + : EnvironmentFilterAttribute(new IsDebugEnvironmentFilter()) +{ } + +/// +/// Attribute for filtering updates that occur in release environment. +/// Only allows updates when the application is running in release mode. +/// +public class IsReleaseEnvironmentAttribute() + : EnvironmentFilterAttribute(new IsReleaseEnvironmentFilter()) +{ } + +/// +/// Attribute for filtering updates based on environment variable values. +/// +public class EnvironmentVariableAttribute : EnvironmentFilterAttribute +{ + /// + /// Initializes the attribute to filter based on an environment variable with a specific value and comparison method. + /// + /// The name of the environment variable + /// The expected value of the environment variable + /// The string comparison method + public EnvironmentVariableAttribute(string variable, string? value, StringComparison comparison) + : base(new EnvironmentVariableFilter(variable, value, comparison)) { } + + /// + /// Initializes the attribute to filter based on an environment variable with a specific value. + /// + /// The name of the environment variable + /// The expected value of the environment variable + public EnvironmentVariableAttribute(string variable, string? value) + : base(new EnvironmentVariableFilter(variable, value)) { } + + /// + /// Initializes the attribute to filter based on the existence of an environment variable. + /// + /// The name of the environment variable + public EnvironmentVariableAttribute(string variable) + : base(new EnvironmentVariableFilter(variable)) { } + + /// + /// Initializes the attribute to filter based on an environment variable with a specific comparison method. + /// + /// The name of the environment variable + /// The string comparison method + public EnvironmentVariableAttribute(string variable, StringComparison comparison) + : base(new EnvironmentVariableFilter(variable, comparison)) { } } diff --git a/src/Telegrator/Annotations/MentionedAttribute.cs b/src/Telegrator/Annotations/MentionedAttribute.cs index 76809bd..9d4653a 100644 --- a/src/Telegrator/Annotations/MentionedAttribute.cs +++ b/src/Telegrator/Annotations/MentionedAttribute.cs @@ -1,40 +1,39 @@ using Telegram.Bot.Types.Enums; using Telegrator.Filters; -namespace Telegrator.Annotations +namespace Telegrator.Annotations; + +/// +/// Attribute for filtering messages that contain mentions. +/// Allows handlers to respond only to messages that mention the bot or specific users. +/// +public class MentionedAttribute : MessageFilterAttribute { /// - /// Attribute for filtering messages that contain mentions. - /// Allows handlers to respond only to messages that mention the bot or specific users. + /// Initializes a new instance of the MentionedAttribute that matches any mention. /// - public class MentionedAttribute : MessageFilterAttribute - { - /// - /// Initializes a new instance of the MentionedAttribute that matches any mention. - /// - public MentionedAttribute() - : base(new MessageHasEntityFilter(MessageEntityType.Mention, null, null), new MentionedFilter()) { } + public MentionedAttribute() + : base(new MessageHasEntityFilter(MessageEntityType.Mention, null, null), new MentionedFilter()) { } - /// - /// Initializes a new instance of the MentionedAttribute that matches mentions at a specific offset. - /// - /// The offset position where the mention should occur. - public MentionedAttribute(int offset) - : base(new MessageHasEntityFilter(MessageEntityType.Mention, offset, null), new MentionedFilter()) { } + /// + /// Initializes a new instance of the MentionedAttribute that matches mentions at a specific offset. + /// + /// The offset position where the mention should occur. + public MentionedAttribute(int offset) + : base(new MessageHasEntityFilter(MessageEntityType.Mention, offset, null), new MentionedFilter()) { } - /// - /// Initializes a new instance of the MentionedAttribute that matches a specific mention. - /// - /// The specific mention text to match. - public MentionedAttribute(string mention) - : base(new MessageHasEntityFilter(MessageEntityType.Mention), new MentionedFilter(mention)) { } + /// + /// Initializes a new instance of the MentionedAttribute that matches a specific mention. + /// + /// The specific mention text to match. + public MentionedAttribute(string mention) + : base(new MessageHasEntityFilter(MessageEntityType.Mention), new MentionedFilter(mention)) { } - /// - /// Initializes a new instance of the MentionedAttribute that matches a specific mention at a specific offset. - /// - /// The specific mention text to match. - /// The offset position where the mention should occur. - public MentionedAttribute(string mention, int offset) - : base(new MessageHasEntityFilter(MessageEntityType.Mention, offset, null), new MentionedFilter(mention)) { } - } + /// + /// Initializes a new instance of the MentionedAttribute that matches a specific mention at a specific offset. + /// + /// The specific mention text to match. + /// The offset position where the mention should occur. + public MentionedAttribute(string mention, int offset) + : base(new MessageHasEntityFilter(MessageEntityType.Mention, offset, null), new MentionedFilter(mention)) { } } diff --git a/src/Telegrator/Annotations/MessageChatFilterAttributes.cs b/src/Telegrator/Annotations/MessageChatFilterAttributes.cs index 70c7cc2..face81d 100644 --- a/src/Telegrator/Annotations/MessageChatFilterAttributes.cs +++ b/src/Telegrator/Annotations/MessageChatFilterAttributes.cs @@ -1,105 +1,104 @@ using Telegram.Bot.Types.Enums; using Telegrator.Filters; -namespace Telegrator.Annotations +namespace Telegrator.Annotations; + +/// +/// Attribute for filtering messages sent in forum chats. +/// +public class ChatIsForumAttribute() + : MessageFilterAttribute(new MessageChatIsForumFilter()) +{ } + +/// +/// Attribute for filtering messages sent in a specific chat by ID. +/// +/// The chat ID to match +public class ChatIdAttribute(long id) + : MessageFilterAttribute(new MessageChatIdFilter(id)) +{ } + +/// +/// Attribute for filtering messages sent in chats of a specific type. +/// +public class ChatTypeAttribute : MessageFilterAttribute { /// - /// Attribute for filtering messages sent in forum chats. + /// Initialize new instance of to filter messages from chat from specific chats /// - public class ChatIsForumAttribute() - : MessageFilterAttribute(new MessageChatIsForumFilter()) - { } + /// + public ChatTypeAttribute(ChatType type) + : base(new MessageChatTypeFilter(type)) { } /// - /// Attribute for filtering messages sent in a specific chat by ID. + /// Initialize new instance of to filter messages from chat from specific chats (with flags) /// - /// The chat ID to match - public class ChatIdAttribute(long id) - : MessageFilterAttribute(new MessageChatIdFilter(id)) - { } - - /// - /// Attribute for filtering messages sent in chats of a specific type. - /// - public class ChatTypeAttribute : MessageFilterAttribute - { - /// - /// Initialize new instance of to filter messages from chat from specific chats - /// - /// - public ChatTypeAttribute(ChatType type) - : base(new MessageChatTypeFilter(type)) { } - - /// - /// Initialize new instance of to filter messages from chat from specific chats (with flags) - /// - /// - public ChatTypeAttribute(ChatTypeFlags flags) - : base(new MessageChatTypeFilter(flags)) { } - } - - /// - /// Attribute for filtering messages based on the chat title. - /// - public class ChatTitleAttribute : MessageFilterAttribute - { - /// - /// Initializes the attribute to filter messages from chats with a specific title and comparison method. - /// - /// The chat title to match - /// The string comparison method - public ChatTitleAttribute(string? title, StringComparison comparison) - : base(new MessageChatTitleFilter(title, comparison)) { } - - /// - /// Initializes the attribute to filter messages from chats with a specific title. - /// - /// The chat title to match - public ChatTitleAttribute(string? title) - : base(new MessageChatTitleFilter(title)) { } - } - - /// - /// Attribute for filtering messages based on the chat username. - /// - public class ChatUsernameAttribute : MessageFilterAttribute - { - /// - /// Initializes the attribute to filter messages from chats with a specific username and comparison method. - /// - /// The chat username to match - /// The string comparison method - public ChatUsernameAttribute(string? userName, StringComparison comparison) - : base(new MessageChatUsernameFilter(userName, comparison)) { } - - /// - /// Initializes the attribute to filter messages from chats with a specific username. - /// - /// The chat username to match - public ChatUsernameAttribute(string? userName) - : base(new MessageChatUsernameFilter(userName, StringComparison.InvariantCulture)) { } - } - - /// - /// Attribute for filtering messages based on the chat name (first name and optionally last name). - /// - public class ChatNameAttribute : MessageFilterAttribute - { - /// - /// Initializes the attribute to filter messages from chats with specific first and last names. - /// - /// The first name to match - /// The last name to match (optional) - /// The string comparison method - public ChatNameAttribute(string? firstName, string? lastName, StringComparison comparison) - : base(new MessageChatNameFilter(firstName, lastName, comparison)) { } - - /// - /// Initializes the attribute to filter messages from chats with specific first and last names. - /// - /// The first name to match - /// The last name to match (optional) - public ChatNameAttribute(string? firstName, string? lastName) - : base(new MessageChatNameFilter(firstName, lastName)) { } - } + /// + public ChatTypeAttribute(ChatTypeFlags flags) + : base(new MessageChatTypeFilter(flags)) { } +} + +/// +/// Attribute for filtering messages based on the chat title. +/// +public class ChatTitleAttribute : MessageFilterAttribute +{ + /// + /// Initializes the attribute to filter messages from chats with a specific title and comparison method. + /// + /// The chat title to match + /// The string comparison method + public ChatTitleAttribute(string? title, StringComparison comparison) + : base(new MessageChatTitleFilter(title, comparison)) { } + + /// + /// Initializes the attribute to filter messages from chats with a specific title. + /// + /// The chat title to match + public ChatTitleAttribute(string? title) + : base(new MessageChatTitleFilter(title)) { } +} + +/// +/// Attribute for filtering messages based on the chat username. +/// +public class ChatUsernameAttribute : MessageFilterAttribute +{ + /// + /// Initializes the attribute to filter messages from chats with a specific username and comparison method. + /// + /// The chat username to match + /// The string comparison method + public ChatUsernameAttribute(string? userName, StringComparison comparison) + : base(new MessageChatUsernameFilter(userName, comparison)) { } + + /// + /// Initializes the attribute to filter messages from chats with a specific username. + /// + /// The chat username to match + public ChatUsernameAttribute(string? userName) + : base(new MessageChatUsernameFilter(userName, StringComparison.InvariantCulture)) { } +} + +/// +/// Attribute for filtering messages based on the chat name (first name and optionally last name). +/// +public class ChatNameAttribute : MessageFilterAttribute +{ + /// + /// Initializes the attribute to filter messages from chats with specific first and last names. + /// + /// The first name to match + /// The last name to match (optional) + /// The string comparison method + public ChatNameAttribute(string? firstName, string? lastName, StringComparison comparison) + : base(new MessageChatNameFilter(firstName, lastName, comparison)) { } + + /// + /// Initializes the attribute to filter messages from chats with specific first and last names. + /// + /// The first name to match + /// The last name to match (optional) + public ChatNameAttribute(string? firstName, string? lastName) + : base(new MessageChatNameFilter(firstName, lastName)) { } } diff --git a/src/Telegrator/Annotations/MessageFilterAttributes.cs b/src/Telegrator/Annotations/MessageFilterAttributes.cs index d9bf293..70dacd6 100644 --- a/src/Telegrator/Annotations/MessageFilterAttributes.cs +++ b/src/Telegrator/Annotations/MessageFilterAttributes.cs @@ -5,157 +5,156 @@ using Telegrator.Filters; using Telegrator.Attributes; using Telegrator.Core.Filters; -namespace Telegrator.Annotations +namespace Telegrator.Annotations; + +/// +/// Abstract base attribute for filtering message-based updates. +/// Supports various message types including regular messages, edited messages, channel posts, and business messages. +/// +/// The filters to apply to messages +public abstract class MessageFilterAttribute(params IFilter[] filters) : UpdateFilterAttribute(filters) { /// - /// Abstract base attribute for filtering message-based updates. - /// Supports various message types including regular messages, edited messages, channel posts, and business messages. + /// Gets the allowed update types that this filter can process. /// - /// The filters to apply to messages - public abstract class MessageFilterAttribute(params IFilter[] filters) : UpdateFilterAttribute(filters) - { - /// - /// Gets the allowed update types that this filter can process. - /// - public override UpdateType[] AllowedTypes => - [ - UpdateType.Message, - UpdateType.EditedMessage, - UpdateType.ChannelPost, - UpdateType.EditedChannelPost, - UpdateType.BusinessMessage, - UpdateType.EditedBusinessMessage - ]; + public override UpdateType[] AllowedTypes => + [ + UpdateType.Message, + UpdateType.EditedMessage, + UpdateType.ChannelPost, + UpdateType.EditedChannelPost, + UpdateType.BusinessMessage, + UpdateType.EditedBusinessMessage + ]; - /// - /// Extracts the message from various types of updates. - /// - /// The Telegram update - /// The message from the update, or null if not present - public override Message? GetFilterringTarget(Update update) + /// + /// Extracts the message from various types of updates. + /// + /// The Telegram update + /// The message from the update, or null if not present + public override Message? GetFilterringTarget(Update update) + { + return update switch { - return update switch - { - { Message: { } message } => message, - { EditedMessage: { } message } => message, - { ChannelPost: { } message } => message, - { EditedChannelPost: { } message } => message, - { BusinessMessage: { } message } => message, - { EditedBusinessMessage: { } message } => message, - _ => null - }; - } - } - - /// - /// Attribute for filtering messages based on regular expression patterns. - /// - public class MessageRegexAttribute : MessageFilterAttribute - { - /// - /// Initializes the attribute with a regex pattern and options. - /// - /// The regular expression pattern to match - /// The regex options for matching - public MessageRegexAttribute(string pattern, RegexOptions regexOptions = default) - : base(new MessageRegexFilter(pattern, regexOptions)) { } - - /// - /// Initializes the attribute with a precompiled regex. - /// - /// The precompiled regular expression - public MessageRegexAttribute(Regex regex) - : base(new MessageRegexFilter(regex)) { } - } - - /// - /// Attribute for filtering messages that contain dice throws with specific values. - /// - public class DiceThrowedAttribute : MessageFilterAttribute - { - /// - /// Initializes the attribute to filter dice throws with a specific value. - /// - /// The dice value to match - public DiceThrowedAttribute(int value) - : base(new DiceThrowedFilter(value)) { } - - /// - /// Initializes the attribute to filter dice throws with a specific type and value. - /// - /// The type of dice - /// The dice value to match - public DiceThrowedAttribute(DiceType diceType, int value) - : base(new DiceThrowedFilter(diceType, value)) { } - } - - /// - /// Attribute for filtering messages that are automatically forwarded. - /// - public class IsAutomaticFormwardMessageAttribute() - : MessageFilterAttribute(new IsAutomaticFormwardMessageFilter()) - { } - - /// - /// Attribute for filtering messages sent while the user was offline. - /// - public class IsFromOfflineMessageAttribute() - : MessageFilterAttribute(new IsFromOfflineMessageFilter()) - { } - - /// - /// Attribute for filtering service messages (e.g., user joined, left, etc.). - /// - public class IsServiceMessageMessageAttribute() - : MessageFilterAttribute(new IsServiceMessageMessageFilter()) - { } - - /// - /// Attribute for filtering topic messages in forum chats. - /// - public class IsTopicMessageMessageAttribute() - : MessageFilterAttribute(new IsServiceMessageMessageFilter()) - { } - - /// - /// Attribute for filtering messages based on their entities (mentions, links, etc.). - /// - public class MessageHasEntityAttribute : MessageFilterAttribute - { - /// - /// Initializes the attribute to filter messages with a specific entity type. - /// - /// The entity type to match - public MessageHasEntityAttribute(MessageEntityType type) - : base(new MessageHasEntityFilter(type)) { } - - /// - /// Initializes the attribute to filter messages with a specific entity type at a specific position. - /// - /// The entity type to match - /// The starting position of the entity - /// The length of the entity (optional) - public MessageHasEntityAttribute(MessageEntityType type, int offset, int? length) - : base(new MessageHasEntityFilter(type, offset, length)) { } - - /// - /// Initializes the attribute to filter messages with a specific entity type and content. - /// - /// The entity type to match - /// The content that the entity should contain - /// The string comparison method - public MessageHasEntityAttribute(MessageEntityType type, string content, StringComparison stringComparison = StringComparison.CurrentCulture) - : base(new MessageHasEntityFilter(type, content, stringComparison)) { } - - /// - /// Initializes the attribute to filter messages with a specific entity type, position, and content. - /// - /// The entity type to match - /// The starting position of the entity - /// The length of the entity (optional) - /// The content that the entity should contain - /// The string comparison method - public MessageHasEntityAttribute(MessageEntityType type, int offset, int? length, string content, StringComparison stringComparison = StringComparison.CurrentCulture) - : base(new MessageHasEntityFilter(type, offset, length, content, stringComparison)) { } + { Message: { } message } => message, + { EditedMessage: { } message } => message, + { ChannelPost: { } message } => message, + { EditedChannelPost: { } message } => message, + { BusinessMessage: { } message } => message, + { EditedBusinessMessage: { } message } => message, + _ => null + }; } } + +/// +/// Attribute for filtering messages based on regular expression patterns. +/// +public class MessageRegexAttribute : MessageFilterAttribute +{ + /// + /// Initializes the attribute with a regex pattern and options. + /// + /// The regular expression pattern to match + /// The regex options for matching + public MessageRegexAttribute(string pattern, RegexOptions regexOptions = default) + : base(new MessageRegexFilter(pattern, regexOptions)) { } + + /// + /// Initializes the attribute with a precompiled regex. + /// + /// The precompiled regular expression + public MessageRegexAttribute(Regex regex) + : base(new MessageRegexFilter(regex)) { } +} + +/// +/// Attribute for filtering messages that contain dice throws with specific values. +/// +public class DiceThrowedAttribute : MessageFilterAttribute +{ + /// + /// Initializes the attribute to filter dice throws with a specific value. + /// + /// The dice value to match + public DiceThrowedAttribute(int value) + : base(new DiceThrowedFilter(value)) { } + + /// + /// Initializes the attribute to filter dice throws with a specific type and value. + /// + /// The type of dice + /// The dice value to match + public DiceThrowedAttribute(DiceType diceType, int value) + : base(new DiceThrowedFilter(diceType, value)) { } +} + +/// +/// Attribute for filtering messages that are automatically forwarded. +/// +public class IsAutomaticFormwardMessageAttribute() + : MessageFilterAttribute(new IsAutomaticFormwardMessageFilter()) +{ } + +/// +/// Attribute for filtering messages sent while the user was offline. +/// +public class IsFromOfflineMessageAttribute() + : MessageFilterAttribute(new IsFromOfflineMessageFilter()) +{ } + +/// +/// Attribute for filtering service messages (e.g., user joined, left, etc.). +/// +public class IsServiceMessageMessageAttribute() + : MessageFilterAttribute(new IsServiceMessageMessageFilter()) +{ } + +/// +/// Attribute for filtering topic messages in forum chats. +/// +public class IsTopicMessageMessageAttribute() + : MessageFilterAttribute(new IsServiceMessageMessageFilter()) +{ } + +/// +/// Attribute for filtering messages based on their entities (mentions, links, etc.). +/// +public class MessageHasEntityAttribute : MessageFilterAttribute +{ + /// + /// Initializes the attribute to filter messages with a specific entity type. + /// + /// The entity type to match + public MessageHasEntityAttribute(MessageEntityType type) + : base(new MessageHasEntityFilter(type)) { } + + /// + /// Initializes the attribute to filter messages with a specific entity type at a specific position. + /// + /// The entity type to match + /// The starting position of the entity + /// The length of the entity (optional) + public MessageHasEntityAttribute(MessageEntityType type, int offset, int? length) + : base(new MessageHasEntityFilter(type, offset, length)) { } + + /// + /// Initializes the attribute to filter messages with a specific entity type and content. + /// + /// The entity type to match + /// The content that the entity should contain + /// The string comparison method + public MessageHasEntityAttribute(MessageEntityType type, string content, StringComparison stringComparison = StringComparison.CurrentCulture) + : base(new MessageHasEntityFilter(type, content, stringComparison)) { } + + /// + /// Initializes the attribute to filter messages with a specific entity type, position, and content. + /// + /// The entity type to match + /// The starting position of the entity + /// The length of the entity (optional) + /// The content that the entity should contain + /// The string comparison method + public MessageHasEntityAttribute(MessageEntityType type, int offset, int? length, string content, StringComparison stringComparison = StringComparison.CurrentCulture) + : base(new MessageHasEntityFilter(type, offset, length, content, stringComparison)) { } +} diff --git a/src/Telegrator/Annotations/MessageRepliedAttributes.cs b/src/Telegrator/Annotations/MessageRepliedAttributes.cs index 6d0a075..eef67d0 100644 --- a/src/Telegrator/Annotations/MessageRepliedAttributes.cs +++ b/src/Telegrator/Annotations/MessageRepliedAttributes.cs @@ -1,27 +1,26 @@ using Telegrator.Filters; -namespace Telegrator.Annotations -{ - /// - /// Attribute for filtering messages with reply to messages of this bot. - /// - public class MeRepliedAttribute() - : MessageFilterAttribute(new MeRepliedFilter()) - { } +namespace Telegrator.Annotations; - /// - /// Attribute for checking message's reply chain. - /// - public class HasReplyAttribute(int replyDepth = 1) - : MessageFilterAttribute(new MessageHasReplyFilter(replyDepth)) - { } +/// +/// Attribute for filtering messages with reply to messages of this bot. +/// +public class MeRepliedAttribute() + : MessageFilterAttribute(new MeRepliedFilter()) +{ } - /// - /// Helper filter class for filters that operate on replied messages. - /// Provides functionality to traverse reply chains and access replied message content. - /// - /// - public class FromReplyChainAttribute(int replyDepth = 1) - : MessageFilterAttribute(new FromReplyChainFilter(replyDepth)) - { } -} +/// +/// Attribute for checking message's reply chain. +/// +public class HasReplyAttribute(int replyDepth = 1) + : MessageFilterAttribute(new MessageHasReplyFilter(replyDepth)) +{ } + +/// +/// Helper filter class for filters that operate on replied messages. +/// Provides functionality to traverse reply chains and access replied message content. +/// +/// +public class FromReplyChainAttribute(int replyDepth = 1) + : MessageFilterAttribute(new FromReplyChainFilter(replyDepth)) +{ } diff --git a/src/Telegrator/Annotations/MessageSenderFilterAttributes.cs b/src/Telegrator/Annotations/MessageSenderFilterAttributes.cs index 40ba586..ce9a454 100644 --- a/src/Telegrator/Annotations/MessageSenderFilterAttributes.cs +++ b/src/Telegrator/Annotations/MessageSenderFilterAttributes.cs @@ -1,92 +1,91 @@ using Telegrator.Filters; -namespace Telegrator.Annotations +namespace Telegrator.Annotations; + +/// +/// Attribute for filtering messages based on the sender's username. +/// +public class FromUsernameAttribute : MessageFilterAttribute { /// - /// Attribute for filtering messages based on the sender's username. + /// Initializes the attribute to filter messages from a specific username. /// - public class FromUsernameAttribute : MessageFilterAttribute - { - /// - /// Initializes the attribute to filter messages from a specific username. - /// - /// The username to match - public FromUsernameAttribute(string username) - : base(new FromUsernameFilter(username)) { } - - /// - /// Initializes the attribute to filter messages from a specific username with custom comparison. - /// - /// The username to match - /// The string comparison method - public FromUsernameAttribute(string username, StringComparison comparison) - : base(new FromUsernameFilter(username, comparison)) { } - } + /// The username to match + public FromUsernameAttribute(string username) + : base(new FromUsernameFilter(username)) { } /// - /// Attribute for filtering messages based on the sender's name (first name and optionally last name). + /// Initializes the attribute to filter messages from a specific username with custom comparison. /// - public class FromUserAttribute : MessageFilterAttribute - { - /// - /// Initializes the attribute to filter messages from a user with specific first and last names. - /// - /// The first name to match - /// The last name to match (optional) - /// The string comparison method - public FromUserAttribute(string firstName, string? lastName, StringComparison comparison) - : base(new FromUserFilter(firstName, lastName, comparison)) { } - - /// - /// Initializes the attribute to filter messages from a user with specific first and last names. - /// - /// The first name to match - /// The last name to match - public FromUserAttribute(string firstName, string? lastName) - : base(new FromUserFilter(firstName, lastName, StringComparison.InvariantCulture)) { } - - /// - /// Initializes the attribute to filter messages from a user with a specific first name. - /// - /// The first name to match - public FromUserAttribute(string firstName) - : base(new FromUserFilter(firstName, null, StringComparison.InvariantCulture)) { } - - /// - /// Initializes the attribute to filter messages from a user with a specific first name and custom comparison. - /// - /// The first name to match - /// The string comparison method - public FromUserAttribute(string firstName, StringComparison comparison) - : base(new FromUserFilter(firstName, null, comparison)) { } - } - - /// - /// Attribute for filtering messages from a specific user ID. - /// - /// The user ID to match - public class FromUserIdAttribute(long userId) - : MessageFilterAttribute(new FromUserIdFilter(userId)) - { } - - /// - /// Attribute for filtering messages sent by not bots (users). - /// - public class NotFromBotAttribute() - : MessageFilterAttribute(new FromBotFilter().Not()) - { } - - /// - /// Attribute for filtering messages sent by bots. - /// - public class FromBotAttribute() - : MessageFilterAttribute(new FromBotFilter()) - { } - - /// - /// Attribute for filtering messages sent by premium users. - /// - public class FromPremiumUserAttribute() - : MessageFilterAttribute(new FromPremiumUserFilter()) - { } + /// The username to match + /// The string comparison method + public FromUsernameAttribute(string username, StringComparison comparison) + : base(new FromUsernameFilter(username, comparison)) { } } + +/// +/// Attribute for filtering messages based on the sender's name (first name and optionally last name). +/// +public class FromUserAttribute : MessageFilterAttribute +{ + /// + /// Initializes the attribute to filter messages from a user with specific first and last names. + /// + /// The first name to match + /// The last name to match (optional) + /// The string comparison method + public FromUserAttribute(string firstName, string? lastName, StringComparison comparison) + : base(new FromUserFilter(firstName, lastName, comparison)) { } + + /// + /// Initializes the attribute to filter messages from a user with specific first and last names. + /// + /// The first name to match + /// The last name to match + public FromUserAttribute(string firstName, string? lastName) + : base(new FromUserFilter(firstName, lastName, StringComparison.InvariantCulture)) { } + + /// + /// Initializes the attribute to filter messages from a user with a specific first name. + /// + /// The first name to match + public FromUserAttribute(string firstName) + : base(new FromUserFilter(firstName, null, StringComparison.InvariantCulture)) { } + + /// + /// Initializes the attribute to filter messages from a user with a specific first name and custom comparison. + /// + /// The first name to match + /// The string comparison method + public FromUserAttribute(string firstName, StringComparison comparison) + : base(new FromUserFilter(firstName, null, comparison)) { } +} + +/// +/// Attribute for filtering messages from a specific user ID. +/// +/// The user ID to match +public class FromUserIdAttribute(long userId) + : MessageFilterAttribute(new FromUserIdFilter(userId)) +{ } + +/// +/// Attribute for filtering messages sent by not bots (users). +/// +public class NotFromBotAttribute() + : MessageFilterAttribute(new FromBotFilter().Not()) +{ } + +/// +/// Attribute for filtering messages sent by bots. +/// +public class FromBotAttribute() + : MessageFilterAttribute(new FromBotFilter()) +{ } + +/// +/// Attribute for filtering messages sent by premium users. +/// +public class FromPremiumUserAttribute() + : MessageFilterAttribute(new FromPremiumUserFilter()) +{ } diff --git a/src/Telegrator/Annotations/MessageTextFilterAttributes.cs b/src/Telegrator/Annotations/MessageTextFilterAttributes.cs index 22509a7..2bf8d56 100644 --- a/src/Telegrator/Annotations/MessageTextFilterAttributes.cs +++ b/src/Telegrator/Annotations/MessageTextFilterAttributes.cs @@ -1,58 +1,57 @@ using Telegrator.Filters; -namespace Telegrator.Annotations -{ - /// - /// Attribute for filtering messages where the text starts with the specified content. - /// - /// The string that the message text should start with - /// The string comparison type - public class TextStartsWithAttribute(string content, StringComparison comparison = StringComparison.InvariantCulture) - : MessageFilterAttribute(new TextStartsWithFilter(content, comparison)) - { } +namespace Telegrator.Annotations; - /// - /// Attribute for filtering messages where the text ends with the specified content. - /// - /// The string that the message text should end with - /// The string comparison type - public class TextEndsWithAttribute(string content, StringComparison comparison = StringComparison.InvariantCulture) - : MessageFilterAttribute(new TextEndsWithFilter(content, comparison)) - { } +/// +/// Attribute for filtering messages where the text starts with the specified content. +/// +/// The string that the message text should start with +/// The string comparison type +public class TextStartsWithAttribute(string content, StringComparison comparison = StringComparison.InvariantCulture) + : MessageFilterAttribute(new TextStartsWithFilter(content, comparison)) +{ } - /// - /// Attribute for filtering messages where the text contains the specified content. - /// - /// The string that the message text should contain - /// The string comparison type - public class TextContainsAttribute(string content, StringComparison comparison = StringComparison.InvariantCulture) - : MessageFilterAttribute(new TextContainsFilter(content, comparison)) - { } +/// +/// Attribute for filtering messages where the text ends with the specified content. +/// +/// The string that the message text should end with +/// The string comparison type +public class TextEndsWithAttribute(string content, StringComparison comparison = StringComparison.InvariantCulture) + : MessageFilterAttribute(new TextEndsWithFilter(content, comparison)) +{ } - /// - /// Attribute for filtering messages where the text equals the specified content. - /// - /// The string that the message text should equal - /// The string comparison type - public class TextEqualsAttribute(string content, StringComparison comparison = StringComparison.InvariantCulture) - : MessageFilterAttribute(new TextEqualsFilter(content, comparison)) - { } +/// +/// Attribute for filtering messages where the text contains the specified content. +/// +/// The string that the message text should contain +/// The string comparison type +public class TextContainsAttribute(string content, StringComparison comparison = StringComparison.InvariantCulture) + : MessageFilterAttribute(new TextContainsFilter(content, comparison)) +{ } - /// - /// Attribute for filtering messages that contain any non-empty text. - /// - public class HasTextAttribute() - : MessageFilterAttribute(new TextNotNullOrEmptyFilter()) - { } +/// +/// Attribute for filtering messages where the text equals the specified content. +/// +/// The string that the message text should equal +/// The string comparison type +public class TextEqualsAttribute(string content, StringComparison comparison = StringComparison.InvariantCulture) + : MessageFilterAttribute(new TextEqualsFilter(content, comparison)) +{ } - /// - /// Attribute for filtering messages where the text contains a 'word'. - /// 'Word' must be a separate member of the text, and not have any alphabetic characters next to it. - /// - /// - /// - /// - public class TextContainsWordAttribute(string word, StringComparison comparison = StringComparison.InvariantCulture, int startIndex = 0) - : MessageFilterAttribute(new TextContainsWordFilter(word, comparison, startIndex)) - { } -} +/// +/// Attribute for filtering messages that contain any non-empty text. +/// +public class HasTextAttribute() + : MessageFilterAttribute(new TextNotNullOrEmptyFilter()) +{ } + +/// +/// Attribute for filtering messages where the text contains a 'word'. +/// 'Word' must be a separate member of the text, and not have any alphabetic characters next to it. +/// +/// +/// +/// +public class TextContainsWordAttribute(string word, StringComparison comparison = StringComparison.InvariantCulture, int startIndex = 0) + : MessageFilterAttribute(new TextContainsWordFilter(word, comparison, startIndex)) +{ } diff --git a/src/Telegrator/Annotations/MightAwaitAttribute.cs b/src/Telegrator/Annotations/MightAwaitAttribute.cs index 0d35886..5279190 100644 --- a/src/Telegrator/Annotations/MightAwaitAttribute.cs +++ b/src/Telegrator/Annotations/MightAwaitAttribute.cs @@ -1,27 +1,26 @@ using Telegram.Bot.Types.Enums; -namespace Telegrator.Annotations +namespace Telegrator.Annotations; + +/// +/// Attribute that says if this handler can await some of await types, that is not listed by its handler base. +/// Used for automatic collecting allowed to receiving 's. +/// If you don't use it, you won't be able to await the updates inside handler. +/// +[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)] +public class MightAwaitAttribute : Attribute { + private readonly UpdateType[] _updateTypes; + /// - /// Attribute that says if this handler can await some of await types, that is not listed by its handler base. - /// Used for automatic collecting allowed to receiving 's. - /// If you don't use it, you won't be able to await the updates inside handler. + /// Update types that may be awaited /// - [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)] - public class MightAwaitAttribute : Attribute - { - private readonly UpdateType[] _updateTypes; + public UpdateType[] UpdateTypes => _updateTypes; - /// - /// Update types that may be awaited - /// - public UpdateType[] UpdateTypes => _updateTypes; - - /// - /// main ctor of - /// - /// - public MightAwaitAttribute(params UpdateType[] updateTypes) - => _updateTypes = updateTypes; - } + /// + /// main ctor of + /// + /// + public MightAwaitAttribute(params UpdateType[] updateTypes) + => _updateTypes = updateTypes; } diff --git a/src/Telegrator/Annotations/WelcomeAttribute.cs b/src/Telegrator/Annotations/WelcomeAttribute.cs index 26f39fb..c386bc9 100644 --- a/src/Telegrator/Annotations/WelcomeAttribute.cs +++ b/src/Telegrator/Annotations/WelcomeAttribute.cs @@ -2,22 +2,21 @@ using Telegram.Bot.Types.Enums; using Telegrator.Filters; -namespace Telegrator.Annotations +namespace Telegrator.Annotations; + +/// +/// Attribute for filtering message with command "start" in bot's private chats. +/// Allows handlers to respond to "welcome" bot commands. +/// +public class WelcomeAttribute : MessageFilterAttribute { /// - /// Attribute for filtering message with command "start" in bot's private chats. - /// Allows handlers to respond to "welcome" bot commands. + /// Creates new instance of /// - public class WelcomeAttribute : MessageFilterAttribute - { - /// - /// Creates new instance of - /// - /// - public WelcomeAttribute(bool onlyFirst = false) : base( - new MessageChatTypeFilter(ChatType.Private), - new CommandAlliasFilter("start"), - Filter.If(ctx => !onlyFirst || ctx.Input.Id == 0)) - { } - } + /// + public WelcomeAttribute(bool onlyFirst = false) : base( + new MessageChatTypeFilter(ChatType.Private), + new CommandAlliasFilter("start"), + Filter.If(ctx => !onlyFirst || ctx.Input.Id == 0)) + { } } diff --git a/src/Telegrator/Aspects/AfterExecutionAttribute.cs b/src/Telegrator/Aspects/AfterExecutionAttribute.cs index 7835048..923a42d 100644 --- a/src/Telegrator/Aspects/AfterExecutionAttribute.cs +++ b/src/Telegrator/Aspects/AfterExecutionAttribute.cs @@ -1,16 +1,15 @@ -namespace Telegrator.Aspects +namespace Telegrator.Aspects; + +/// +/// Attribute that specifies a post-execution processor to be executed after the handler. +/// The processor type must implement interface. +/// +/// The type of the post-processor that implements . +[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] +public class AfterExecutionAttribute : Attribute where T : IPostProcessor { /// - /// Attribute that specifies a post-execution processor to be executed after the handler. - /// The processor type must implement interface. + /// Gets the type of the post-processor. /// - /// The type of the post-processor that implements . - [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] - public class AfterExecutionAttribute : Attribute where T : IPostProcessor - { - /// - /// Gets the type of the post-processor. - /// - public Type ProcessorType => typeof(T); - } + public Type ProcessorType => typeof(T); } diff --git a/src/Telegrator/Aspects/BeforeExecutionAttribute.cs b/src/Telegrator/Aspects/BeforeExecutionAttribute.cs index 0d5bbbf..fdf7fb5 100644 --- a/src/Telegrator/Aspects/BeforeExecutionAttribute.cs +++ b/src/Telegrator/Aspects/BeforeExecutionAttribute.cs @@ -1,16 +1,15 @@ -namespace Telegrator.Aspects +namespace Telegrator.Aspects; + +/// +/// Attribute that specifies a pre-execution processor to be executed before the handler. +/// The processor type must implement interface. +/// +/// The type of the pre-processor that implements . +[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] +public class BeforeExecutionAttribute : Attribute where T : IPreProcessor { /// - /// Attribute that specifies a pre-execution processor to be executed before the handler. - /// The processor type must implement interface. + /// Gets the type of the pre-processor. /// - /// The type of the pre-processor that implements . - [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] - public class BeforeExecutionAttribute : Attribute where T : IPreProcessor - { - /// - /// Gets the type of the pre-processor. - /// - public Type ProcessorType => typeof(T); - } + public Type ProcessorType => typeof(T); } diff --git a/src/Telegrator/Aspects/IPostProcessor.cs.cs b/src/Telegrator/Aspects/IPostProcessor.cs.cs index 2e91e31..5a221d0 100644 --- a/src/Telegrator/Aspects/IPostProcessor.cs.cs +++ b/src/Telegrator/Aspects/IPostProcessor.cs.cs @@ -1,19 +1,18 @@ using Telegrator.Core.Handlers; -namespace Telegrator.Aspects +namespace Telegrator.Aspects; + +/// +/// Interface for post-execution processors that are executed after handler execution. +/// Implement this interface to add cross-cutting concerns like logging, cleanup, or metrics collection. +/// +public interface IPostProcessor { /// - /// Interface for post-execution processors that are executed after handler execution. - /// Implement this interface to add cross-cutting concerns like logging, cleanup, or metrics collection. + /// Executes after the handler's main execution logic. /// - public interface IPostProcessor - { - /// - /// Executes after the handler's main execution logic. - /// - /// The handler container containing the current update and context. - /// - /// A indicating the final execution result. - public Task AfterExecution(IHandlerContainer container, CancellationToken cancellationToken); - } + /// The handler container containing the current update and context. + /// + /// A indicating the final execution result. + public Task AfterExecution(IHandlerContainer container, CancellationToken cancellationToken); } diff --git a/src/Telegrator/Aspects/IPreProcessor.cs b/src/Telegrator/Aspects/IPreProcessor.cs index 73ec197..5350b28 100644 --- a/src/Telegrator/Aspects/IPreProcessor.cs +++ b/src/Telegrator/Aspects/IPreProcessor.cs @@ -1,19 +1,18 @@ using Telegrator.Core.Handlers; -namespace Telegrator.Aspects +namespace Telegrator.Aspects; + +/// +/// Interface for pre-execution processors that are executed before handler execution. +/// Implement this interface to add cross-cutting concerns like validation, logging, or authorization. +/// +public interface IPreProcessor { /// - /// Interface for pre-execution processors that are executed before handler execution. - /// Implement this interface to add cross-cutting concerns like validation, logging, or authorization. + /// Executes before the handler's main execution logic. /// - public interface IPreProcessor - { - /// - /// Executes before the handler's main execution logic. - /// - /// The handler container containing the current update and context. - /// - /// A indicating whether execution should continue or be stopped. - public Task BeforeExecution(IHandlerContainer container, CancellationToken cancellationToken = default); - } + /// The handler container containing the current update and context. + /// + /// A indicating whether execution should continue or be stopped. + public Task BeforeExecution(IHandlerContainer container, CancellationToken cancellationToken = default); } diff --git a/src/Telegrator/Attributes/FilterAnnotation.cs b/src/Telegrator/Attributes/FilterAnnotation.cs index 187ce95..374d47f 100644 --- a/src/Telegrator/Attributes/FilterAnnotation.cs +++ b/src/Telegrator/Attributes/FilterAnnotation.cs @@ -3,37 +3,36 @@ using Telegram.Bot.Types.Enums; using Telegrator.Core.Filters; using Telegrator.Filters; -namespace Telegrator.Attributes +namespace Telegrator.Attributes; + +/// +/// Reactive way to implement a new of type +/// +/// +public abstract class FilterAnnotation : UpdateFilterAttribute, IFilter, INamedFilter where T : class { + /// + public virtual bool IsCollectible { get; } = false; + + /// + public override UpdateType[] AllowedTypes { get; } = typeof(T).GetAllowedUpdateTypes(); + + /// + public string Name => GetType().Name; + /// - /// Reactive way to implement a new of type + /// Initializes new instance of /// - /// - public abstract class FilterAnnotation : UpdateFilterAttribute, IFilter, INamedFilter where T : class + public FilterAnnotation() : base() { - /// - public virtual bool IsCollectible { get; } = false; - - /// - public override UpdateType[] AllowedTypes { get; } = typeof(T).GetAllowedUpdateTypes(); - - /// - public string Name => GetType().Name; - - /// - /// Initializes new instance of - /// - public FilterAnnotation() : base() - { - UpdateFilter = Filter.If(CanPass); - AnonymousFilter = AnonymousTypeFilter.Compile(UpdateFilter, GetFilterringTarget); - } - - /// - public override T? GetFilterringTarget(Update update) - => update.GetActualUpdateObject(); - - /// - public abstract bool CanPass(FilterExecutionContext context); + UpdateFilter = Filter.If(CanPass); + AnonymousFilter = AnonymousTypeFilter.Compile(UpdateFilter, GetFilterringTarget); } + + /// + public override T? GetFilterringTarget(Update update) + => update.GetActualUpdateObject(); + + /// + public abstract bool CanPass(FilterExecutionContext context); } diff --git a/src/Telegrator/Attributes/FilterModifier.cs b/src/Telegrator/Attributes/FilterModifier.cs index b25c89d..0a4332b 100644 --- a/src/Telegrator/Attributes/FilterModifier.cs +++ b/src/Telegrator/Attributes/FilterModifier.cs @@ -1,25 +1,24 @@ -namespace Telegrator.Attributes +namespace Telegrator.Attributes; + +/// +/// Enumeration of filter modifiers that can be applied to update filters. +/// Defines how filters should be combined and applied in filter chains. +/// +[Flags] +public enum FilterModifier { /// - /// Enumeration of filter modifiers that can be applied to update filters. - /// Defines how filters should be combined and applied in filter chains. + /// No modifier applied. Filter is applied as-is. /// - [Flags] - public enum FilterModifier - { - /// - /// No modifier applied. Filter is applied as-is. - /// - None = 1, - - /// - /// OR modifier. This filter or the next filter in the chain should match. - /// - OrNext = 2, - - /// - /// NOT modifier. The inverse of this filter should match. - /// - Not = 4, - } + None = 1, + + /// + /// OR modifier. This filter or the next filter in the chain should match. + /// + OrNext = 2, + + /// + /// NOT modifier. The inverse of this filter should match. + /// + Not = 4, } diff --git a/src/Telegrator/Attributes/UpdateFilterAttribute.cs b/src/Telegrator/Attributes/UpdateFilterAttribute.cs index 492f069..1932b34 100644 --- a/src/Telegrator/Attributes/UpdateFilterAttribute.cs +++ b/src/Telegrator/Attributes/UpdateFilterAttribute.cs @@ -3,83 +3,82 @@ using Telegrator.Core.Attributes; using Telegrator.Core.Filters; using Telegrator.Filters; -namespace Telegrator.Attributes +namespace Telegrator.Attributes; + +/// +/// Abstract base attribute for defining update filters for a specific type of update target. +/// Provides logic for filter composition, modifier processing, and target extraction. +/// +/// The type of the update target to filter (e.g., Message, Update). +public abstract class UpdateFilterAttribute : UpdateFilterAttributeBase where T : class { /// - /// Abstract base attribute for defining update filters for a specific type of update target. - /// Provides logic for filter composition, modifier processing, and target extraction. + /// Gets the compiled anonymous filter for this attribute. /// - /// The type of the update target to filter (e.g., Message, Update). - public abstract class UpdateFilterAttribute : UpdateFilterAttributeBase where T : class + public override IFilter AnonymousFilter { get; protected set; } + + /// + /// Gets the compiled filter logic for the update target. + /// + public IFilter UpdateFilter { get; protected set; } + + /// + /// Empty constructor for internal using + /// + internal UpdateFilterAttribute() { - /// - /// Gets the compiled anonymous filter for this attribute. - /// - public override IFilter AnonymousFilter { get; protected set; } - - /// - /// Gets the compiled filter logic for the update target. - /// - public IFilter UpdateFilter { get; protected set; } - - /// - /// Empty constructor for internal using - /// - internal UpdateFilterAttribute() - { - AnonymousFilter = null!; - UpdateFilter = null!; - _ = 0xBAD + 0xC0DE; - } - - /// - /// Initializes the attribute with one or more filters for the update target. - /// - /// The filters to compose - protected UpdateFilterAttribute(params IFilter[] filters) - { - string name = GetType().Name; - UpdateFilter = new CompiledFilter(name, filters); - AnonymousFilter = AnonymousTypeFilter.Compile(name, UpdateFilter, GetFilterringTarget); - } - - /// - /// Initializes the attribute with a precompiled filter for the update target. - /// - /// The compiled filter - protected UpdateFilterAttribute(IFilter updateFilter) - { - string name = GetType().Name; - UpdateFilter = updateFilter; - AnonymousFilter = AnonymousTypeFilter.Compile(name, UpdateFilter, GetFilterringTarget); - } - - /// - /// Processes filter modifiers and combines this filter with the previous one if needed. - /// - /// The previous filter attribute in the chain - /// True if the OrNext modifier is set; otherwise, false. - public override sealed bool ProcessModifiers(UpdateFilterAttributeBase? previous) - { - if (Modifiers.HasFlag(FilterModifier.Not)) - AnonymousFilter = Filter.Not(AnonymousFilter); - - if (previous is not null) - { - if (previous.Modifiers.HasFlag(FilterModifier.OrNext)) - { - AnonymousFilter = Filter.Or(previous.AnonymousFilter, AnonymousFilter); - } - } - - return Modifiers.HasFlag(FilterModifier.OrNext); - } - - /// - /// Extracts the filtering target of type from the given update. - /// - /// The Telegram update - /// The target object to filter, or null if not applicable - public abstract T? GetFilterringTarget(Update update); + AnonymousFilter = null!; + UpdateFilter = null!; + _ = 0xBAD + 0xC0DE; } + + /// + /// Initializes the attribute with one or more filters for the update target. + /// + /// The filters to compose + protected UpdateFilterAttribute(params IFilter[] filters) + { + string name = GetType().Name; + UpdateFilter = new CompiledFilter(name, filters); + AnonymousFilter = AnonymousTypeFilter.Compile(name, UpdateFilter, GetFilterringTarget); + } + + /// + /// Initializes the attribute with a precompiled filter for the update target. + /// + /// The compiled filter + protected UpdateFilterAttribute(IFilter updateFilter) + { + string name = GetType().Name; + UpdateFilter = updateFilter; + AnonymousFilter = AnonymousTypeFilter.Compile(name, UpdateFilter, GetFilterringTarget); + } + + /// + /// Processes filter modifiers and combines this filter with the previous one if needed. + /// + /// The previous filter attribute in the chain + /// True if the OrNext modifier is set; otherwise, false. + public override sealed bool ProcessModifiers(UpdateFilterAttributeBase? previous) + { + if (Modifiers.HasFlag(FilterModifier.Not)) + AnonymousFilter = Filter.Not(AnonymousFilter); + + if (previous is not null) + { + if (previous.Modifiers.HasFlag(FilterModifier.OrNext)) + { + AnonymousFilter = Filter.Or(previous.AnonymousFilter, AnonymousFilter); + } + } + + return Modifiers.HasFlag(FilterModifier.OrNext); + } + + /// + /// Extracts the filtering target of type from the given update. + /// + /// The Telegram update + /// The target object to filter, or null if not applicable + public abstract T? GetFilterringTarget(Update update); } diff --git a/src/Telegrator/Attributes/UpdateHandlerAttribute.cs b/src/Telegrator/Attributes/UpdateHandlerAttribute.cs index 0a96103..b62525b 100644 --- a/src/Telegrator/Attributes/UpdateHandlerAttribute.cs +++ b/src/Telegrator/Attributes/UpdateHandlerAttribute.cs @@ -2,45 +2,44 @@ using Telegrator.Core.Attributes; using Telegrator.Core.Handlers; -namespace Telegrator.Attributes +namespace Telegrator.Attributes; + +/// +/// Abstract base attribute for marking update handler classes. +/// Provides a type-safe way to associate handler types with specific update types and importance settings. +/// +/// The type of the update handler that this attribute is applied to. +public abstract class UpdateHandlerAttribute : UpdateHandlerAttributeBase where T : UpdateHandlerBase { /// - /// Abstract base attribute for marking update handler classes. - /// Provides a type-safe way to associate handler types with specific update types and importance settings. + /// Initializes new instance of /// - /// The type of the update handler that this attribute is applied to. - public abstract class UpdateHandlerAttribute : UpdateHandlerAttributeBase where T : UpdateHandlerBase - { - /// - /// Initializes new instance of - /// - /// The type of update that this handler can process. - protected UpdateHandlerAttribute(UpdateType updateType) - : base([typeof(T)], updateType, 0) { } + /// The type of update that this handler can process. + protected UpdateHandlerAttribute(UpdateType updateType) + : base([typeof(T)], updateType, 0) { } - /// - /// Initializes new instance of - /// - /// The type of update that this handler can process. - /// The importance level for this handler - protected UpdateHandlerAttribute(UpdateType updateType, int importance) - : base([typeof(T)], updateType, importance) { } + /// + /// Initializes new instance of + /// + /// The type of update that this handler can process. + /// The importance level for this handler + protected UpdateHandlerAttribute(UpdateType updateType, int importance) + : base([typeof(T)], updateType, importance) { } - /// - /// Initializes new instance of - /// - /// Additional suported types. - /// The type of update that this handler can process. - protected UpdateHandlerAttribute(Type[] types, UpdateType updateType) - : base([..types, typeof(T)], updateType, 0) { } + /// + /// Initializes new instance of + /// + /// Additional suported types. + /// The type of update that this handler can process. + protected UpdateHandlerAttribute(Type[] types, UpdateType updateType) + : base([..types, typeof(T)], updateType, 0) { } - /// - /// Initializes new instance of - /// - /// Additional suported types. - /// The type of update that this handler can process. - /// The importance level for this handler - protected UpdateHandlerAttribute(Type[] types, UpdateType updateType, int importance) - : base([.. types, typeof(T)], updateType, importance) { } - } + /// + /// Initializes new instance of + /// + /// Additional suported types. + /// The type of update that this handler can process. + /// The importance level for this handler + protected UpdateHandlerAttribute(Type[] types, UpdateType updateType, int importance) + : base([.. types, typeof(T)], updateType, importance) { } } diff --git a/src/Telegrator/Core/Attributes/UpdateFilterAttributeBase.cs b/src/Telegrator/Core/Attributes/UpdateFilterAttributeBase.cs index 2e79b38..cab76ae 100644 --- a/src/Telegrator/Core/Attributes/UpdateFilterAttributeBase.cs +++ b/src/Telegrator/Core/Attributes/UpdateFilterAttributeBase.cs @@ -5,43 +5,42 @@ using Telegrator.Core.Filters; using Telegrator.Core.Handlers; using Telegrator.Filters; -namespace Telegrator.Core.Attributes +namespace Telegrator.Core.Attributes; + +/// +/// Defines the to validation for entry into execution of the +/// +[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)] +public abstract class UpdateFilterAttributeBase : Attribute { /// - /// Defines the to validation for entry into execution of the + /// Gets the 's that processing /// - [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)] - public abstract class UpdateFilterAttributeBase : Attribute + public abstract UpdateType[] AllowedTypes { get; } + + /// + /// Gets the that processing + /// + public abstract IFilter AnonymousFilter { get; protected set; } + + /// + /// Gets or sets the filter modifiers that affect how this filter is combined with others. + /// + public FilterModifier Modifiers { get; set; } + + /// + /// Creates a new instance of + /// + /// + protected internal UpdateFilterAttributeBase() { - /// - /// Gets the 's that processing - /// - public abstract UpdateType[] AllowedTypes { get; } - - /// - /// Gets the that processing - /// - public abstract IFilter AnonymousFilter { get; protected set; } - - /// - /// Gets or sets the filter modifiers that affect how this filter is combined with others. - /// - public FilterModifier Modifiers { get; set; } - - /// - /// Creates a new instance of - /// - /// - protected internal UpdateFilterAttributeBase() - { - if (AllowedTypes.Length == 0) - throw new ArgumentException(); - } - - /// - /// Determines the logic of filter modifiers. Exceptionally internal implementation - /// - /// - public abstract bool ProcessModifiers(UpdateFilterAttributeBase? previous); + if (AllowedTypes.Length == 0) + throw new ArgumentException(); } + + /// + /// Determines the logic of filter modifiers. Exceptionally internal implementation + /// + /// + public abstract bool ProcessModifiers(UpdateFilterAttributeBase? previous); } diff --git a/src/Telegrator/Core/Attributes/UpdateHandlerAttributeBase.cs b/src/Telegrator/Core/Attributes/UpdateHandlerAttributeBase.cs index 851a314..c55fb8b 100644 --- a/src/Telegrator/Core/Attributes/UpdateHandlerAttributeBase.cs +++ b/src/Telegrator/Core/Attributes/UpdateHandlerAttributeBase.cs @@ -4,79 +4,78 @@ using Telegrator.Core.Descriptors; using Telegrator.Core.Filters; using Telegrator.Core.Handlers; -namespace Telegrator.Core.Attributes +namespace Telegrator.Core.Attributes; + +/// +/// Defines the 's and validator () of the that will process +/// +[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)] +public abstract class UpdateHandlerAttributeBase : Attribute, IFilter { + /// + public bool IsCollectible => GetType().HasPublicProperties(); + /// - /// Defines the 's and validator () of the that will process + /// Gets an array of that this attribute can be attached to /// - [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)] - public abstract class UpdateHandlerAttributeBase : Attribute, IFilter + public Type[] ExpectingHandlerType { get; private set; } + + /// + /// Gets an that handlers processes + /// + public UpdateType Type { get; private set; } + + /// + /// Gets or sets importance of this in same pool + /// + public int Importance { get; set; } + + /// + /// Gets or sets priority of this in same type handlers pool + /// + public int Priority { get; set; } + + /// + /// Gets or sets a value indicating whether to form a fallback report. + /// + public bool FormReport { get; set; } + + /// + /// Creates a new instance of + /// + /// The types of handlers that this attribute can be applied to. + /// The type of update that this handler processes. + /// The importance level of this handler (default: 0). + /// Thrown when is null. + /// Thrown when one of the handler types is not a valid handler type. + /// Thrown when is . + protected internal UpdateHandlerAttributeBase(Type[] expectingHandlerType, UpdateType updateType, int importance = 0) { - /// - public bool IsCollectible => GetType().HasPublicProperties(); - - /// - /// Gets an array of that this attribute can be attached to - /// - public Type[] ExpectingHandlerType { get; private set; } + if (expectingHandlerType == null) + throw new ArgumentNullException(nameof(expectingHandlerType)); - /// - /// Gets an that handlers processes - /// - public UpdateType Type { get; private set; } - - /// - /// Gets or sets importance of this in same pool - /// - public int Importance { get; set; } + if (expectingHandlerType.Any(type => !type.IsHandlerAbstract())) + throw new ArgumentException("One of expectingHandlerType is not a handler type", nameof(expectingHandlerType)); - /// - /// Gets or sets priority of this in same type handlers pool - /// - public int Priority { get; set; } - - /// - /// Gets or sets a value indicating whether to form a fallback report. - /// - public bool FormReport { get; set; } + if (updateType == UpdateType.Unknown) + throw new Exception(); - /// - /// Creates a new instance of - /// - /// The types of handlers that this attribute can be applied to. - /// The type of update that this handler processes. - /// The importance level of this handler (default: 0). - /// Thrown when is null. - /// Thrown when one of the handler types is not a valid handler type. - /// Thrown when is . - protected internal UpdateHandlerAttributeBase(Type[] expectingHandlerType, UpdateType updateType, int importance = 0) - { - if (expectingHandlerType == null) - throw new ArgumentNullException(nameof(expectingHandlerType)); - - if (expectingHandlerType.Any(type => !type.IsHandlerAbstract())) - throw new ArgumentException("One of expectingHandlerType is not a handler type", nameof(expectingHandlerType)); - - if (updateType == UpdateType.Unknown) - throw new Exception(); - - ExpectingHandlerType = expectingHandlerType; - Type = updateType; - Importance = importance; - } - - /// - /// Gets an of this from and - /// - /// A descriptor indexer for this handler attribute. - public DescriptorIndexer GetIndexer() - => new DescriptorIndexer(0, this); - - /// - /// Validator () of the that will process - /// - /// The filter execution context containing the update to validate. - /// True if the update passes validation; otherwise, false. - public abstract bool CanPass(FilterExecutionContext context); + ExpectingHandlerType = expectingHandlerType; + Type = updateType; + Importance = importance; } + + /// + /// Gets an of this from and + /// + /// A descriptor indexer for this handler attribute. + public DescriptorIndexer GetIndexer() + => new DescriptorIndexer(0, this); + + /// + /// Validator () of the that will process + /// + /// The filter execution context containing the update to validate. + /// True if the update passes validation; otherwise, false. + public abstract bool CanPass(FilterExecutionContext context); } diff --git a/src/Telegrator/Core/Descriptors/DefaultCustomDescriptors.cs b/src/Telegrator/Core/Descriptors/DefaultCustomDescriptors.cs index 7e72539..9ab158a 100644 --- a/src/Telegrator/Core/Descriptors/DefaultCustomDescriptors.cs +++ b/src/Telegrator/Core/Descriptors/DefaultCustomDescriptors.cs @@ -7,63 +7,62 @@ using Telegrator.Core.Handlers; using Telegrator.Handlers; using Telegrator.Handlers.Building; -namespace Telegrator.Core.Descriptors +namespace Telegrator.Core.Descriptors; + +/// +/// Descriptor for creating handlers from methods +/// +/// +public class MethodHandlerDescriptor : HandlerDescriptor where TUpdate : class { + private readonly MethodInfo Method; + /// - /// Descriptor for creating handlers from methods + /// Initializes new instance of /// - /// - public class MethodHandlerDescriptor : HandlerDescriptor where TUpdate : class + /// + public MethodHandlerDescriptor(AbstractHandlerAction action) : base(DescriptorType.General, typeof(MethodHandler), true) { - private readonly MethodInfo Method; + UpdateHandlerAttributeBase handlerAttribute = HandlerInspector.GetHandlerAttribute(action.Method); + IFilter? stateKeeperAttribute = HandlerInspector.GetStateKeeperAttribute(action.Method); + IFilter[] filters = HandlerInspector.GetFilterAttributes(action.Method, handlerAttribute.Type).ToArray(); - /// - /// Initializes new instance of - /// - /// - public MethodHandlerDescriptor(AbstractHandlerAction action) : base(DescriptorType.General, typeof(MethodHandler), true) + UpdateType = handlerAttribute.Type; + Indexer = handlerAttribute.GetIndexer(); + Filters = new DescriptorFiltersSet(handlerAttribute, stateKeeperAttribute, filters); + DisplayString = HandlerInspector.GetDisplayName(action.Method) ?? action.Method.Name; + Method = action.Method; + InstanceFactory = () => new MethodHandler(UpdateType); + LazyInitialization = handler => { - UpdateHandlerAttributeBase handlerAttribute = HandlerInspector.GetHandlerAttribute(action.Method); - IFilter? stateKeeperAttribute = HandlerInspector.GetStateKeeperAttribute(action.Method); - IFilter[] filters = HandlerInspector.GetFilterAttributes(action.Method, handlerAttribute.Type).ToArray(); + if (handler is not MethodHandler methodHandler) + throw new InvalidDataException(); - UpdateType = handlerAttribute.Type; - Indexer = handlerAttribute.GetIndexer(); - Filters = new DescriptorFiltersSet(handlerAttribute, stateKeeperAttribute, filters); - DisplayString = HandlerInspector.GetDisplayName(action.Method) ?? action.Method.Name; - Method = action.Method; - InstanceFactory = () => new MethodHandler(UpdateType); - LazyInitialization = handler => - { - if (handler is not MethodHandler methodHandler) - throw new InvalidDataException(); + methodHandler.Method = Method; + }; + } - methodHandler.Method = Method; - }; - } + private class MethodHandler(UpdateType updateType) : AbstractUpdateHandler(updateType) + { + internal MethodInfo Method = null!; - private class MethodHandler(UpdateType updateType) : AbstractUpdateHandler(updateType) + public override async Task Execute(IHandlerContainer container, CancellationToken cancellation) { - internal MethodInfo Method = null!; + if (Method is null) + throw new Exception(); - public override async Task Execute(IHandlerContainer container, CancellationToken cancellation) + if (Method.ReturnType == typeof(void)) { - if (Method is null) - throw new Exception(); + Method.Invoke(this, [container, cancellation]); + return Result.Ok(); + } + else + { + object branchReturn = Method.Invoke(this, [container, cancellation]); + if (branchReturn is not Task branchTask) + throw new InvalidOperationException(); - if (Method.ReturnType == typeof(void)) - { - Method.Invoke(this, [container, cancellation]); - return Result.Ok(); - } - else - { - object branchReturn = Method.Invoke(this, [container, cancellation]); - if (branchReturn is not Task branchTask) - throw new InvalidOperationException(); - - return await branchTask; - } + return await branchTask; } } } diff --git a/src/Telegrator/Core/Descriptors/DescribedHandlerDescriptor.cs b/src/Telegrator/Core/Descriptors/DescribedHandlerDescriptor.cs index 50b2565..a0e5bbf 100644 --- a/src/Telegrator/Core/Descriptors/DescribedHandlerDescriptor.cs +++ b/src/Telegrator/Core/Descriptors/DescribedHandlerDescriptor.cs @@ -4,134 +4,133 @@ using Telegrator.Core.Filters; using Telegrator.Core.Handlers; using Telegrator.Core.States; -namespace Telegrator.Core.Descriptors +namespace Telegrator.Core.Descriptors; + +/// +/// Contains information about a described handler, including its context, client, and execution logic. +/// +public class DescribedHandlerDescriptor { + private readonly ManualResetEventSlim ResetEvent = new ManualResetEventSlim(false); + /// - /// Contains information about a described handler, including its context, client, and execution logic. + /// Descriptor from that handler was described from. /// - public class DescribedHandlerDescriptor + public HandlerDescriptor From { get; } + + /// + /// The update router associated with this handler. + /// + public IUpdateRouter UpdateRouter { get; } + + /// + /// The awaiting provider to fetch new updates inside handler + /// + public IAwaitingProvider AwaitingProvider { get; } + + /// + /// The state storage to handling state machines + /// + public IStateStorage StateStorage { get; } + + /// + /// The Telegram bot client used for this handler. + /// + public ITelegramBotClient Client { get; } + + /// + /// The handler instance being described. + /// + public UpdateHandlerBase HandlerInstance { get; } + + /// + /// Extra data associated with the handler execution. + /// + public Dictionary ExtraData { get; } + + /// + /// List of completed filters for this handler. + /// + public CompletedFiltersList CompletedFilters { get; } + + /// + /// The update being handled. + /// + public Update HandlingUpdate { get; } + + /// + /// Lifetime token for the handler instance. + /// + public HandlerLifetimeToken HandlerLifetime => HandlerInstance.LifetimeToken; + + /// + /// Display string for the handler (for debugging or logging). + /// + public string DisplayString { get; set; } + + /// + /// The final execution result. + /// + public Result? Result { get; private set; } + + /// + /// Initializes a new instance of the class. + /// + /// The descriptor from which this handler was described. + /// The update router. + /// The awaiting provider. + /// The state storage. + /// The Telegram bot client. + /// The handler instance. + /// The filter execution context. + /// Optional display string. + public DescribedHandlerDescriptor( + HandlerDescriptor fromDescriptor, + IUpdateRouter updateRouter, + IAwaitingProvider awaitingProvider, + IStateStorage stateStorage, + ITelegramBotClient client, + UpdateHandlerBase handlerInstance, + FilterExecutionContext filterContext, + string? displayString) { - private readonly ManualResetEventSlim ResetEvent = new ManualResetEventSlim(false); - - /// - /// Descriptor from that handler was described from. - /// - public HandlerDescriptor From { get; } - - /// - /// The update router associated with this handler. - /// - public IUpdateRouter UpdateRouter { get; } - - /// - /// The awaiting provider to fetch new updates inside handler - /// - public IAwaitingProvider AwaitingProvider { get; } - - /// - /// The state storage to handling state machines - /// - public IStateStorage StateStorage { get; } - - /// - /// The Telegram bot client used for this handler. - /// - public ITelegramBotClient Client { get; } - - /// - /// The handler instance being described. - /// - public UpdateHandlerBase HandlerInstance { get; } - - /// - /// Extra data associated with the handler execution. - /// - public Dictionary ExtraData { get; } - - /// - /// List of completed filters for this handler. - /// - public CompletedFiltersList CompletedFilters { get; } - - /// - /// The update being handled. - /// - public Update HandlingUpdate { get; } - - /// - /// Lifetime token for the handler instance. - /// - public HandlerLifetimeToken HandlerLifetime => HandlerInstance.LifetimeToken; - - /// - /// Display string for the handler (for debugging or logging). - /// - public string DisplayString { get; set; } - - /// - /// The final execution result. - /// - public Result? Result { get; private set; } - - /// - /// Initializes a new instance of the class. - /// - /// The descriptor from which this handler was described. - /// The update router. - /// The awaiting provider. - /// The state storage. - /// The Telegram bot client. - /// The handler instance. - /// The filter execution context. - /// Optional display string. - public DescribedHandlerDescriptor( - HandlerDescriptor fromDescriptor, - IUpdateRouter updateRouter, - IAwaitingProvider awaitingProvider, - IStateStorage stateStorage, - ITelegramBotClient client, - UpdateHandlerBase handlerInstance, - FilterExecutionContext filterContext, - string? displayString) - { - From = fromDescriptor; - UpdateRouter = updateRouter; - AwaitingProvider = awaitingProvider; - StateStorage = stateStorage; - Client = client; - HandlerInstance = handlerInstance; - ExtraData = filterContext.Data; - CompletedFilters = filterContext.CompletedFilters; - HandlingUpdate = filterContext.Update; - DisplayString = displayString ?? fromDescriptor.HandlerType.Name; - } - - /// - /// Waits for the handler execution result. - /// - /// The cancellation token. - public async Task AwaitResult(CancellationToken cancellationToken) - { - await Task.Yield(); - ResetEvent.Reset(); - ResetEvent.Wait(cancellationToken); - } - - /// - /// Reports the execution result and signals completion. - /// - /// The execution result. - public void ReportResult(Result? result) - { - if (Result != null) - throw new InvalidOperationException("Result already reported"); - - Result = result; - ResetEvent.Set(); - } - - /// - public override string ToString() - => DisplayString ?? From.HandlerType.Name; + From = fromDescriptor; + UpdateRouter = updateRouter; + AwaitingProvider = awaitingProvider; + StateStorage = stateStorage; + Client = client; + HandlerInstance = handlerInstance; + ExtraData = filterContext.Data; + CompletedFilters = filterContext.CompletedFilters; + HandlingUpdate = filterContext.Update; + DisplayString = displayString ?? fromDescriptor.HandlerType.Name; } + + /// + /// Waits for the handler execution result. + /// + /// The cancellation token. + public async Task AwaitResult(CancellationToken cancellationToken) + { + await Task.Yield(); + ResetEvent.Reset(); + ResetEvent.Wait(cancellationToken); + } + + /// + /// Reports the execution result and signals completion. + /// + /// The execution result. + public void ReportResult(Result? result) + { + if (Result != null) + throw new InvalidOperationException("Result already reported"); + + Result = result; + ResetEvent.Set(); + } + + /// + public override string ToString() + => DisplayString ?? From.HandlerType.Name; } diff --git a/src/Telegrator/Core/Descriptors/DescriptorAspectsSet.cs b/src/Telegrator/Core/Descriptors/DescriptorAspectsSet.cs index 31629ff..036563d 100644 --- a/src/Telegrator/Core/Descriptors/DescriptorAspectsSet.cs +++ b/src/Telegrator/Core/Descriptors/DescriptorAspectsSet.cs @@ -1,80 +1,79 @@ using Telegrator.Aspects; using Telegrator.Core.Handlers; -namespace Telegrator.Core.Descriptors +namespace Telegrator.Core.Descriptors; + +/// +/// Manages the execution of pre and post-execution aspects for a handler. +/// This class coordinates between self-processing (handler implements interfaces) +/// and typed processing (external processor classes). +/// +public sealed class DescriptorAspectsSet { /// - /// Manages the execution of pre and post-execution aspects for a handler. - /// This class coordinates between self-processing (handler implements interfaces) - /// and typed processing (external processor classes). + /// Gets the type of the external pre-processor, if specified via . /// - public sealed class DescriptorAspectsSet + public Type? TypedPre { get; private set; } + + /// + /// Gets the type of the external post-processor, if specified via . + /// + public Type? TypedPost { get; private set; } + + /// + /// Initializes a new instance of the class. + /// + /// The type of external pre-processor, if any. + /// The type of external post-processor, if any. + public DescriptorAspectsSet(Type? typedPre, Type? typedPost) { - /// - /// Gets the type of the external pre-processor, if specified via . - /// - public Type? TypedPre { get; private set; } + TypedPre = typedPre; + TypedPost = typedPost; + } - /// - /// Gets the type of the external post-processor, if specified via . - /// - public Type? TypedPost { get; private set; } - - /// - /// Initializes a new instance of the class. - /// - /// The type of external pre-processor, if any. - /// The type of external post-processor, if any. - public DescriptorAspectsSet(Type? typedPre, Type? typedPost) + /// + /// Executes the pre-execution aspect for the handler. + /// + /// The handler instance. + /// The handler container with update context. + /// + /// A indicating whether execution should continue. + /// Thrown when handler claims to implement but doesn't. + public async Task ExecutePre(UpdateHandlerBase handler, IHandlerContainer container, CancellationToken cancellationToken) + { + if (handler is IPreProcessor preProcessor) { - TypedPre = typedPre; - TypedPost = typedPost; + return await preProcessor.BeforeExecution(container, cancellationToken).ConfigureAwait(false); + } + else if (TypedPre != null) + { + preProcessor = (IPreProcessor)Activator.CreateInstance(TypedPre); + return await preProcessor.BeforeExecution(container, cancellationToken).ConfigureAwait(false); } - /// - /// Executes the pre-execution aspect for the handler. - /// - /// The handler instance. - /// The handler container with update context. - /// - /// A indicating whether execution should continue. - /// Thrown when handler claims to implement but doesn't. - public async Task ExecutePre(UpdateHandlerBase handler, IHandlerContainer container, CancellationToken cancellationToken) - { - if (handler is IPreProcessor preProcessor) - { - return await preProcessor.BeforeExecution(container, cancellationToken).ConfigureAwait(false); - } - else if (TypedPre != null) - { - preProcessor = (IPreProcessor)Activator.CreateInstance(TypedPre); - return await preProcessor.BeforeExecution(container, cancellationToken).ConfigureAwait(false); - } + return Result.Ok(); + } - return Result.Ok(); + /// + /// Executes the post-execution aspect for the handler. + /// + /// The handler instance. + /// The handler container with update context. + /// + /// A indicating the final execution result. + /// Thrown when handler claims to implement but doesn't. + public async Task ExecutePost(UpdateHandlerBase handler, IHandlerContainer container, CancellationToken cancellationToken) + { + if (handler is IPostProcessor postProcessor) + { + return await postProcessor.AfterExecution(container, cancellationToken).ConfigureAwait(false); + } + else if (TypedPost != null) + { + postProcessor = (IPostProcessor)Activator.CreateInstance(TypedPost); + return await postProcessor.AfterExecution(container, cancellationToken).ConfigureAwait(false); } - /// - /// Executes the post-execution aspect for the handler. - /// - /// The handler instance. - /// The handler container with update context. - /// - /// A indicating the final execution result. - /// Thrown when handler claims to implement but doesn't. - public async Task ExecutePost(UpdateHandlerBase handler, IHandlerContainer container, CancellationToken cancellationToken) - { - if (handler is IPostProcessor postProcessor) - { - return await postProcessor.AfterExecution(container, cancellationToken).ConfigureAwait(false); - } - else if (TypedPost != null) - { - postProcessor = (IPostProcessor)Activator.CreateInstance(TypedPost); - return await postProcessor.AfterExecution(container, cancellationToken).ConfigureAwait(false); - } - - return Result.Ok(); - } + return Result.Ok(); } } diff --git a/src/Telegrator/Core/Descriptors/DescriptorFiltersSet.cs b/src/Telegrator/Core/Descriptors/DescriptorFiltersSet.cs index c08f0bb..c4c7c66 100644 --- a/src/Telegrator/Core/Descriptors/DescriptorFiltersSet.cs +++ b/src/Telegrator/Core/Descriptors/DescriptorFiltersSet.cs @@ -3,143 +3,142 @@ using Telegrator.Core.Filters; using Telegrator.Handlers.Diagnostics; using Telegrator.Logging; -namespace Telegrator.Core.Descriptors +namespace Telegrator.Core.Descriptors; + +/// +/// Represents a set of filters for a handler descriptor, including update and state keeper validators. +/// +public sealed class DescriptorFiltersSet { /// - /// Represents a set of filters for a handler descriptor, including update and state keeper validators. + /// Validator for the update object. /// - public sealed class DescriptorFiltersSet + public IFilter? UpdateValidator { get; set; } + + /// + /// Validator for the state keeper. + /// + public IFilter? StateKeeperValidator { get; set; } + + /// + /// Array of update filters. + /// + public IFilter[]? UpdateFilters { get; set; } + + /// + /// Initializes a new instance of the class. + /// + /// Validator for the update object. + /// Validator for the state keeper. + /// Array of update filters. + public DescriptorFiltersSet(IFilter? updateValidator, IFilter? stateKeeperValidator, IFilter[]? updateFilters) { - /// - /// Validator for the update object. - /// - public IFilter? UpdateValidator { get; set; } + UpdateValidator = updateValidator; + StateKeeperValidator = stateKeeperValidator; + UpdateFilters = updateFilters; + } - /// - /// Validator for the state keeper. - /// - public IFilter? StateKeeperValidator { get; set; } + /// + /// Validates the filter context using all filters in the set. + /// + /// The filter execution context. + /// + /// + /// True if all filters pass; otherwise, false. + public Result Validate(FilterExecutionContext filterContext, bool formReport, ref FiltersFallbackReport report) + { + bool anyErrors = false; - /// - /// Array of update filters. - /// - public IFilter[]? UpdateFilters { get; set; } - - /// - /// Initializes a new instance of the class. - /// - /// Validator for the update object. - /// Validator for the state keeper. - /// Array of update filters. - public DescriptorFiltersSet(IFilter? updateValidator, IFilter? stateKeeperValidator, IFilter[]? updateFilters) + if (UpdateValidator != null) { - UpdateValidator = updateValidator; - StateKeeperValidator = stateKeeperValidator; - UpdateFilters = updateFilters; + bool result = ExecuteFilter(UpdateValidator, filterContext, out Exception? exc); + + if (formReport) + { + report.UpdateValidator = new FilterFallbackInfo("Validator", UpdateValidator, !result, exc); + } + + if (!result) + { + anyErrors = true; + TelegratorLogging.LogTrace("(E) UpdateValidator filter of '{0}' for Update ({1}) didnt pass!", filterContext.Data["handler_name"], filterContext.Update.Id); + + if (!formReport) + return Result.Fault(); + } + else + { + filterContext.CompletedFilters.Add(UpdateValidator); + } } - /// - /// Validates the filter context using all filters in the set. - /// - /// The filter execution context. - /// - /// - /// True if all filters pass; otherwise, false. - public Result Validate(FilterExecutionContext filterContext, bool formReport, ref FiltersFallbackReport report) + if (StateKeeperValidator != null) { - bool anyErrors = false; + bool result = ExecuteFilter(StateKeeperValidator, filterContext, out Exception? exc); - if (UpdateValidator != null) + if (formReport) { - bool result = ExecuteFilter(UpdateValidator, filterContext, out Exception? exc); + report.StateKeeperValidator = new FilterFallbackInfo("State", StateKeeperValidator, !result, exc); + } + + if (!result) + { + anyErrors = true; + TelegratorLogging.LogTrace("(E) StateKeeperValidator filter of '{0}' for Update ({1}) didnt pass!", filterContext.Data["handler_name"], filterContext.Update.Id); + + if (!formReport) + return Result.Fault(); + } + else + { + filterContext.CompletedFilters.Add(StateKeeperValidator); + } + } + + if (UpdateFilters != null) + { + foreach (IFilter filter in UpdateFilters) + { + bool result = ExecuteFilter(filter, filterContext, out Exception? exc); + string filterName = filter is INamedFilter named ? named.Name : filter.GetType().Name; if (formReport) { - report.UpdateValidator = new FilterFallbackInfo("Validator", UpdateValidator, !result, exc); + report.UpdateFilters.Add(new FilterFallbackInfo(filterName, filter, !result, exc)); } if (!result) { anyErrors = true; - TelegratorLogging.LogTrace("(E) UpdateValidator filter of '{0}' for Update ({1}) didnt pass!", filterContext.Data["handler_name"], filterContext.Update.Id); + TelegratorLogging.LogTrace("(E) '{0}' filter of '{1}' for Update ({2}) didnt pass!", filterName, filterContext.Data["handler_name"], filterContext.Update.Id); if (!formReport) return Result.Fault(); } else { - filterContext.CompletedFilters.Add(UpdateValidator); + filterContext.CompletedFilters.Add(filter); } } - - if (StateKeeperValidator != null) - { - bool result = ExecuteFilter(StateKeeperValidator, filterContext, out Exception? exc); - - if (formReport) - { - report.StateKeeperValidator = new FilterFallbackInfo("State", StateKeeperValidator, !result, exc); - } - - if (!result) - { - anyErrors = true; - TelegratorLogging.LogTrace("(E) StateKeeperValidator filter of '{0}' for Update ({1}) didnt pass!", filterContext.Data["handler_name"], filterContext.Update.Id); - - if (!formReport) - return Result.Fault(); - } - else - { - filterContext.CompletedFilters.Add(StateKeeperValidator); - } - } - - if (UpdateFilters != null) - { - foreach (IFilter filter in UpdateFilters) - { - bool result = ExecuteFilter(filter, filterContext, out Exception? exc); - string filterName = filter is INamedFilter named ? named.Name : filter.GetType().Name; - - if (formReport) - { - report.UpdateFilters.Add(new FilterFallbackInfo(filterName, filter, !result, exc)); - } - - if (!result) - { - anyErrors = true; - TelegratorLogging.LogTrace("(E) '{0}' filter of '{1}' for Update ({2}) didnt pass!", filterName, filterContext.Data["handler_name"], filterContext.Update.Id); - - if (!formReport) - return Result.Fault(); - } - else - { - filterContext.CompletedFilters.Add(filter); - } - } - } - - if (!anyErrors) - return Result.Ok(); - - return formReport ? Result.Next() : Result.Fault(); } - private static bool ExecuteFilter(IFilter filter, FilterExecutionContext context, out Exception? exception) where T : class + if (!anyErrors) + return Result.Ok(); + + return formReport ? Result.Next() : Result.Fault(); + } + + private static bool ExecuteFilter(IFilter filter, FilterExecutionContext context, out Exception? exception) where T : class + { + try { - try - { - exception = null; - return filter.CanPass(context); - } - catch (Exception ex) - { - exception = ex; - return false; - } + exception = null; + return filter.CanPass(context); + } + catch (Exception ex) + { + exception = ex; + return false; } } } diff --git a/src/Telegrator/Core/Descriptors/DescriptorIndexer.cs b/src/Telegrator/Core/Descriptors/DescriptorIndexer.cs index eab8e86..ba612d7 100644 --- a/src/Telegrator/Core/Descriptors/DescriptorIndexer.cs +++ b/src/Telegrator/Core/Descriptors/DescriptorIndexer.cs @@ -1,88 +1,87 @@ using Telegrator.Core.Attributes; -namespace Telegrator.Core.Descriptors +namespace Telegrator.Core.Descriptors; + +/// +/// Represents an indexer for handler descriptors, containing importance and priority information. +/// +public readonly struct DescriptorIndexer(int routerIndex, int importance, int priority) : IComparable { /// - /// Represents an indexer for handler descriptors, containing importance and priority information. + /// Index of this descriptor when it was added to router /// - public readonly struct DescriptorIndexer(int routerIndex, int importance, int priority) : IComparable + public readonly int RouterIndex = routerIndex; + + /// + /// Of this handlert type + /// + public readonly int Importance = importance; + + /// + /// The priority of the handler. + /// + public readonly int Priority = priority; + + /// + /// Initializes a new instance of the struct from a handler attribute. + /// + /// + /// The handler attribute. + public DescriptorIndexer(int routerIndex, UpdateHandlerAttributeBase pollingHandler) + : this(routerIndex, pollingHandler.Importance, pollingHandler.Priority) { } + + /// + /// Returns a new with updated priority. + /// + /// The new priority value. + /// A new instance. + public DescriptorIndexer UpdatePriority(int priority) + => new DescriptorIndexer(RouterIndex, Importance, priority); + + /// + /// Returns a new with updated importance. + /// + /// The new importance value. + /// A new instance. + public DescriptorIndexer UpdateImportance(int importance) + => new DescriptorIndexer(RouterIndex, importance, Priority); + + /// + /// Returns a new with updated RouterIndex. + /// + /// + /// A new instance. + public DescriptorIndexer UpdateIndex(int routerIndex) + => new DescriptorIndexer(routerIndex, Importance, Priority); + + /// + /// Compares this instance to another . + /// + /// The other indexer to compare to. + /// An integer indicating the relative order. + public int CompareTo(DescriptorIndexer other) { - /// - /// Index of this descriptor when it was added to router - /// - public readonly int RouterIndex = routerIndex; + int importanceCmp = Importance.CompareTo(other.Importance); + if (importanceCmp != 0) + return importanceCmp; - /// - /// Of this handlert type - /// - public readonly int Importance = importance; + int priorityCmp = Priority.CompareTo(other.Priority); + if (priorityCmp != 0) + return priorityCmp; - /// - /// The priority of the handler. - /// - public readonly int Priority = priority; + int routerIndexCmp = RouterIndex.CompareTo(other.RouterIndex); + if (routerIndexCmp != 0) + return routerIndexCmp; - /// - /// Initializes a new instance of the struct from a handler attribute. - /// - /// - /// The handler attribute. - public DescriptorIndexer(int routerIndex, UpdateHandlerAttributeBase pollingHandler) - : this(routerIndex, pollingHandler.Importance, pollingHandler.Priority) { } + return 0; + } - /// - /// Returns a new with updated priority. - /// - /// The new priority value. - /// A new instance. - public DescriptorIndexer UpdatePriority(int priority) - => new DescriptorIndexer(RouterIndex, Importance, priority); - - /// - /// Returns a new with updated importance. - /// - /// The new importance value. - /// A new instance. - public DescriptorIndexer UpdateImportance(int importance) - => new DescriptorIndexer(RouterIndex, importance, Priority); - - /// - /// Returns a new with updated RouterIndex. - /// - /// - /// A new instance. - public DescriptorIndexer UpdateIndex(int routerIndex) - => new DescriptorIndexer(routerIndex, Importance, Priority); - - /// - /// Compares this instance to another . - /// - /// The other indexer to compare to. - /// An integer indicating the relative order. - public int CompareTo(DescriptorIndexer other) - { - int importanceCmp = Importance.CompareTo(other.Importance); - if (importanceCmp != 0) - return importanceCmp; - - int priorityCmp = Priority.CompareTo(other.Priority); - if (priorityCmp != 0) - return priorityCmp; - - int routerIndexCmp = RouterIndex.CompareTo(other.RouterIndex); - if (routerIndexCmp != 0) - return routerIndexCmp; - - return 0; - } - - /// - /// Returns a string representation of the indexer. - /// - /// A string in the format (C:importance, P:priority). - public override string ToString() - { - return string.Format("(Ix: {0,2}, Im: {1,2}, Pr: {2,2})", RouterIndex, Importance, Priority); - } + /// + /// Returns a string representation of the indexer. + /// + /// A string in the format (C:importance, P:priority). + public override string ToString() + { + return string.Format("(Ix: {0,2}, Im: {1,2}, Pr: {2,2})", RouterIndex, Importance, Priority); } } diff --git a/src/Telegrator/Core/Descriptors/HandlerDescriptor.cs b/src/Telegrator/Core/Descriptors/HandlerDescriptor.cs index 723e886..ad87255 100644 --- a/src/Telegrator/Core/Descriptors/HandlerDescriptor.cs +++ b/src/Telegrator/Core/Descriptors/HandlerDescriptor.cs @@ -4,472 +4,471 @@ using Telegrator.Core.Attributes; using Telegrator.Core.Filters; using Telegrator.Core.Handlers; -namespace Telegrator.Core.Descriptors +namespace Telegrator.Core.Descriptors; + +/// +/// Specifies the type of handler descriptor. +/// +public enum DescriptorType { /// - /// Specifies the type of handler descriptor. + /// General handler descriptor. /// - public enum DescriptorType + General, + + /// + /// Keyed handler descriptor (uses a service key). + /// + Keyed, + + /// + /// Implicit handler descriptor. + /// + Implicit, + + /// + /// Singleton handler descriptor (single instance). + /// + Singleton +} + +/// +/// Describes a handler, its type, filters, and instantiation logic. +/// +public class HandlerDescriptor +{ + /// + /// The type of the descriptor. + /// + public DescriptorType Type { - /// - /// General handler descriptor. - /// - General, - - /// - /// Keyed handler descriptor (uses a service key). - /// - Keyed, - - /// - /// Implicit handler descriptor. - /// - Implicit, - - /// - /// Singleton handler descriptor (single instance). - /// - Singleton + get; + private set; } /// - /// Describes a handler, its type, filters, and instantiation logic. + /// The type of the handler. /// - public class HandlerDescriptor + public Type HandlerType { - /// - /// The type of the descriptor. - /// - public DescriptorType Type - { - get; - private set; - } - - /// - /// The type of the handler. - /// - public Type HandlerType - { - get; - private set; - } - - /// - /// The update type handled by this handler. - /// - public UpdateType UpdateType - { - get; - protected set; - } - - /// - /// The indexer for handler concurrency and priority. - /// - public DescriptorIndexer Indexer - { - get; - set; - } - - /// - /// Gets or sets a value indicating whether to form a fallback report. - /// - public bool FormReport - { - get; - set; - } - - /// - /// The set of filters associated with this handler. - /// - public DescriptorFiltersSet? Filters - { - get; - protected set; - } - - /// - /// Gets or sets the aspects configuration for this handler. - /// Contains pre and post-execution processors if the handler uses the aspect system. - /// - public DescriptorAspectsSet? Aspects - { - get; - protected set; - } - - /// - /// The service key for keyed handlers. - /// - public object? ServiceKey - { - get; - protected set; - } - - /// - /// Factory for creating handler instances. - /// - public Func? InstanceFactory - { - get; - protected set; - } - - /// - /// Singleton instance of the handler, if applicable. - /// - public UpdateHandlerBase? SingletonInstance - { - get; - protected set; - } - - /// - /// Display string for the handler (for debugging or logging). - /// - public string? DisplayString - { - get; - set; - } - - /// - /// Gets or sets a function for 'lazy' handlers initialization - /// - public Action? LazyInitialization - { - get; - set; - } - - /// - /// Initializes a new instance of the class with the specified descriptor type and handler type. - /// Automatically inspects the handler type to extract attributes, filters, and configuration. - /// - /// The type of the descriptor - /// The type of the handler to describe - /// - /// Thrown when the handler type is not compatible with the expected handler type - public HandlerDescriptor(DescriptorType descriptorType, Type handlerType, bool dontInspect = false) - { - Type = descriptorType; - HandlerType = handlerType; - Filters = new DescriptorFiltersSet(null, null, null); - - if (dontInspect) - return; - - UpdateHandlerAttributeBase handlerAttribute = HandlerInspector.GetHandlerAttribute(handlerType); - if (handlerAttribute.ExpectingHandlerType != null && !handlerAttribute.ExpectingHandlerType.Contains(handlerType.BaseType)) - throw new ArgumentException(string.Format("This handler attribute cannot be attached to this class. Attribute can be attached on next handlers : {0}", string.Join(", ", handlerAttribute.ExpectingHandlerType.AsEnumerable()))); - - IFilter? stateKeeperAttribute = HandlerInspector.GetStateKeeperAttribute(handlerType); - IFilter[] filters = HandlerInspector.GetFilterAttributes(handlerType, handlerAttribute.Type).ToArray(); - - UpdateType = handlerAttribute.Type; - Indexer = handlerAttribute.GetIndexer(); - FormReport = handlerAttribute.FormReport; - Filters = new DescriptorFiltersSet(handlerAttribute, stateKeeperAttribute, filters); - Aspects = HandlerInspector.GetAspects(handlerType); - DisplayString = HandlerInspector.GetDisplayName(handlerType); - } - - /// - /// Initializes a new instance of the class as a keyed handler with the specified service key. - /// - /// The type of the handler to describe - /// The service key for dependency injection - /// Thrown when is null - public HandlerDescriptor(Type handlerType, object serviceKey) : this(DescriptorType.Keyed, handlerType) - { - ServiceKey = serviceKey ?? throw new ArgumentNullException(nameof(serviceKey)); - } - - /// - /// Initializes a new instance of the class with all basic properties. - /// - /// The type of the descriptor - /// The type of the handler - /// The type of update this handler processes - /// The indexer for handler concurrency and priority - /// The set of filters associated with this handler - public HandlerDescriptor(DescriptorType type, Type handlerType, UpdateType updateType, DescriptorIndexer indexer, DescriptorFiltersSet filters) - { - Type = type; - HandlerType = handlerType; - UpdateType = updateType; - Indexer = indexer; - Filters = filters; - } - - /// - /// Initializes a new instance of the class with singleton instance support. - /// - /// The type of the descriptor - /// The type of the handler - /// The type of update this handler processes - /// The indexer for handler concurrency and priority - /// The set of filters associated with this handler - /// The service key for dependency injection - /// The singleton instance of the handler - /// Thrown when or is null - public HandlerDescriptor(DescriptorType type, Type handlerType, UpdateType updateType, DescriptorIndexer indexer, DescriptorFiltersSet filters, object serviceKey, UpdateHandlerBase singletonInstance) - { - Type = type; - HandlerType = handlerType; - UpdateType = updateType; - Indexer = indexer; - Filters = filters; - ServiceKey = serviceKey ?? throw new ArgumentNullException(nameof(serviceKey)); - SingletonInstance = singletonInstance ?? throw new ArgumentNullException(nameof(singletonInstance)); - } - - /// - /// Initializes a new instance of the class with instance factory support. - /// - /// The type of the descriptor - /// The type of the handler - /// The type of update this handler processes - /// The indexer for handler concurrency and priority - /// The set of filters associated with this handler - /// Factory for creating handler instances - /// Thrown when is null - public HandlerDescriptor(DescriptorType type, Type handlerType, UpdateType updateType, DescriptorIndexer indexer, DescriptorFiltersSet filters, Func instanceFactory) - { - Type = type; - HandlerType = handlerType; - UpdateType = updateType; - Indexer = indexer; - Filters = filters; - InstanceFactory = instanceFactory ?? throw new ArgumentNullException(nameof(instanceFactory)); - } - - /// - /// Initializes a new instance of the class with service key and instance factory support. - /// - /// The type of the descriptor - /// The type of the handler - /// The type of update this handler processes - /// The indexer for handler concurrency and priority - /// The set of filters associated with this handler - /// The service key for dependency injection - /// Factory for creating handler instances - /// Thrown when or is null - public HandlerDescriptor(DescriptorType type, Type handlerType, UpdateType updateType, DescriptorIndexer indexer, DescriptorFiltersSet filters, object serviceKey, Func instanceFactory) - { - Type = type; - HandlerType = handlerType; - UpdateType = updateType; - Indexer = indexer; - Filters = filters; - ServiceKey = serviceKey ?? throw new ArgumentNullException(nameof(serviceKey)); - InstanceFactory = instanceFactory ?? throw new ArgumentNullException(nameof(instanceFactory)); - } - - /// - /// Initializes a new instance of the class with polling handler attribute and filters. - /// - /// The type of the descriptor - /// The type of the handler - /// The polling handler attribute containing configuration - /// Optional array of filters to apply - /// Optional state keeping filter - public HandlerDescriptor(DescriptorType type, Type handlerType, UpdateHandlerAttributeBase pollingHandlerAttribute, IFilter[]? filters, IFilter? stateKeepFilter) - { - Type = type; - HandlerType = handlerType; - UpdateType = pollingHandlerAttribute.Type; - Indexer = pollingHandlerAttribute.GetIndexer(); - Filters = new DescriptorFiltersSet(pollingHandlerAttribute, stateKeepFilter, filters); - } - - /// - /// Initializes a new instance of the class with polling handler attribute, filters, and singleton instance. - /// - /// The type of the descriptor - /// The type of the handler - /// The polling handler attribute containing configuration - /// Optional array of filters to apply - /// Optional state keeping filter - /// The service key for dependency injection - /// The singleton instance of the handler - /// Thrown when or is null - public HandlerDescriptor(DescriptorType type, Type handlerType, UpdateHandlerAttributeBase pollingHandlerAttribute, IFilter[]? filters, IFilter? stateKeepFilter, object serviceKey, UpdateHandlerBase singletonInstance) - { - Type = type; - HandlerType = handlerType; - UpdateType = pollingHandlerAttribute.Type; - Indexer = pollingHandlerAttribute.GetIndexer(); - Filters = new DescriptorFiltersSet(pollingHandlerAttribute, stateKeepFilter, filters); - ServiceKey = serviceKey ?? throw new ArgumentNullException(nameof(serviceKey)); - SingletonInstance = singletonInstance ?? throw new ArgumentNullException(nameof(singletonInstance)); - } - - /// - /// Initializes a new instance of the class with polling handler attribute, filters, and instance factory. - /// - /// The type of the descriptor - /// The type of the handler - /// The polling handler attribute containing configuration - /// Optional array of filters to apply - /// Optional state keeping filter - /// Factory for creating handler instances - /// Thrown when is null - public HandlerDescriptor(DescriptorType type, Type handlerType, UpdateHandlerAttributeBase pollingHandlerAttribute, IFilter[]? filters, IFilter? stateKeepFilter, Func instanceFactory) - { - Type = type; - HandlerType = handlerType; - UpdateType = pollingHandlerAttribute.Type; - Indexer = pollingHandlerAttribute.GetIndexer(); - Filters = new DescriptorFiltersSet(pollingHandlerAttribute, stateKeepFilter, filters); - InstanceFactory = instanceFactory ?? throw new ArgumentNullException(nameof(instanceFactory)); - } - - /// - /// Initializes a new instance of the class with polling handler attribute, filters, service key, and instance factory. - /// - /// The type of the descriptor - /// The type of the handler - /// The polling handler attribute containing configuration - /// Optional array of filters to apply - /// Optional state keeping filter - /// The service key for dependency injection - /// Factory for creating handler instances - /// Thrown when or is null - public HandlerDescriptor(DescriptorType type, Type handlerType, UpdateHandlerAttributeBase pollingHandlerAttribute, IFilter[]? filters, IFilter? stateKeepFilter, object serviceKey, Func instanceFactory) - { - Type = type; - HandlerType = handlerType; - UpdateType = pollingHandlerAttribute.Type; - Indexer = pollingHandlerAttribute.GetIndexer(); - Filters = new DescriptorFiltersSet(pollingHandlerAttribute, stateKeepFilter, filters); - ServiceKey = serviceKey ?? throw new ArgumentNullException(nameof(serviceKey)); - InstanceFactory = instanceFactory ?? throw new ArgumentNullException(nameof(instanceFactory)); - } - - /// - /// Initializes a new instance of the class with validation filter support. - /// - /// The type of the descriptor - /// The type of the handler - /// The type of update this handler processes - /// The indexer for handler concurrency and priority - /// Optional validation filter - /// Optional array of filters to apply - /// Optional state keeping filter - public HandlerDescriptor(DescriptorType type, Type handlerType, UpdateType updateType, DescriptorIndexer indexer, IFilter? validateFilter, IFilter[]? filters, IFilter? stateKeepFilter) - { - Type = type; - HandlerType = handlerType; - UpdateType = updateType; - Indexer = indexer; - Filters = new DescriptorFiltersSet(validateFilter, stateKeepFilter, filters); - } - - /// - /// Initializes a new instance of the class with validation filter and singleton instance support. - /// - /// The type of the descriptor - /// The type of the handler - /// The type of update this handler processes - /// The indexer for handler concurrency and priority - /// Optional validation filter - /// Optional array of filters to apply - /// Optional state keeping filter - /// The service key for dependency injection - /// The singleton instance of the handler - /// Thrown when or is null - public HandlerDescriptor(DescriptorType type, Type handlerType, UpdateType updateType, DescriptorIndexer indexer, IFilter? validateFilter, IFilter[]? filters, IFilter? stateKeepFilter, object serviceKey, UpdateHandlerBase singletonInstance) - { - Type = type; - HandlerType = handlerType; - UpdateType = updateType; - Indexer = indexer; - Filters = new DescriptorFiltersSet(validateFilter, stateKeepFilter, filters); - ServiceKey = serviceKey ?? throw new ArgumentNullException(nameof(serviceKey)); - SingletonInstance = singletonInstance ?? throw new ArgumentNullException(nameof(singletonInstance)); - } - - /// - /// Initializes a new instance of the class with validation filter and instance factory support. - /// - /// The type of the descriptor - /// The type of the handler - /// The type of update this handler processes - /// The indexer for handler concurrency and priority - /// Optional validation filter - /// Optional array of filters to apply - /// Optional state keeping filter - /// Factory for creating handler instances - /// Thrown when is null - public HandlerDescriptor(DescriptorType type, Type handlerType, UpdateType updateType, DescriptorIndexer indexer, IFilter? validateFilter, IFilter[]? filters, IFilter? stateKeepFilter, Func instanceFactory) - { - Type = type; - HandlerType = handlerType; - UpdateType = updateType; - Indexer = indexer; - Filters = new DescriptorFiltersSet(validateFilter, stateKeepFilter, filters); - InstanceFactory = instanceFactory ?? throw new ArgumentNullException(nameof(instanceFactory)); - } - - /// - /// Initializes a new instance of the class with validation filter, service key, and instance factory support. - /// - /// The type of the descriptor - /// The type of the handler - /// The type of update this handler processes - /// The indexer for handler concurrency and priority - /// Optional validation filter - /// Optional array of filters to apply - /// Optional state keeping filter - /// The service key for dependency injection - /// Factory for creating handler instances - /// Thrown when or is null - public HandlerDescriptor(DescriptorType type, Type handlerType, UpdateType updateType, DescriptorIndexer indexer, IFilter? validateFilter, IFilter[]? filters, IFilter? stateKeepFilter, object serviceKey, Func instanceFactory) - { - Type = type; - HandlerType = handlerType; - UpdateType = updateType; - Indexer = indexer; - Filters = new DescriptorFiltersSet(validateFilter, stateKeepFilter, filters); - ServiceKey = serviceKey ?? throw new ArgumentNullException(nameof(serviceKey)); - InstanceFactory = instanceFactory ?? throw new ArgumentNullException(nameof(instanceFactory)); - } - - /// - /// Sets singleton instance of this descriptor - /// Throws exception if instance already set - /// - /// - /// - public void SetInstance(UpdateHandlerBase instance) - { - if (SingletonInstance != null) - throw new Exception(); - - SingletonInstance = instance; - } - - /// - /// Tries to set singleton instance of this descriptor - /// - /// - /// - public bool TrySetInstance(UpdateHandlerBase instance) - { - if (SingletonInstance != null) - return false; - - SingletonInstance = instance; - return true; - } - - /// - public override string ToString() - => DisplayString ?? HandlerType.Name; + get; + private set; } + + /// + /// The update type handled by this handler. + /// + public UpdateType UpdateType + { + get; + protected set; + } + + /// + /// The indexer for handler concurrency and priority. + /// + public DescriptorIndexer Indexer + { + get; + set; + } + + /// + /// Gets or sets a value indicating whether to form a fallback report. + /// + public bool FormReport + { + get; + set; + } + + /// + /// The set of filters associated with this handler. + /// + public DescriptorFiltersSet? Filters + { + get; + protected set; + } + + /// + /// Gets or sets the aspects configuration for this handler. + /// Contains pre and post-execution processors if the handler uses the aspect system. + /// + public DescriptorAspectsSet? Aspects + { + get; + protected set; + } + + /// + /// The service key for keyed handlers. + /// + public object? ServiceKey + { + get; + protected set; + } + + /// + /// Factory for creating handler instances. + /// + public Func? InstanceFactory + { + get; + protected set; + } + + /// + /// Singleton instance of the handler, if applicable. + /// + public UpdateHandlerBase? SingletonInstance + { + get; + protected set; + } + + /// + /// Display string for the handler (for debugging or logging). + /// + public string? DisplayString + { + get; + set; + } + + /// + /// Gets or sets a function for 'lazy' handlers initialization + /// + public Action? LazyInitialization + { + get; + set; + } + + /// + /// Initializes a new instance of the class with the specified descriptor type and handler type. + /// Automatically inspects the handler type to extract attributes, filters, and configuration. + /// + /// The type of the descriptor + /// The type of the handler to describe + /// + /// Thrown when the handler type is not compatible with the expected handler type + public HandlerDescriptor(DescriptorType descriptorType, Type handlerType, bool dontInspect = false) + { + Type = descriptorType; + HandlerType = handlerType; + Filters = new DescriptorFiltersSet(null, null, null); + + if (dontInspect) + return; + + UpdateHandlerAttributeBase handlerAttribute = HandlerInspector.GetHandlerAttribute(handlerType); + if (handlerAttribute.ExpectingHandlerType != null && !handlerAttribute.ExpectingHandlerType.Contains(handlerType.BaseType)) + throw new ArgumentException(string.Format("This handler attribute cannot be attached to this class. Attribute can be attached on next handlers : {0}", string.Join(", ", handlerAttribute.ExpectingHandlerType.AsEnumerable()))); + + IFilter? stateKeeperAttribute = HandlerInspector.GetStateKeeperAttribute(handlerType); + IFilter[] filters = HandlerInspector.GetFilterAttributes(handlerType, handlerAttribute.Type).ToArray(); + + UpdateType = handlerAttribute.Type; + Indexer = handlerAttribute.GetIndexer(); + FormReport = handlerAttribute.FormReport; + Filters = new DescriptorFiltersSet(handlerAttribute, stateKeeperAttribute, filters); + Aspects = HandlerInspector.GetAspects(handlerType); + DisplayString = HandlerInspector.GetDisplayName(handlerType); + } + + /// + /// Initializes a new instance of the class as a keyed handler with the specified service key. + /// + /// The type of the handler to describe + /// The service key for dependency injection + /// Thrown when is null + public HandlerDescriptor(Type handlerType, object serviceKey) : this(DescriptorType.Keyed, handlerType) + { + ServiceKey = serviceKey ?? throw new ArgumentNullException(nameof(serviceKey)); + } + + /// + /// Initializes a new instance of the class with all basic properties. + /// + /// The type of the descriptor + /// The type of the handler + /// The type of update this handler processes + /// The indexer for handler concurrency and priority + /// The set of filters associated with this handler + public HandlerDescriptor(DescriptorType type, Type handlerType, UpdateType updateType, DescriptorIndexer indexer, DescriptorFiltersSet filters) + { + Type = type; + HandlerType = handlerType; + UpdateType = updateType; + Indexer = indexer; + Filters = filters; + } + + /// + /// Initializes a new instance of the class with singleton instance support. + /// + /// The type of the descriptor + /// The type of the handler + /// The type of update this handler processes + /// The indexer for handler concurrency and priority + /// The set of filters associated with this handler + /// The service key for dependency injection + /// The singleton instance of the handler + /// Thrown when or is null + public HandlerDescriptor(DescriptorType type, Type handlerType, UpdateType updateType, DescriptorIndexer indexer, DescriptorFiltersSet filters, object serviceKey, UpdateHandlerBase singletonInstance) + { + Type = type; + HandlerType = handlerType; + UpdateType = updateType; + Indexer = indexer; + Filters = filters; + ServiceKey = serviceKey ?? throw new ArgumentNullException(nameof(serviceKey)); + SingletonInstance = singletonInstance ?? throw new ArgumentNullException(nameof(singletonInstance)); + } + + /// + /// Initializes a new instance of the class with instance factory support. + /// + /// The type of the descriptor + /// The type of the handler + /// The type of update this handler processes + /// The indexer for handler concurrency and priority + /// The set of filters associated with this handler + /// Factory for creating handler instances + /// Thrown when is null + public HandlerDescriptor(DescriptorType type, Type handlerType, UpdateType updateType, DescriptorIndexer indexer, DescriptorFiltersSet filters, Func instanceFactory) + { + Type = type; + HandlerType = handlerType; + UpdateType = updateType; + Indexer = indexer; + Filters = filters; + InstanceFactory = instanceFactory ?? throw new ArgumentNullException(nameof(instanceFactory)); + } + + /// + /// Initializes a new instance of the class with service key and instance factory support. + /// + /// The type of the descriptor + /// The type of the handler + /// The type of update this handler processes + /// The indexer for handler concurrency and priority + /// The set of filters associated with this handler + /// The service key for dependency injection + /// Factory for creating handler instances + /// Thrown when or is null + public HandlerDescriptor(DescriptorType type, Type handlerType, UpdateType updateType, DescriptorIndexer indexer, DescriptorFiltersSet filters, object serviceKey, Func instanceFactory) + { + Type = type; + HandlerType = handlerType; + UpdateType = updateType; + Indexer = indexer; + Filters = filters; + ServiceKey = serviceKey ?? throw new ArgumentNullException(nameof(serviceKey)); + InstanceFactory = instanceFactory ?? throw new ArgumentNullException(nameof(instanceFactory)); + } + + /// + /// Initializes a new instance of the class with polling handler attribute and filters. + /// + /// The type of the descriptor + /// The type of the handler + /// The polling handler attribute containing configuration + /// Optional array of filters to apply + /// Optional state keeping filter + public HandlerDescriptor(DescriptorType type, Type handlerType, UpdateHandlerAttributeBase pollingHandlerAttribute, IFilter[]? filters, IFilter? stateKeepFilter) + { + Type = type; + HandlerType = handlerType; + UpdateType = pollingHandlerAttribute.Type; + Indexer = pollingHandlerAttribute.GetIndexer(); + Filters = new DescriptorFiltersSet(pollingHandlerAttribute, stateKeepFilter, filters); + } + + /// + /// Initializes a new instance of the class with polling handler attribute, filters, and singleton instance. + /// + /// The type of the descriptor + /// The type of the handler + /// The polling handler attribute containing configuration + /// Optional array of filters to apply + /// Optional state keeping filter + /// The service key for dependency injection + /// The singleton instance of the handler + /// Thrown when or is null + public HandlerDescriptor(DescriptorType type, Type handlerType, UpdateHandlerAttributeBase pollingHandlerAttribute, IFilter[]? filters, IFilter? stateKeepFilter, object serviceKey, UpdateHandlerBase singletonInstance) + { + Type = type; + HandlerType = handlerType; + UpdateType = pollingHandlerAttribute.Type; + Indexer = pollingHandlerAttribute.GetIndexer(); + Filters = new DescriptorFiltersSet(pollingHandlerAttribute, stateKeepFilter, filters); + ServiceKey = serviceKey ?? throw new ArgumentNullException(nameof(serviceKey)); + SingletonInstance = singletonInstance ?? throw new ArgumentNullException(nameof(singletonInstance)); + } + + /// + /// Initializes a new instance of the class with polling handler attribute, filters, and instance factory. + /// + /// The type of the descriptor + /// The type of the handler + /// The polling handler attribute containing configuration + /// Optional array of filters to apply + /// Optional state keeping filter + /// Factory for creating handler instances + /// Thrown when is null + public HandlerDescriptor(DescriptorType type, Type handlerType, UpdateHandlerAttributeBase pollingHandlerAttribute, IFilter[]? filters, IFilter? stateKeepFilter, Func instanceFactory) + { + Type = type; + HandlerType = handlerType; + UpdateType = pollingHandlerAttribute.Type; + Indexer = pollingHandlerAttribute.GetIndexer(); + Filters = new DescriptorFiltersSet(pollingHandlerAttribute, stateKeepFilter, filters); + InstanceFactory = instanceFactory ?? throw new ArgumentNullException(nameof(instanceFactory)); + } + + /// + /// Initializes a new instance of the class with polling handler attribute, filters, service key, and instance factory. + /// + /// The type of the descriptor + /// The type of the handler + /// The polling handler attribute containing configuration + /// Optional array of filters to apply + /// Optional state keeping filter + /// The service key for dependency injection + /// Factory for creating handler instances + /// Thrown when or is null + public HandlerDescriptor(DescriptorType type, Type handlerType, UpdateHandlerAttributeBase pollingHandlerAttribute, IFilter[]? filters, IFilter? stateKeepFilter, object serviceKey, Func instanceFactory) + { + Type = type; + HandlerType = handlerType; + UpdateType = pollingHandlerAttribute.Type; + Indexer = pollingHandlerAttribute.GetIndexer(); + Filters = new DescriptorFiltersSet(pollingHandlerAttribute, stateKeepFilter, filters); + ServiceKey = serviceKey ?? throw new ArgumentNullException(nameof(serviceKey)); + InstanceFactory = instanceFactory ?? throw new ArgumentNullException(nameof(instanceFactory)); + } + + /// + /// Initializes a new instance of the class with validation filter support. + /// + /// The type of the descriptor + /// The type of the handler + /// The type of update this handler processes + /// The indexer for handler concurrency and priority + /// Optional validation filter + /// Optional array of filters to apply + /// Optional state keeping filter + public HandlerDescriptor(DescriptorType type, Type handlerType, UpdateType updateType, DescriptorIndexer indexer, IFilter? validateFilter, IFilter[]? filters, IFilter? stateKeepFilter) + { + Type = type; + HandlerType = handlerType; + UpdateType = updateType; + Indexer = indexer; + Filters = new DescriptorFiltersSet(validateFilter, stateKeepFilter, filters); + } + + /// + /// Initializes a new instance of the class with validation filter and singleton instance support. + /// + /// The type of the descriptor + /// The type of the handler + /// The type of update this handler processes + /// The indexer for handler concurrency and priority + /// Optional validation filter + /// Optional array of filters to apply + /// Optional state keeping filter + /// The service key for dependency injection + /// The singleton instance of the handler + /// Thrown when or is null + public HandlerDescriptor(DescriptorType type, Type handlerType, UpdateType updateType, DescriptorIndexer indexer, IFilter? validateFilter, IFilter[]? filters, IFilter? stateKeepFilter, object serviceKey, UpdateHandlerBase singletonInstance) + { + Type = type; + HandlerType = handlerType; + UpdateType = updateType; + Indexer = indexer; + Filters = new DescriptorFiltersSet(validateFilter, stateKeepFilter, filters); + ServiceKey = serviceKey ?? throw new ArgumentNullException(nameof(serviceKey)); + SingletonInstance = singletonInstance ?? throw new ArgumentNullException(nameof(singletonInstance)); + } + + /// + /// Initializes a new instance of the class with validation filter and instance factory support. + /// + /// The type of the descriptor + /// The type of the handler + /// The type of update this handler processes + /// The indexer for handler concurrency and priority + /// Optional validation filter + /// Optional array of filters to apply + /// Optional state keeping filter + /// Factory for creating handler instances + /// Thrown when is null + public HandlerDescriptor(DescriptorType type, Type handlerType, UpdateType updateType, DescriptorIndexer indexer, IFilter? validateFilter, IFilter[]? filters, IFilter? stateKeepFilter, Func instanceFactory) + { + Type = type; + HandlerType = handlerType; + UpdateType = updateType; + Indexer = indexer; + Filters = new DescriptorFiltersSet(validateFilter, stateKeepFilter, filters); + InstanceFactory = instanceFactory ?? throw new ArgumentNullException(nameof(instanceFactory)); + } + + /// + /// Initializes a new instance of the class with validation filter, service key, and instance factory support. + /// + /// The type of the descriptor + /// The type of the handler + /// The type of update this handler processes + /// The indexer for handler concurrency and priority + /// Optional validation filter + /// Optional array of filters to apply + /// Optional state keeping filter + /// The service key for dependency injection + /// Factory for creating handler instances + /// Thrown when or is null + public HandlerDescriptor(DescriptorType type, Type handlerType, UpdateType updateType, DescriptorIndexer indexer, IFilter? validateFilter, IFilter[]? filters, IFilter? stateKeepFilter, object serviceKey, Func instanceFactory) + { + Type = type; + HandlerType = handlerType; + UpdateType = updateType; + Indexer = indexer; + Filters = new DescriptorFiltersSet(validateFilter, stateKeepFilter, filters); + ServiceKey = serviceKey ?? throw new ArgumentNullException(nameof(serviceKey)); + InstanceFactory = instanceFactory ?? throw new ArgumentNullException(nameof(instanceFactory)); + } + + /// + /// Sets singleton instance of this descriptor + /// Throws exception if instance already set + /// + /// + /// + public void SetInstance(UpdateHandlerBase instance) + { + if (SingletonInstance != null) + throw new Exception(); + + SingletonInstance = instance; + } + + /// + /// Tries to set singleton instance of this descriptor + /// + /// + /// + public bool TrySetInstance(UpdateHandlerBase instance) + { + if (SingletonInstance != null) + return false; + + SingletonInstance = instance; + return true; + } + + /// + public override string ToString() + => DisplayString ?? HandlerType.Name; } \ No newline at end of file diff --git a/src/Telegrator/Core/Descriptors/HandlerDescriptorList.cs b/src/Telegrator/Core/Descriptors/HandlerDescriptorList.cs index 44f84b2..83b4694 100644 --- a/src/Telegrator/Core/Descriptors/HandlerDescriptorList.cs +++ b/src/Telegrator/Core/Descriptors/HandlerDescriptorList.cs @@ -2,158 +2,157 @@ using Telegram.Bot.Types; using Telegram.Bot.Types.Enums; -namespace Telegrator.Core.Descriptors +namespace Telegrator.Core.Descriptors; + +/// +/// The collection containing the 's. Used to route 's in +/// +public sealed class HandlerDescriptorList : IEnumerable { + private readonly object _lock = new object(); + private readonly SortedList _innerCollection; + private readonly TelegratorOptions? _options; + private readonly UpdateType _handlingType; + + private int count; + /// - /// The collection containing the 's. Used to route 's in + /// Gets a value indicating whether the collection is read-only. /// - public sealed class HandlerDescriptorList : IEnumerable + public bool IsReadOnly { get; private set; } = false; + + /// + /// Gets the of handlers in this collection. + /// + public UpdateType HandlingType => _handlingType; + + /// + /// Gets count of registered handlers in list + /// + public int Count => _innerCollection.Count; + + /// + /// Gets or sets the at the specified index. + /// + /// + /// + public HandlerDescriptor this[int index] { - private readonly object _lock = new object(); - private readonly SortedList _innerCollection; - private readonly TelegratorOptions? _options; - private readonly UpdateType _handlingType; + get => _innerCollection.Values[index]; + set => _innerCollection.Values[index] = value; + } - private int count; + /// + /// Initializes a new instance of the class without a specific . + /// + public HandlerDescriptorList() + : this(UpdateType.Unknown, default) { } - /// - /// Gets a value indicating whether the collection is read-only. - /// - public bool IsReadOnly { get; private set; } = false; + /// + /// Initializes a new instance of the class. + /// + /// The update type for the handlers. + /// The collecting options. + public HandlerDescriptorList(UpdateType updateType, TelegratorOptions? options) + { + _innerCollection = []; + _handlingType = updateType; + _options = options; + } - /// - /// Gets the of handlers in this collection. - /// - public UpdateType HandlingType => _handlingType; - - /// - /// Gets count of registered handlers in list - /// - public int Count => _innerCollection.Count; - - /// - /// Gets or sets the at the specified index. - /// - /// - /// - public HandlerDescriptor this[int index] + /// + /// Adds a new to the collection. + /// + /// The handler descriptor to add. + /// Thrown if the collection is frozen. + /// Thrown if the update type does not match. + public void Add(HandlerDescriptor descriptor) + { + lock (_lock) { - get => _innerCollection.Values[index]; - set => _innerCollection.Values[index] = value; - } + if (IsReadOnly) + throw new CollectionFrozenException(); - /// - /// Initializes a new instance of the class without a specific . - /// - public HandlerDescriptorList() - : this(UpdateType.Unknown, default) { } + if (_handlingType != UpdateType.Unknown && descriptor.UpdateType != _handlingType) + throw new InvalidOperationException(); - /// - /// Initializes a new instance of the class. - /// - /// The update type for the handlers. - /// The collecting options. - public HandlerDescriptorList(UpdateType updateType, TelegratorOptions? options) - { - _innerCollection = []; - _handlingType = updateType; - _options = options; - } + descriptor.Indexer = descriptor.Indexer.UpdateIndex(count++); + _innerCollection.Add(descriptor.Indexer, descriptor); - /// - /// Adds a new to the collection. - /// - /// The handler descriptor to add. - /// Thrown if the collection is frozen. - /// Thrown if the update type does not match. - public void Add(HandlerDescriptor descriptor) - { - lock (_lock) - { - if (IsReadOnly) - throw new CollectionFrozenException(); - - if (_handlingType != UpdateType.Unknown && descriptor.UpdateType != _handlingType) - throw new InvalidOperationException(); - - descriptor.Indexer = descriptor.Indexer.UpdateIndex(count++); - _innerCollection.Add(descriptor.Indexer, descriptor); - - if (_handlingType == UpdateType.InlineQuery) - IsReadOnly = true; - } - } - - /// - /// Checks if the collection contains a with the specified . - /// - /// The descriptor indexer. - /// True if the descriptor exists; otherwise, false. - public bool ContainsKey(DescriptorIndexer indexer) - { - return _innerCollection.ContainsKey(indexer); - } - - /// - /// Removes the with the specified from the collection. - /// - /// The descriptor indexer. - /// True if the descriptor was removed; otherwise, false. - public bool Remove(DescriptorIndexer indexer) - { - lock (_lock) - { - return _innerCollection.Remove(indexer); - } - } - - /// - /// Removes the from the collection. - /// - /// - /// - public bool Remove(HandlerDescriptor descriptor) - { - lock (_lock) - { - int index = _innerCollection.IndexOfValue(descriptor); - if (index == -1) - return false; - - _innerCollection.RemoveAt(index); - return true; - } - } - - /// - /// Removes all descriptos from the - /// - public void Clear() - { - lock (_lock) - { - _innerCollection.Clear(); - } - } - - /// - /// Freezes the and prohibits adding new elements to it. - /// - public void Freeze() - { - IsReadOnly = true; - } - - /// - public IEnumerator GetEnumerator() - { - return _innerCollection.Values.GetEnumerator(); - } - - /// - IEnumerator IEnumerable.GetEnumerator() - { - return _innerCollection.Values.GetEnumerator(); + if (_handlingType == UpdateType.InlineQuery) + IsReadOnly = true; } } + + /// + /// Checks if the collection contains a with the specified . + /// + /// The descriptor indexer. + /// True if the descriptor exists; otherwise, false. + public bool ContainsKey(DescriptorIndexer indexer) + { + return _innerCollection.ContainsKey(indexer); + } + + /// + /// Removes the with the specified from the collection. + /// + /// The descriptor indexer. + /// True if the descriptor was removed; otherwise, false. + public bool Remove(DescriptorIndexer indexer) + { + lock (_lock) + { + return _innerCollection.Remove(indexer); + } + } + + /// + /// Removes the from the collection. + /// + /// + /// + public bool Remove(HandlerDescriptor descriptor) + { + lock (_lock) + { + int index = _innerCollection.IndexOfValue(descriptor); + if (index == -1) + return false; + + _innerCollection.RemoveAt(index); + return true; + } + } + + /// + /// Removes all descriptos from the + /// + public void Clear() + { + lock (_lock) + { + _innerCollection.Clear(); + } + } + + /// + /// Freezes the and prohibits adding new elements to it. + /// + public void Freeze() + { + IsReadOnly = true; + } + + /// + public IEnumerator GetEnumerator() + { + return _innerCollection.Values.GetEnumerator(); + } + + /// + IEnumerator IEnumerable.GetEnumerator() + { + return _innerCollection.Values.GetEnumerator(); + } } diff --git a/src/Telegrator/Core/Descriptors/HandlerInspector.cs b/src/Telegrator/Core/Descriptors/HandlerInspector.cs index 5dbb21f..25c9d77 100644 --- a/src/Telegrator/Core/Descriptors/HandlerInspector.cs +++ b/src/Telegrator/Core/Descriptors/HandlerInspector.cs @@ -7,93 +7,92 @@ using Telegrator.Aspects; using Telegrator.Core.Attributes; using Telegrator.Core.Filters; -namespace Telegrator.Core.Descriptors +namespace Telegrator.Core.Descriptors; + +/// +/// Provides methods for inspecting handler types and retrieving their attributes and filters. +/// +public static class HandlerInspector { /// - /// Provides methods for inspecting handler types and retrieving their attributes and filters. + /// Gets handler's display name /// - public static class HandlerInspector + /// + /// + public static string? GetDisplayName(MemberInfo handlerType) { - /// - /// Gets handler's display name - /// - /// - /// - public static string? GetDisplayName(MemberInfo handlerType) + return handlerType.GetCustomAttribute()?.DisplayName; + } + + /// + /// Gets the handler attribute from the specified member info. + /// + /// The member info representing the handler type. + /// The handler attribute. + public static UpdateHandlerAttributeBase GetHandlerAttribute(MemberInfo handlerType) + { + // Getting polling handler attribute + IEnumerable handlerAttrs = handlerType.GetCustomAttributes(); + + // + return handlerAttrs.Single(); + } + + /// + /// Gets the state keeper attribute from the specified member info, if present. + /// + /// The member info representing the handler type. + /// The state keeper attribute, or null if not present. + public static IFilter? GetStateKeeperAttribute(MemberInfo handlerType) + { + // Getting polling handler attribute + Attribute stateAttr = handlerType.GetCustomAttribute(typeof(StateAttribute<,>)); + + // + return stateAttr as IFilter; + } + + /// + /// Gets all filter attributes for the specified handler type and update type. + /// + /// The member info representing the handler type. + /// The valid update type. + /// An enumerable of filter attributes. + public static IEnumerable> GetFilterAttributes(MemberInfo handlerType, UpdateType validUpdType) + { + // + IEnumerable filters = handlerType.GetCustomAttributes(); + + // + if (filters.Any(filterAttr => !filterAttr.AllowedTypes.Contains(validUpdType))) + throw new InvalidOperationException(); + + UpdateFilterAttributeBase? lastFilterAttribute = null; + foreach (UpdateFilterAttributeBase filterAttribute in filters) { - return handlerType.GetCustomAttribute()?.DisplayName; - } - - /// - /// Gets the handler attribute from the specified member info. - /// - /// The member info representing the handler type. - /// The handler attribute. - public static UpdateHandlerAttributeBase GetHandlerAttribute(MemberInfo handlerType) - { - // Getting polling handler attribute - IEnumerable handlerAttrs = handlerType.GetCustomAttributes(); - - // - return handlerAttrs.Single(); - } - - /// - /// Gets the state keeper attribute from the specified member info, if present. - /// - /// The member info representing the handler type. - /// The state keeper attribute, or null if not present. - public static IFilter? GetStateKeeperAttribute(MemberInfo handlerType) - { - // Getting polling handler attribute - Attribute stateAttr = handlerType.GetCustomAttribute(typeof(StateAttribute<,>)); - - // - return stateAttr as IFilter; - } - - /// - /// Gets all filter attributes for the specified handler type and update type. - /// - /// The member info representing the handler type. - /// The valid update type. - /// An enumerable of filter attributes. - public static IEnumerable> GetFilterAttributes(MemberInfo handlerType, UpdateType validUpdType) - { - // - IEnumerable filters = handlerType.GetCustomAttributes(); - - // - if (filters.Any(filterAttr => !filterAttr.AllowedTypes.Contains(validUpdType))) - throw new InvalidOperationException(); - - UpdateFilterAttributeBase? lastFilterAttribute = null; - foreach (UpdateFilterAttributeBase filterAttribute in filters) + if (!filterAttribute.ProcessModifiers(lastFilterAttribute)) { - if (!filterAttribute.ProcessModifiers(lastFilterAttribute)) - { - lastFilterAttribute = null; - yield return filterAttribute.AnonymousFilter; - } - else - { - lastFilterAttribute = filterAttribute; - continue; - } + lastFilterAttribute = null; + yield return filterAttribute.AnonymousFilter; + } + else + { + lastFilterAttribute = filterAttribute; + continue; } } + } - /// - /// Gets the aspects configuration for the specified handler type. - /// Inspects the handler for both self-processing (implements interfaces) and typed processing (uses attributes). - /// - /// The type of the handler to inspect. - /// A containing the aspects configuration. - public static DescriptorAspectsSet GetAspects(Type handlerType) - { - Type? typedPre = handlerType.GetCustomAttribute(typeof(BeforeExecutionAttribute<>))?.GetType().GetGenericArguments()[0]; - Type? typedPost = handlerType.GetCustomAttribute(typeof(AfterExecutionAttribute<>))?.GetType().GetGenericArguments()[0]; - return new DescriptorAspectsSet(typedPre, typedPost); - } + /// + /// Gets the aspects configuration for the specified handler type. + /// Inspects the handler for both self-processing (implements interfaces) and typed processing (uses attributes). + /// + /// The type of the handler to inspect. + /// A containing the aspects configuration. + public static DescriptorAspectsSet GetAspects(Type handlerType) + { + Type? typedPre = handlerType.GetCustomAttribute(typeof(BeforeExecutionAttribute<>))?.GetType().GetGenericArguments()[0]; + Type? typedPost = handlerType.GetCustomAttribute(typeof(AfterExecutionAttribute<>))?.GetType().GetGenericArguments()[0]; + return new DescriptorAspectsSet(typedPre, typedPost); } } diff --git a/src/Telegrator/Core/Filters/AnonymousCompiledFilter.cs b/src/Telegrator/Core/Filters/AnonymousCompiledFilter.cs index 5069444..bbe88bb 100644 --- a/src/Telegrator/Core/Filters/AnonymousCompiledFilter.cs +++ b/src/Telegrator/Core/Filters/AnonymousCompiledFilter.cs @@ -2,108 +2,107 @@ using Telegrator.Filters; using Telegrator.Logging; -namespace Telegrator.Core.Filters +namespace Telegrator.Core.Filters; + +/// +/// Represents a compiled filter that applies a set of filters to an anonymous target type. +/// +public class AnonymousCompiledFilter : Filter, INamedFilter { + private readonly Func, object, bool> FilterAction; + private readonly Func GetFilterringTarget; + private readonly string _name; + /// - /// Represents a compiled filter that applies a set of filters to an anonymous target type. + /// Gets the name of this compiled filter. /// - public class AnonymousCompiledFilter : Filter, INamedFilter + public virtual string Name => _name; + + /// + /// Initializes a new instance of the class. + /// + /// The name of the compiled filter. + /// The filter action delegate. + /// The function to get the filtering target from an update. + private AnonymousCompiledFilter(string name, Func getFilterringTarget, Func, object, bool> filterAction) { - private readonly Func, object, bool> FilterAction; - private readonly Func GetFilterringTarget; - private readonly string _name; + FilterAction = filterAction; + GetFilterringTarget = getFilterringTarget; + _name = name; + } - /// - /// Gets the name of this compiled filter. - /// - public virtual string Name => _name; + /// + /// Compiles a set of filters into an for a specific target type. + /// + /// The type of the filtering target. + /// The list of filters to compile. + /// The function to get the filtering target from an update. + /// The compiled filter. + public static AnonymousCompiledFilter Compile(IEnumerable> filters, Func getFilterringTarget) where T : class + { + return new AnonymousCompiledFilter( + string.Join("+", filters.Select(fltr => fltr.GetType().Name)), + getFilterringTarget, + (context, filterringTarget) => CanPassInternal(context, filters, filterringTarget)); + } - /// - /// Initializes a new instance of the class. - /// - /// The name of the compiled filter. - /// The filter action delegate. - /// The function to get the filtering target from an update. - private AnonymousCompiledFilter(string name, Func getFilterringTarget, Func, object, bool> filterAction) + /// + /// Compiles a set of filters into an for a specific target type with a custom name. + /// + /// The type of the filtering target. + /// The custom name for the compiled filter. + /// The list of filters to compile. + /// The function to get the filtering target from an update. + /// The compiled filter. + public static AnonymousCompiledFilter Compile(string name, IEnumerable> filters, Func getFilterringTarget) where T : class + { + return new AnonymousCompiledFilter( + name, + getFilterringTarget, + (context, filterringTarget) => CanPassInternal(context, filters, filterringTarget)); + } + + /// + /// Determines whether all filters can pass for the given context and filtering target. + /// + /// The type of the filtering target. + /// The list of filters. + /// The filter execution context. + /// The filtering target. + /// True if all filters pass; otherwise, false. + private static bool CanPassInternal(FilterExecutionContext updateContext, IEnumerable> filters, object filterringTarget) where T : class + { + FilterExecutionContext context = updateContext.CreateChild((T)filterringTarget); + foreach (IFilter filter in filters) { - FilterAction = filterAction; - GetFilterringTarget = getFilterringTarget; - _name = name; - } - - /// - /// Compiles a set of filters into an for a specific target type. - /// - /// The type of the filtering target. - /// The list of filters to compile. - /// The function to get the filtering target from an update. - /// The compiled filter. - public static AnonymousCompiledFilter Compile(IEnumerable> filters, Func getFilterringTarget) where T : class - { - return new AnonymousCompiledFilter( - string.Join("+", filters.Select(fltr => fltr.GetType().Name)), - getFilterringTarget, - (context, filterringTarget) => CanPassInternal(context, filters, filterringTarget)); - } - - /// - /// Compiles a set of filters into an for a specific target type with a custom name. - /// - /// The type of the filtering target. - /// The custom name for the compiled filter. - /// The list of filters to compile. - /// The function to get the filtering target from an update. - /// The compiled filter. - public static AnonymousCompiledFilter Compile(string name, IEnumerable> filters, Func getFilterringTarget) where T : class - { - return new AnonymousCompiledFilter( - name, - getFilterringTarget, - (context, filterringTarget) => CanPassInternal(context, filters, filterringTarget)); - } - - /// - /// Determines whether all filters can pass for the given context and filtering target. - /// - /// The type of the filtering target. - /// The list of filters. - /// The filter execution context. - /// The filtering target. - /// True if all filters pass; otherwise, false. - private static bool CanPassInternal(FilterExecutionContext updateContext, IEnumerable> filters, object filterringTarget) where T : class - { - FilterExecutionContext context = updateContext.CreateChild((T)filterringTarget); - foreach (IFilter filter in filters) - { - if (!filter.CanPass(context)) - { - if (filter is not AnonymousCompiledFilter && filter is not AnonymousTypeFilter) - TelegratorLogging.LogDebug("{0} filter of {1} didnt pass! (Compiled anonymous)", filter.GetType().Name, context.Data["handler_name"]); - - return false; - } - - context.CompletedFilters.Add(filter); - } - - return true; - } - - /// - public override bool CanPass(FilterExecutionContext context) - { - try - { - object? filterringTarget = GetFilterringTarget.Invoke(context.Input); - if (filterringTarget == null) - return false; - - return FilterAction.Invoke(context, filterringTarget); - } - catch + if (!filter.CanPass(context)) { + if (filter is not AnonymousCompiledFilter && filter is not AnonymousTypeFilter) + TelegratorLogging.LogDebug("{0} filter of {1} didnt pass! (Compiled anonymous)", filter.GetType().Name, context.Data["handler_name"]); + return false; } + + context.CompletedFilters.Add(filter); + } + + return true; + } + + /// + public override bool CanPass(FilterExecutionContext context) + { + try + { + object? filterringTarget = GetFilterringTarget.Invoke(context.Input); + if (filterringTarget == null) + return false; + + return FilterAction.Invoke(context, filterringTarget); + } + catch + { + return false; } } } diff --git a/src/Telegrator/Core/Filters/AnonymousTypeFilter.cs b/src/Telegrator/Core/Filters/AnonymousTypeFilter.cs index c25b562..973554f 100644 --- a/src/Telegrator/Core/Filters/AnonymousTypeFilter.cs +++ b/src/Telegrator/Core/Filters/AnonymousTypeFilter.cs @@ -2,109 +2,108 @@ using Telegrator.Filters; using Telegrator.Logging; -namespace Telegrator.Core.Filters +namespace Telegrator.Core.Filters; + +/// +/// Represents a filter that applies a filter action to an anonymous target type extracted from an update. +/// +public class AnonymousTypeFilter : Filter, INamedFilter { + private static readonly Type[] IgnoreLog = [typeof(CompiledFilter<>), typeof(AnonymousCompiledFilter), typeof(AnonymousTypeFilter)]; + + private readonly Func, object, bool> FilterAction; + private readonly Func GetFilterringTarget; + private readonly string _name; + /// - /// Represents a filter that applies a filter action to an anonymous target type extracted from an update. + /// Gets the name of this filter. /// - public class AnonymousTypeFilter : Filter, INamedFilter + public virtual string Name => _name; + + /// + /// Initializes a new instance of the class. + /// + /// The name of the filter. + /// The filter action delegate. + /// The function to get the filtering target from an update. + public AnonymousTypeFilter(string name, Func getFilterringTarget, Func, object, bool> filterAction) { - private static readonly Type[] IgnoreLog = [typeof(CompiledFilter<>), typeof(AnonymousCompiledFilter), typeof(AnonymousTypeFilter)]; + FilterAction = filterAction; + GetFilterringTarget = getFilterringTarget; + _name = name; + } - private readonly Func, object, bool> FilterAction; - private readonly Func GetFilterringTarget; - private readonly string _name; + /// + /// Compiles a filter for a specific target type. + /// + /// The type of the filtering target. + /// The filter to apply. + /// The function to get the filtering target from an update. + /// The compiled filter. + public static AnonymousTypeFilter Compile(IFilter filter, Func getFilterringTarget) where T : class + { + return new AnonymousTypeFilter( + filter.GetType().Name, getFilterringTarget, + (context, filterringTarget) => CanPassInternal(context, filter, filterringTarget)); + } - /// - /// Gets the name of this filter. - /// - public virtual string Name => _name; + /// + /// Compiles a filter for a specific target type with a custom name. + /// + /// The type of the filtering target. + /// The custom name for the compiled filter. + /// The filter to apply. + /// The function to get the filtering target from an update. + /// The compiled filter. + public static AnonymousTypeFilter Compile(string name, IFilter filter, Func getFilterringTarget) where T : class + { + return new AnonymousTypeFilter( + name, + getFilterringTarget, + (context, filterringTarget) => CanPassInternal(context, filter, filterringTarget)); + } - /// - /// Initializes a new instance of the class. - /// - /// The name of the filter. - /// The filter action delegate. - /// The function to get the filtering target from an update. - public AnonymousTypeFilter(string name, Func getFilterringTarget, Func, object, bool> filterAction) + /// + /// Determines whether the filter can pass for the given context and filtering target. + /// + /// The type of the filtering target. + /// The filter execution context. + /// The filter to apply. + /// The filtering target. + /// True if the filter passes; otherwise, false. + private static bool CanPassInternal(FilterExecutionContext updateContext, IFilter filter, object filterringTarget) where T : class + { + FilterExecutionContext context = updateContext.CreateChild((T)filterringTarget); + if (!filter.CanPass(context)) { - FilterAction = filterAction; - GetFilterringTarget = getFilterringTarget; - _name = name; + if (IgnoreLog.Contains(filter.GetType().MakeGenericType())) + TelegratorLogging.LogDebug("{0} filter of {1} didnt pass!", filter.GetType().Name, context.Data["handler_name"]); + + return false; } - /// - /// Compiles a filter for a specific target type. - /// - /// The type of the filtering target. - /// The filter to apply. - /// The function to get the filtering target from an update. - /// The compiled filter. - public static AnonymousTypeFilter Compile(IFilter filter, Func getFilterringTarget) where T : class - { - return new AnonymousTypeFilter( - filter.GetType().Name, getFilterringTarget, - (context, filterringTarget) => CanPassInternal(context, filter, filterringTarget)); - } + context.CompletedFilters.Add(filter); + return true; + } - /// - /// Compiles a filter for a specific target type with a custom name. - /// - /// The type of the filtering target. - /// The custom name for the compiled filter. - /// The filter to apply. - /// The function to get the filtering target from an update. - /// The compiled filter. - public static AnonymousTypeFilter Compile(string name, IFilter filter, Func getFilterringTarget) where T : class + /// + /// Determines whether the filter can pass for the given context by extracting the filtering target and applying the filter action. + /// + /// The filter execution context. + /// True if the filter passes; otherwise, false. + public override bool CanPass(FilterExecutionContext context) + { + try { - return new AnonymousTypeFilter( - name, - getFilterringTarget, - (context, filterringTarget) => CanPassInternal(context, filter, filterringTarget)); - } - - /// - /// Determines whether the filter can pass for the given context and filtering target. - /// - /// The type of the filtering target. - /// The filter execution context. - /// The filter to apply. - /// The filtering target. - /// True if the filter passes; otherwise, false. - private static bool CanPassInternal(FilterExecutionContext updateContext, IFilter filter, object filterringTarget) where T : class - { - FilterExecutionContext context = updateContext.CreateChild((T)filterringTarget); - if (!filter.CanPass(context)) - { - if (IgnoreLog.Contains(filter.GetType().MakeGenericType())) - TelegratorLogging.LogDebug("{0} filter of {1} didnt pass!", filter.GetType().Name, context.Data["handler_name"]); - + object? filterringTarget = GetFilterringTarget.Invoke(context.Input); + if (filterringTarget == null) return false; - } - context.CompletedFilters.Add(filter); - return true; + return FilterAction.Invoke(context, filterringTarget); } - - /// - /// Determines whether the filter can pass for the given context by extracting the filtering target and applying the filter action. - /// - /// The filter execution context. - /// True if the filter passes; otherwise, false. - public override bool CanPass(FilterExecutionContext context) + catch { - try - { - object? filterringTarget = GetFilterringTarget.Invoke(context.Input); - if (filterringTarget == null) - return false; - - return FilterAction.Invoke(context, filterringTarget); - } - catch - { - return false; - } + return false; } } } diff --git a/src/Telegrator/Core/Filters/CompiledFilter.cs b/src/Telegrator/Core/Filters/CompiledFilter.cs index 198c8f0..a4d5ef3 100644 --- a/src/Telegrator/Core/Filters/CompiledFilter.cs +++ b/src/Telegrator/Core/Filters/CompiledFilter.cs @@ -1,64 +1,63 @@ using Telegrator.Filters; using Telegrator.Logging; -namespace Telegrator.Core.Filters +namespace Telegrator.Core.Filters; + +/// +/// Represents a filter that composes multiple filters and passes only if all of them pass. +/// +/// The type of the input for the filter. +public class CompiledFilter : Filter, INamedFilter where T : class { + private readonly IFilter[] Filters; + private readonly string _name; + /// - /// Represents a filter that composes multiple filters and passes only if all of them pass. + /// Gets the name of this compiled filter. /// - /// The type of the input for the filter. - public class CompiledFilter : Filter, INamedFilter where T : class + public virtual string Name => _name; + + /// + /// Initializes a new instance of the class. + /// + /// The filters to compose. + public CompiledFilter(params IFilter[] filters) { - private readonly IFilter[] Filters; - private readonly string _name; + _name = string.Join("+", filters.Select(fltr => fltr.GetType().Name)); + Filters = filters; + } - /// - /// Gets the name of this compiled filter. - /// - public virtual string Name => _name; + /// + /// Initializes a new instance of the class with a custom name. + /// + /// The custom name for the compiled filter. + /// The filters to compose. + public CompiledFilter(string name, params IFilter[] filters) + { + _name = name; + Filters = filters; + } - /// - /// Initializes a new instance of the class. - /// - /// The filters to compose. - public CompiledFilter(params IFilter[] filters) + /// + /// Determines whether all composed filters pass for the given context. + /// + /// The filter execution context. + /// True if all filters pass; otherwise, false. + public override bool CanPass(FilterExecutionContext context) + { + foreach (IFilter filter in Filters) { - _name = string.Join("+", filters.Select(fltr => fltr.GetType().Name)); - Filters = filters; - } - - /// - /// Initializes a new instance of the class with a custom name. - /// - /// The custom name for the compiled filter. - /// The filters to compose. - public CompiledFilter(string name, params IFilter[] filters) - { - _name = name; - Filters = filters; - } - - /// - /// Determines whether all composed filters pass for the given context. - /// - /// The filter execution context. - /// True if all filters pass; otherwise, false. - public override bool CanPass(FilterExecutionContext context) - { - foreach (IFilter filter in Filters) + if (!filter.CanPass(context)) { - if (!filter.CanPass(context)) - { - if (filter is not AnonymousCompiledFilter && filter is not AnonymousTypeFilter) - TelegratorLogging.LogTrace("{0} filter of {1} didnt pass! (Compiled)", filter.GetType().Name, context.Data["handler_name"]); + if (filter is not AnonymousCompiledFilter && filter is not AnonymousTypeFilter) + TelegratorLogging.LogTrace("{0} filter of {1} didnt pass! (Compiled)", filter.GetType().Name, context.Data["handler_name"]); - return false; - } - - context.CompletedFilters.Add(filter); + return false; } - - return true; + + context.CompletedFilters.Add(filter); } + + return true; } } diff --git a/src/Telegrator/Core/Filters/CompletedFiltersList.cs b/src/Telegrator/Core/Filters/CompletedFiltersList.cs index 6a39ccb..b29e014 100644 --- a/src/Telegrator/Core/Filters/CompletedFiltersList.cs +++ b/src/Telegrator/Core/Filters/CompletedFiltersList.cs @@ -1,86 +1,85 @@ using System.Collections; -namespace Telegrator.Core.Filters +namespace Telegrator.Core.Filters; + +/// +/// The list containing filters worked out during Polling to further obtain additional filtering information +/// +public class CompletedFiltersList : IEnumerable { + private readonly List CompletedFilters = []; + /// - /// The list containing filters worked out during Polling to further obtain additional filtering information + /// Adds the completed filter to the list. /// - public class CompletedFiltersList : IEnumerable + /// The type of update. + /// The filter to add. + public void Add(IFilter filter) where TUpdate : class { - private readonly List CompletedFilters = []; + if (filter is AnonymousTypeFilter | filter is AnonymousCompiledFilter) + return; - /// - /// Adds the completed filter to the list. - /// - /// The type of update. - /// The filter to add. - public void Add(IFilter filter) where TUpdate : class - { - if (filter is AnonymousTypeFilter | filter is AnonymousCompiledFilter) - return; + if (!filter.IsCollectible) + return; - if (!filter.IsCollectible) - return; - - CompletedFilters.Add(filter); - } - - /// - /// Adds many completed filters to the list. - /// - /// The type of update. - /// The filters to add. - public void AddRange(IEnumerable> filters) where TUpdate : class - { - foreach (IFilter filter in filters) - Add(filter); - } - - /// - /// Looks for filters of a given type in the list. - /// - /// The filter type to search for. - /// The enumerable containing filters of the given type. - /// Thrown if the type is not a filter type. - public IEnumerable Get() where TFilter : notnull, IFilterCollectable - { - if (!typeof(TFilter).IsFilterType()) - throw new NotFilterTypeException(typeof(TFilter)); - - return CompletedFilters.OfType(); - } - - /// - /// Looks for a filter of a given type at the specified index in the list. - /// - /// The filter type to search for. - /// The index of the filter. - /// The filter of the given type at the specified index. - /// Thrown if the type is not a filter type. - /// Thrown if no filter is found at the index. - public TFilter Get(int index) where TFilter : notnull, IFilterCollectable - { - IEnumerable filters = Get(); - return filters.Any() ? filters.ElementAt(index) : throw new KeyNotFoundException(); - } - - /// - /// Returns a filter of a given type at the specified index, or null if it does not exist. - /// - /// The filter type to search for. - /// The index of the filter. - /// The filter at the specified index, or null if it does not exist. - /// Thrown if the type is not a filter type. - public TFilter? GetOrDefault(int index) where TFilter : IFilterCollectable - { - IEnumerable filters = Get(); - return filters.Any() ? filters.ElementAt(index) : default; - } - - /// - public IEnumerator GetEnumerator() => CompletedFilters.GetEnumerator(); - - /// - IEnumerator IEnumerable.GetEnumerator() => CompletedFilters.GetEnumerator(); + CompletedFilters.Add(filter); } + + /// + /// Adds many completed filters to the list. + /// + /// The type of update. + /// The filters to add. + public void AddRange(IEnumerable> filters) where TUpdate : class + { + foreach (IFilter filter in filters) + Add(filter); + } + + /// + /// Looks for filters of a given type in the list. + /// + /// The filter type to search for. + /// The enumerable containing filters of the given type. + /// Thrown if the type is not a filter type. + public IEnumerable Get() where TFilter : notnull, IFilterCollectable + { + if (!typeof(TFilter).IsFilterType()) + throw new NotFilterTypeException(typeof(TFilter)); + + return CompletedFilters.OfType(); + } + + /// + /// Looks for a filter of a given type at the specified index in the list. + /// + /// The filter type to search for. + /// The index of the filter. + /// The filter of the given type at the specified index. + /// Thrown if the type is not a filter type. + /// Thrown if no filter is found at the index. + public TFilter Get(int index) where TFilter : notnull, IFilterCollectable + { + IEnumerable filters = Get(); + return filters.Any() ? filters.ElementAt(index) : throw new KeyNotFoundException(); + } + + /// + /// Returns a filter of a given type at the specified index, or null if it does not exist. + /// + /// The filter type to search for. + /// The index of the filter. + /// The filter at the specified index, or null if it does not exist. + /// Thrown if the type is not a filter type. + public TFilter? GetOrDefault(int index) where TFilter : IFilterCollectable + { + IEnumerable filters = Get(); + return filters.Any() ? filters.ElementAt(index) : default; + } + + /// + public IEnumerator GetEnumerator() => CompletedFilters.GetEnumerator(); + + /// + IEnumerator IEnumerable.GetEnumerator() => CompletedFilters.GetEnumerator(); } diff --git a/src/Telegrator/Core/Filters/FilterExecutionContext.cs b/src/Telegrator/Core/Filters/FilterExecutionContext.cs index 1bfaab4..a16ce8c 100644 --- a/src/Telegrator/Core/Filters/FilterExecutionContext.cs +++ b/src/Telegrator/Core/Filters/FilterExecutionContext.cs @@ -1,86 +1,85 @@ using Telegram.Bot.Types; using Telegram.Bot.Types.Enums; -namespace Telegrator.Core.Filters +namespace Telegrator.Core.Filters; + +/// +/// Represents the context for filter execution, including update, input, and additional data. +/// +/// The type of the input for the filter. +public class FilterExecutionContext where T : class { /// - /// Represents the context for filter execution, including update, input, and additional data. + /// Gets the for the current context. /// - /// The type of the input for the filter. - public class FilterExecutionContext where T : class + public IUpdateRouter UpdateRouter { get; } + + /// + /// Gets the for the current context. + /// + public ITelegramBotInfo BotInfo { get; } + + /// + /// Gets the additional data dictionary for the context. + /// + public Dictionary Data { get; } + + /// + /// Gets the list of completed filters for the context. + /// + public CompletedFiltersList CompletedFilters { get; } + + /// + /// Gets the being processed. + /// + public Update Update { get; } + + /// + /// Gets the of the update. + /// + public UpdateType Type { get; } + + /// + /// Gets the input object for the filter. + /// + public T Input { get; } + + /// + /// Initializes a new instance of the class with all parameters. + /// + /// The router, that invoked filter. + /// The bot info. + /// The update. + /// The input object. + /// The additional data dictionary. + /// The list of completed filters. + public FilterExecutionContext(IUpdateRouter router, ITelegramBotInfo botInfo, Update update, T input, Dictionary data, CompletedFiltersList completedFilters) { - /// - /// Gets the for the current context. - /// - public IUpdateRouter UpdateRouter { get; } - - /// - /// Gets the for the current context. - /// - public ITelegramBotInfo BotInfo { get; } - - /// - /// Gets the additional data dictionary for the context. - /// - public Dictionary Data { get; } - - /// - /// Gets the list of completed filters for the context. - /// - public CompletedFiltersList CompletedFilters { get; } - - /// - /// Gets the being processed. - /// - public Update Update { get; } - - /// - /// Gets the of the update. - /// - public UpdateType Type { get; } - - /// - /// Gets the input object for the filter. - /// - public T Input { get; } - - /// - /// Initializes a new instance of the class with all parameters. - /// - /// The router, that invoked filter. - /// The bot info. - /// The update. - /// The input object. - /// The additional data dictionary. - /// The list of completed filters. - public FilterExecutionContext(IUpdateRouter router, ITelegramBotInfo botInfo, Update update, T input, Dictionary data, CompletedFiltersList completedFilters) - { - UpdateRouter = router; - BotInfo = botInfo; - Data = data; - CompletedFilters = completedFilters; - Update = update; - Type = update.Type; - Input = input; - } - - /// - /// Initializes a new instance of the class with default data and filters. - /// - /// The router, that invoked filter. - /// The bot info. - /// The update. - /// The input object. - public FilterExecutionContext(IUpdateRouter router, ITelegramBotInfo botInfo, Update update, T input) - : this(router, botInfo, update, input, [], []) { } - - /// - /// Creates a child context for a different input type, sharing the same data and completed filters. - /// - /// The type of the new input. - /// The new input object. - /// A new instance. - public FilterExecutionContext CreateChild(C input) where C : class - => new FilterExecutionContext(UpdateRouter, BotInfo, Update, input, Data, CompletedFilters); + UpdateRouter = router; + BotInfo = botInfo; + Data = data; + CompletedFilters = completedFilters; + Update = update; + Type = update.Type; + Input = input; } + + /// + /// Initializes a new instance of the class with default data and filters. + /// + /// The router, that invoked filter. + /// The bot info. + /// The update. + /// The input object. + public FilterExecutionContext(IUpdateRouter router, ITelegramBotInfo botInfo, Update update, T input) + : this(router, botInfo, update, input, [], []) { } + + /// + /// Creates a child context for a different input type, sharing the same data and completed filters. + /// + /// The type of the new input. + /// The new input object. + /// A new instance. + public FilterExecutionContext CreateChild(C input) where C : class + => new FilterExecutionContext(UpdateRouter, BotInfo, Update, input, Data, CompletedFilters); } diff --git a/src/Telegrator/Core/Filters/IFilter.cs b/src/Telegrator/Core/Filters/IFilter.cs index 10c3b0f..671cb0d 100644 --- a/src/Telegrator/Core/Filters/IFilter.cs +++ b/src/Telegrator/Core/Filters/IFilter.cs @@ -1,39 +1,50 @@ -namespace Telegrator.Core.Filters +namespace Telegrator.Core.Filters; + +/// +/// Interface for filters that have a name for identification and debugging purposes. +/// +public interface INamedFilter { /// - /// Interface for filters that have a name for identification and debugging purposes. + /// Gets the name of the filter. /// - public interface INamedFilter - { - /// - /// Gets the name of the filter. - /// - public string Name { get; } - } - - /// - /// Interface for filters that can be collected into a completed filters list. - /// Provides information about whether a filter should be tracked during execution. - /// - public interface IFilterCollectable - { - /// - /// Gets if filter can be collected to - /// - public bool IsCollectible { get; } - } - - /// - /// Represents a filter for a specific update type. - /// - /// The type of the update to filter. - public interface IFilter : IFilterCollectable where T : class - { - /// - /// Determines whether the filter can pass for the given context. - /// - /// The filter execution context. - /// True if the filter passes; otherwise, false. - public bool CanPass(FilterExecutionContext info); - } + public string Name { get; } +} + +/// +/// Interface for filters that can be collected into a completed filters list. +/// Provides information about whether a filter should be tracked during execution. +/// +public interface IFilterCollectable +{ + /// + /// Gets if filter can be collected to + /// + public bool IsCollectible { get; } +} + +/// +/// Represents a filter for a specific update type. +/// +/// The type of the update to filter. +public interface IFilter : IFilterCollectable where T : class +{ + /// + /// Determines whether the filter can pass for the given context. + /// + /// The filter execution context. + /// True if the filter passes; otherwise, false. + public bool CanPass(FilterExecutionContext info); +} + +/// +/// Represents a filter that joins multiple filters together. +/// +/// The type of the input for the filter. +public interface IJoinedFilter : IFilter where T : class +{ + /// + /// Gets the array of joined filters. + /// + public IFilter[] Filters { get; } } diff --git a/src/Telegrator/Core/Filters/IJoinedFilter.cs b/src/Telegrator/Core/Filters/IJoinedFilter.cs deleted file mode 100644 index 341d8d3..0000000 --- a/src/Telegrator/Core/Filters/IJoinedFilter.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace Telegrator.Core.Filters -{ - /// - /// Represents a filter that joins multiple filters together. - /// - /// The type of the input for the filter. - public interface IJoinedFilter : IFilter where T : class - { - /// - /// Gets the array of joined filters. - /// - public IFilter[] Filters { get; } - } -} diff --git a/src/Telegrator/Core/Handlers/AbstractUpdateHandler.cs b/src/Telegrator/Core/Handlers/AbstractUpdateHandler.cs index 9b21fd0..5086076 100644 --- a/src/Telegrator/Core/Handlers/AbstractUpdateHandler.cs +++ b/src/Telegrator/Core/Handlers/AbstractUpdateHandler.cs @@ -6,91 +6,90 @@ using Telegrator.Core.Filters; using Telegrator.Core.States; using Telegrator.Handlers; -namespace Telegrator.Core.Handlers +namespace Telegrator.Core.Handlers; + +/// +/// Abstract handler for Telegram updates of type . +/// +public abstract class AbstractUpdateHandler : UpdateHandlerBase, IHandlerContainerFactory, IAbstractUpdateHandler where TUpdate : class { /// - /// Abstract handler for Telegram updates of type . + /// Handler container for the current update. /// - public abstract class AbstractUpdateHandler : UpdateHandlerBase, IHandlerContainerFactory, IAbstractUpdateHandler where TUpdate : class + public IHandlerContainer Container { get; private set; } = default!; + + /// + /// Telegram Bot client associated with the current container. + /// + protected ITelegramBotClient Client => Container.Client; + + /// + /// Incoming update of type . + /// + protected TUpdate Input => Container.ActualUpdate; + + /// + /// The Telegram update being handled. + /// + protected Update HandlingUpdate => Container.HandlingUpdate; + + /// + /// Additional data associated with the handler execution. + /// + protected Dictionary ExtraData => Container.ExtraData; + + /// + /// List of successfully passed filters. + /// + protected CompletedFiltersList CompletedFilters => Container.CompletedFilters; + + /// + /// Provider for awaiting asynchronous operations. + /// + protected IAwaitingProvider AwaitingProvider => Container.AwaitingProvider; + + /// + /// Storage of bot states. + /// + protected IStateStorage StateStorage => Container.StateStorage; + + /// + /// Initializes a new instance and checks that the update type matches . + /// + /// The type of update to handle. + protected AbstractUpdateHandler(UpdateType handlingUpdateType) : base(handlingUpdateType) { - /// - /// Handler container for the current update. - /// - public IHandlerContainer Container { get; private set; } = default!; - - /// - /// Telegram Bot client associated with the current container. - /// - protected ITelegramBotClient Client => Container.Client; - - /// - /// Incoming update of type . - /// - protected TUpdate Input => Container.ActualUpdate; - - /// - /// The Telegram update being handled. - /// - protected Update HandlingUpdate => Container.HandlingUpdate; - - /// - /// Additional data associated with the handler execution. - /// - protected Dictionary ExtraData => Container.ExtraData; - - /// - /// List of successfully passed filters. - /// - protected CompletedFiltersList CompletedFilters => Container.CompletedFilters; - - /// - /// Provider for awaiting asynchronous operations. - /// - protected IAwaitingProvider AwaitingProvider => Container.AwaitingProvider; - - /// - /// Storage of bot states. - /// - protected IStateStorage StateStorage => Container.StateStorage; - - /// - /// Initializes a new instance and checks that the update type matches . - /// - /// The type of update to handle. - protected AbstractUpdateHandler(UpdateType handlingUpdateType) : base(handlingUpdateType) - { - if (!HandlingUpdateType.IsValidUpdateObject()) - throw new Exception(); - } - - /// - /// Creates a handler container for the specified awaiting provider and handler info. - /// - /// The handler descriptor info. - /// The created handler container. - public virtual IHandlerContainer CreateContainer(DescribedHandlerDescriptor handlerInfo) - { - return new HandlerContainer(handlerInfo); - } - - /// - /// Executes the handler logic using the specified container. - /// - /// The handler container. - /// Cancellation token. - /// A task representing the asynchronous operation. - protected override sealed async Task ExecuteInternal(IHandlerContainer container, CancellationToken cancellationToken) - { - Container = (IHandlerContainer)container; - return await Execute(Container, cancellationToken); - } - - /// - /// Abstract method to execute the update handling logic. - /// - /// The handler container. - /// Cancellation token. - /// A task representing the asynchronous operation. - public abstract Task Execute(IHandlerContainer container, CancellationToken cancellation); + if (!HandlingUpdateType.IsValidUpdateObject()) + throw new Exception(); } + + /// + /// Creates a handler container for the specified awaiting provider and handler info. + /// + /// The handler descriptor info. + /// The created handler container. + public virtual IHandlerContainer CreateContainer(DescribedHandlerDescriptor handlerInfo) + { + return new HandlerContainer(handlerInfo); + } + + /// + /// Executes the handler logic using the specified container. + /// + /// The handler container. + /// Cancellation token. + /// A task representing the asynchronous operation. + protected override sealed async Task ExecuteInternal(IHandlerContainer container, CancellationToken cancellationToken) + { + Container = (IHandlerContainer)container; + return await Execute(Container, cancellationToken); + } + + /// + /// Abstract method to execute the update handling logic. + /// + /// The handler container. + /// Cancellation token. + /// A task representing the asynchronous operation. + public abstract Task Execute(IHandlerContainer container, CancellationToken cancellation); } diff --git a/src/Telegrator/Core/Handlers/BranchingUpdateHandler.cs b/src/Telegrator/Core/Handlers/BranchingUpdateHandler.cs index 5a71e4d..b7533db 100644 --- a/src/Telegrator/Core/Handlers/BranchingUpdateHandler.cs +++ b/src/Telegrator/Core/Handlers/BranchingUpdateHandler.cs @@ -6,160 +6,159 @@ using Telegrator.Core.Descriptors; using Telegrator.Core.Filters; using Telegrator.Handlers; -namespace Telegrator.Core.Handlers +namespace Telegrator.Core.Handlers; + +/// +/// Abstract base class for handlers that support branching execution based on different methods. +/// Allows multiple handler methods to be defined in a single class, each with its own filters. +/// +/// The type of update being handled. +public abstract class BranchingUpdateHandler : AbstractUpdateHandler, IHandlerContainerFactory, ICustomDescriptorsProvider where TUpdate : class { /// - /// Abstract base class for handlers that support branching execution based on different methods. - /// Allows multiple handler methods to be defined in a single class, each with its own filters. + /// The method info for the current branch being executed. /// - /// The type of update being handled. - public abstract class BranchingUpdateHandler : AbstractUpdateHandler, IHandlerContainerFactory, ICustomDescriptorsProvider where TUpdate : class + private MethodInfo? branchMethodInfo = null; + + /// + /// Gets the binding flags used to discover branch methods. + /// + protected virtual BindingFlags BranchesBindingFlags => BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public; + + /// + /// Gets the allowed return types for branch methods. + /// + protected virtual Type[] AllowedBranchReturnTypes => [typeof(void), typeof(Task)]; + + /// + /// Gets the cancellation token for the current execution. + /// + protected CancellationToken Cancellation { get; private set; } = default; + + /// + /// Initializes a new instance of the class. + /// + /// The type of update this handler processes. + protected BranchingUpdateHandler(UpdateType handlingUpdateType) + : base(handlingUpdateType) { } + + /// + /// Describes all handler branches in this class. + /// + /// A collection of handler descriptors for each branch method. + /// Thrown when no branch methods are found. + public IEnumerable DescribeHandlers() { - /// - /// The method info for the current branch being executed. - /// - private MethodInfo? branchMethodInfo = null; + Type thisType = GetType(); + UpdateHandlerAttributeBase updateHandlerAttribute = HandlerInspector.GetHandlerAttribute(thisType); + IEnumerable> handlerFilters = HandlerInspector.GetFilterAttributes(thisType, HandlingUpdateType); - /// - /// Gets the binding flags used to discover branch methods. - /// - protected virtual BindingFlags BranchesBindingFlags => BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public; + MethodInfo[] handlerBranches = thisType.GetMethods().Where(branch => branch.DeclaringType == thisType).ToArray(); + if (handlerBranches.Length == 0) + throw new Exception(); - /// - /// Gets the allowed return types for branch methods. - /// - protected virtual Type[] AllowedBranchReturnTypes => [typeof(void), typeof(Task)]; + foreach (MethodInfo branch in handlerBranches) + yield return DescribeBranch(branch, updateHandlerAttribute, handlerFilters); + } - /// - /// Gets the cancellation token for the current execution. - /// - protected CancellationToken Cancellation { get; private set; } = default; + /// + /// Describes a specific branch method. + /// + /// The branch method to describe. + /// The handler attribute for the class. + /// The filters applied to the class. + /// A handler descriptor for the branch method. + /// Thrown when the branch method has parameters or invalid return type. + protected virtual HandlerDescriptor DescribeBranch(MethodInfo branch, UpdateHandlerAttributeBase handlerAttribute, IEnumerable> handlerFilters) + { + Type thisType = GetType(); - /// - /// Initializes a new instance of the class. - /// - /// The type of update this handler processes. - protected BranchingUpdateHandler(UpdateType handlingUpdateType) - : base(handlingUpdateType) { } + if (branch.GetParameters().Length != 0) + throw new Exception("Branch method must have no parameters."); - /// - /// Describes all handler branches in this class. - /// - /// A collection of handler descriptors for each branch method. - /// Thrown when no branch methods are found. - public IEnumerable DescribeHandlers() + if (!AllowedBranchReturnTypes.Any(branch.ReturnType.Equals)) + throw new Exception("Branch method must have one of allowed return types. [void, Task]"); + + try { - Type thisType = GetType(); - UpdateHandlerAttributeBase updateHandlerAttribute = HandlerInspector.GetHandlerAttribute(thisType); - IEnumerable> handlerFilters = HandlerInspector.GetFilterAttributes(thisType, HandlingUpdateType); - - MethodInfo[] handlerBranches = thisType.GetMethods().Where(branch => branch.DeclaringType == thisType).ToArray(); - if (handlerBranches.Length == 0) - throw new Exception(); - - foreach (MethodInfo branch in handlerBranches) - yield return DescribeBranch(branch, updateHandlerAttribute, handlerFilters); + handlerAttribute = HandlerInspector.GetHandlerAttribute(branch); + } + catch + { + _ = 0xBAD + 0xC0DE; } - /// - /// Describes a specific branch method. - /// - /// The branch method to describe. - /// The handler attribute for the class. - /// The filters applied to the class. - /// A handler descriptor for the branch method. - /// Thrown when the branch method has parameters or invalid return type. - protected virtual HandlerDescriptor DescribeBranch(MethodInfo branch, UpdateHandlerAttributeBase handlerAttribute, IEnumerable> handlerFilters) + List> branchFiltersList = HandlerInspector.GetFilterAttributes(branch, HandlingUpdateType).ToList(); + branchFiltersList.AddRange(handlerFilters); + + DescriptorFiltersSet filtersSet = new DescriptorFiltersSet( + handlerAttribute, + HandlerInspector.GetStateKeeperAttribute(branch), + branchFiltersList.ToArray()); + + return new HandlerBranchDescriptor(thisType, branch, HandlingUpdateType, handlerAttribute.GetIndexer(), filtersSet); + } + + /// + /// Creates a handler container for this branching handler. + /// + /// The handler information. + /// A handler container for this branching handler. + /// Thrown when the awaiting provider is not of the expected type. + public override IHandlerContainer CreateContainer(DescribedHandlerDescriptor handlerInfo) + { + return new HandlerContainer(handlerInfo); + } + + /// + /// Executes the current branch method. + /// + /// The handler container. + /// The cancellation token. + /// Thrown when no branch method is set. + public override async Task Execute(IHandlerContainer container, CancellationToken cancellation) + { + if (branchMethodInfo is null) + throw new Exception(); + + Cancellation = cancellation; + return await BranchExecuteWrapper(container, branchMethodInfo); + } + + /// + /// Wraps the execution of a branch method, handling both void and Task return types. + /// + /// The handler container. + /// The method to execute. + protected virtual async Task BranchExecuteWrapper(IHandlerContainer container, MethodInfo methodInfo) + { + if (methodInfo.ReturnType == typeof(void)) { - Type thisType = GetType(); - - if (branch.GetParameters().Length != 0) - throw new Exception("Branch method must have no parameters."); - - if (!AllowedBranchReturnTypes.Any(branch.ReturnType.Equals)) - throw new Exception("Branch method must have one of allowed return types. [void, Task]"); - - try - { - handlerAttribute = HandlerInspector.GetHandlerAttribute(branch); - } - catch - { - _ = 0xBAD + 0xC0DE; - } - - List> branchFiltersList = HandlerInspector.GetFilterAttributes(branch, HandlingUpdateType).ToList(); - branchFiltersList.AddRange(handlerFilters); - - DescriptorFiltersSet filtersSet = new DescriptorFiltersSet( - handlerAttribute, - HandlerInspector.GetStateKeeperAttribute(branch), - branchFiltersList.ToArray()); - - return new HandlerBranchDescriptor(thisType, branch, HandlingUpdateType, handlerAttribute.GetIndexer(), filtersSet); + methodInfo.Invoke(this, []); + return Result.Ok(); } - - /// - /// Creates a handler container for this branching handler. - /// - /// The handler information. - /// A handler container for this branching handler. - /// Thrown when the awaiting provider is not of the expected type. - public override IHandlerContainer CreateContainer(DescribedHandlerDescriptor handlerInfo) + else { - return new HandlerContainer(handlerInfo); + object branchReturn = methodInfo.Invoke(this, []); + if (branchReturn is not Task branchTask) + throw new InvalidOperationException(); + + return await branchTask; } + } - /// - /// Executes the current branch method. - /// - /// The handler container. - /// The cancellation token. - /// Thrown when no branch method is set. - public override async Task Execute(IHandlerContainer container, CancellationToken cancellation) + private class HandlerBranchDescriptor : HandlerDescriptor + { + public HandlerBranchDescriptor(Type decalringType, MethodInfo method, UpdateType updateType, DescriptorIndexer indexer, DescriptorFiltersSet filters) : base(DescriptorType.General, decalringType, updateType, indexer, filters) { - if (branchMethodInfo is null) - throw new Exception(); - - Cancellation = cancellation; - return await BranchExecuteWrapper(container, branchMethodInfo); - } - - /// - /// Wraps the execution of a branch method, handling both void and Task return types. - /// - /// The handler container. - /// The method to execute. - protected virtual async Task BranchExecuteWrapper(IHandlerContainer container, MethodInfo methodInfo) - { - if (methodInfo.ReturnType == typeof(void)) + DisplayString = HandlerInspector.GetDisplayName(method) ?? string.Format("{0}+{1}", method.DeclaringType.Name, method.Name); + LazyInitialization = handler => { - methodInfo.Invoke(this, []); - return Result.Ok(); - } - else - { - object branchReturn = methodInfo.Invoke(this, []); - if (branchReturn is not Task branchTask) - throw new InvalidOperationException(); - - return await branchTask; - } - } + if (handler is not BranchingUpdateHandler brancher) + throw new InvalidDataException(); - private class HandlerBranchDescriptor : HandlerDescriptor - { - public HandlerBranchDescriptor(Type decalringType, MethodInfo method, UpdateType updateType, DescriptorIndexer indexer, DescriptorFiltersSet filters) : base(DescriptorType.General, decalringType, updateType, indexer, filters) - { - DisplayString = HandlerInspector.GetDisplayName(method) ?? string.Format("{0}+{1}", method.DeclaringType.Name, method.Name); - LazyInitialization = handler => - { - if (handler is not BranchingUpdateHandler brancher) - throw new InvalidDataException(); - - brancher.branchMethodInfo = method; - }; - } + brancher.branchMethodInfo = method; + }; } } } diff --git a/src/Telegrator/Core/Handlers/Building/HandlerBuilderBase.cs b/src/Telegrator/Core/Handlers/Building/HandlerBuilderBase.cs index 937b983..194c81b 100644 --- a/src/Telegrator/Core/Handlers/Building/HandlerBuilderBase.cs +++ b/src/Telegrator/Core/Handlers/Building/HandlerBuilderBase.cs @@ -5,176 +5,175 @@ using Telegrator.Core.Filters; using Telegrator.Core.States; using Telegrator.Filters; -namespace Telegrator.Core.Handlers.Building +namespace Telegrator.Core.Handlers.Building; + +/// +/// Base class for building handler descriptors and managing handler filters. +/// +public abstract class HandlerBuilderBase(Type buildingHandlerType, UpdateType updateType, IHandlersCollection? handlerCollection) : IHandlerBuilder { + private static int HandlerServiceKeyIndex = 0; + /// - /// Base class for building handler descriptors and managing handler filters. + /// to ehich new builded handlers is adding /// - public abstract class HandlerBuilderBase(Type buildingHandlerType, UpdateType updateType, IHandlersCollection? handlerCollection) : IHandlerBuilder + protected readonly IHandlersCollection? HandlerCollection = handlerCollection; + + /// + /// of building handler + /// + protected readonly UpdateType UpdateType = updateType; + + /// + /// Type of handler to build + /// + protected readonly Type BuildingHandlerType = buildingHandlerType; + + /// + /// Filters applied to handler + /// + protected readonly List> Filters = []; + + /// + /// of building handler + /// + protected DescriptorIndexer Indexer = new DescriptorIndexer(0, 0, 0); + + /// + /// Update validation filter of building handler + /// + protected IFilter? ValidateFilter; + + /// + /// State keeper of building handler + /// + protected IFilter? StateKeeper; + + /// + /// Builds an implicit for the specified handler instance. + /// + /// The instance. + /// The created . + protected HandlerDescriptor BuildImplicitDescriptor(UpdateHandlerBase instance) { - private static int HandlerServiceKeyIndex = 0; + object handlerServiceKey = GetImplicitHandlerServiceKey(BuildingHandlerType); - /// - /// to ehich new builded handlers is adding - /// - protected readonly IHandlersCollection? HandlerCollection = handlerCollection; + HandlerDescriptor descriptor = new HandlerDescriptor( + DescriptorType.Implicit, BuildingHandlerType, + UpdateType, Indexer, ValidateFilter, + Filters.ToArray(), StateKeeper, + handlerServiceKey, instance); - /// - /// of building handler - /// - protected readonly UpdateType UpdateType = updateType; - - /// - /// Type of handler to build - /// - protected readonly Type BuildingHandlerType = buildingHandlerType; - - /// - /// Filters applied to handler - /// - protected readonly List> Filters = []; + HandlerCollection?.AddDescriptor(descriptor); + return descriptor; + } - /// - /// of building handler - /// - protected DescriptorIndexer Indexer = new DescriptorIndexer(0, 0, 0); - - /// - /// Update validation filter of building handler - /// - protected IFilter? ValidateFilter; - - /// - /// State keeper of building handler - /// - protected IFilter? StateKeeper; + /// + /// Gets a unique service key for an implicit handler type. + /// + /// The handler type. + /// A unique service key string. + public static object GetImplicitHandlerServiceKey(Type BuildingHandlerType) + => string.Format("ImplicitHandler_{0}+{1}", HandlerServiceKeyIndex++, BuildingHandlerType.Name); - /// - /// Builds an implicit for the specified handler instance. - /// - /// The instance. - /// The created . - protected HandlerDescriptor BuildImplicitDescriptor(UpdateHandlerBase instance) - { - object handlerServiceKey = GetImplicitHandlerServiceKey(BuildingHandlerType); + /// + /// Sets the update validating action for the handler. + /// + /// The to use. + /// The builder instance. + public void SetUpdateValidating(UpdateValidateAction validateAction) + { + ValidateFilter = new UpdateValidateFilter(validateAction); + } - HandlerDescriptor descriptor = new HandlerDescriptor( - DescriptorType.Implicit, BuildingHandlerType, - UpdateType, Indexer, ValidateFilter, - Filters.ToArray(), StateKeeper, - handlerServiceKey, instance); + /// + /// Sets the concurrency level for the handler. + /// + /// The concurrency value. + /// The builder instance. + public void SetConcurreny(int concurrency) + { + Indexer = Indexer.UpdateImportance(concurrency); + } - HandlerCollection?.AddDescriptor(descriptor); - return descriptor; - } + /// + /// Sets the priority for the handler. + /// + /// The priority value. + /// The builder instance. + public void SetPriority(int priority) + { + Indexer = Indexer.UpdatePriority(priority); + } - /// - /// Gets a unique service key for an implicit handler type. - /// - /// The handler type. - /// A unique service key string. - public static object GetImplicitHandlerServiceKey(Type BuildingHandlerType) - => string.Format("ImplicitHandler_{0}+{1}", HandlerServiceKeyIndex++, BuildingHandlerType.Name); + /// + /// Sets both concurrency and priority for the handler. + /// + /// The concurrency value. + /// The priority value. + /// The builder instance. + public void SetIndexer(int concurrency, int priority) + { + Indexer = new DescriptorIndexer(0, concurrency, priority); + } - /// - /// Sets the update validating action for the handler. - /// - /// The to use. - /// The builder instance. - public void SetUpdateValidating(UpdateValidateAction validateAction) - { - ValidateFilter = new UpdateValidateFilter(validateAction); - } + /// + /// Adds a filter to the handler. + /// + /// The to add. + /// The builder instance. + public void AddFilter(IFilter filter) + { + Filters.Add(filter); + } - /// - /// Sets the concurrency level for the handler. - /// - /// The concurrency value. - /// The builder instance. - public void SetConcurreny(int concurrency) - { - Indexer = Indexer.UpdateImportance(concurrency); - } + /// + /// Adds multiple filters to the handler. + /// + /// The filters to add. + /// The builder instance. + public void AddFilters(params IFilter[] filters) + { + Filters.AddRange(filters); + } - /// - /// Sets the priority for the handler. - /// - /// The priority value. - /// The builder instance. - public void SetPriority(int priority) - { - Indexer = Indexer.UpdatePriority(priority); - } + /// + /// Sets a state keeper for the handler using a specific state and key resolver. + /// + /// The key resolver. + /// The state value. + /// The state value. + /// The builder instance. + public void SetState(TValue? state) + where TKey : IStateKeyResolver, new() + where TValue : IEquatable + { + StateKeeper = new StateKeyFilter(state); + } - /// - /// Sets both concurrency and priority for the handler. - /// - /// The concurrency value. - /// The priority value. - /// The builder instance. - public void SetIndexer(int concurrency, int priority) - { - Indexer = new DescriptorIndexer(0, concurrency, priority); - } + /// + /// Adds a targeted filter for a specific filter target type. + /// + /// The type of the filter target. + /// Function to get the filter target from an update. + /// The filter to add. + /// The builder instance. + public void AddTargetedFilter(Func getFilterringTarget, IFilter filter) where TFilterTarget : class + { + AnonymousTypeFilter anonymousTypeFilter = AnonymousTypeFilter.Compile(filter, getFilterringTarget); + Filters.Add(anonymousTypeFilter); + } - /// - /// Adds a filter to the handler. - /// - /// The to add. - /// The builder instance. - public void AddFilter(IFilter filter) - { - Filters.Add(filter); - } - - /// - /// Adds multiple filters to the handler. - /// - /// The filters to add. - /// The builder instance. - public void AddFilters(params IFilter[] filters) - { - Filters.AddRange(filters); - } - - /// - /// Sets a state keeper for the handler using a specific state and key resolver. - /// - /// The key resolver. - /// The state value. - /// The state value. - /// The builder instance. - public void SetState(TValue? state) - where TKey : IStateKeyResolver, new() - where TValue : IEquatable - { - StateKeeper = new StateKeyFilter(state); - } - - /// - /// Adds a targeted filter for a specific filter target type. - /// - /// The type of the filter target. - /// Function to get the filter target from an update. - /// The filter to add. - /// The builder instance. - public void AddTargetedFilter(Func getFilterringTarget, IFilter filter) where TFilterTarget : class - { - AnonymousTypeFilter anonymousTypeFilter = AnonymousTypeFilter.Compile(filter, getFilterringTarget); - Filters.Add(anonymousTypeFilter); - } - - /// - /// Adds multiple targeted filters for a specific filter target type. - /// - /// The type of the filter target. - /// Function to get the filter target from an update. - /// The filters to add. - /// The builder instance. - public void AddTargetedFilters(Func getFilterringTarget, params IFilter[] filters) where TFilterTarget : class - { - AnonymousCompiledFilter compiledPollingFilter = AnonymousCompiledFilter.Compile(filters, getFilterringTarget); - Filters.Add(compiledPollingFilter); - } + /// + /// Adds multiple targeted filters for a specific filter target type. + /// + /// The type of the filter target. + /// Function to get the filter target from an update. + /// The filters to add. + /// The builder instance. + public void AddTargetedFilters(Func getFilterringTarget, params IFilter[] filters) where TFilterTarget : class + { + AnonymousCompiledFilter compiledPollingFilter = AnonymousCompiledFilter.Compile(filters, getFilterringTarget); + Filters.Add(compiledPollingFilter); } } diff --git a/src/Telegrator/Core/Handlers/Building/IAwaiterHandlerBuilder.cs b/src/Telegrator/Core/Handlers/Building/IAwaiterHandlerBuilder.cs index 954c51e..409714f 100644 --- a/src/Telegrator/Core/Handlers/Building/IAwaiterHandlerBuilder.cs +++ b/src/Telegrator/Core/Handlers/Building/IAwaiterHandlerBuilder.cs @@ -1,19 +1,18 @@ using Telegrator.Core.States; -namespace Telegrator.Core.Handlers.Building +namespace Telegrator.Core.Handlers.Building; + +/// +/// Defines a builder for awaiting handler logic for a specific update type. +/// +/// The type of update to await. +public interface IAwaiterHandlerBuilder : IHandlerBuilder where TUpdate : class { /// - /// Defines a builder for awaiting handler logic for a specific update type. + /// Awaits an update using the specified key resolver and cancellation token. /// - /// The type of update to await. - public interface IAwaiterHandlerBuilder : IHandlerBuilder where TUpdate : class - { - /// - /// Awaits an update using the specified key resolver and cancellation token. - /// - /// The to resolve the key. - /// The cancellation token. - /// A representing the awaited update. - public Task Await(IStateKeyResolver keyResolver, CancellationToken cancellationToken = default); - } + /// The to resolve the key. + /// The cancellation token. + /// A representing the awaited update. + public Task Await(IStateKeyResolver keyResolver, CancellationToken cancellationToken = default); } diff --git a/src/Telegrator/Core/Handlers/Building/IHandlerBuilder.cs b/src/Telegrator/Core/Handlers/Building/IHandlerBuilder.cs index 821909d..6f56169 100644 --- a/src/Telegrator/Core/Handlers/Building/IHandlerBuilder.cs +++ b/src/Telegrator/Core/Handlers/Building/IHandlerBuilder.cs @@ -2,85 +2,84 @@ using Telegrator.Core.Filters; using Telegrator.Core.States; -namespace Telegrator.Core.Handlers.Building +namespace Telegrator.Core.Handlers.Building; + +/// +/// Defines builder actions for configuring handler builders. +/// +public interface IHandlerBuilder { /// - /// Defines builder actions for configuring handler builders. + /// Sets the update validating action for the handler. /// - public interface IHandlerBuilder - { - /// - /// Sets the update validating action for the handler. - /// - /// The to use. - /// The builder instance. - public void SetUpdateValidating(UpdateValidateAction validateAction); + /// The to use. + /// The builder instance. + public void SetUpdateValidating(UpdateValidateAction validateAction); - /// - /// Sets the concurrency level for the handler. - /// - /// The concurrency value. - /// The builder instance. - public void SetConcurreny(int concurrency); + /// + /// Sets the concurrency level for the handler. + /// + /// The concurrency value. + /// The builder instance. + public void SetConcurreny(int concurrency); - /// - /// Sets the priority for the handler. - /// - /// The priority value. - /// The builder instance. - public void SetPriority(int priority); + /// + /// Sets the priority for the handler. + /// + /// The priority value. + /// The builder instance. + public void SetPriority(int priority); - /// - /// Sets both concurrency and priority for the handler. - /// - /// The concurrency value. - /// The priority value. - /// The builder instance. - public void SetIndexer(int concurrency, int priority); + /// + /// Sets both concurrency and priority for the handler. + /// + /// The concurrency value. + /// The priority value. + /// The builder instance. + public void SetIndexer(int concurrency, int priority); - /// - /// Adds a filter to the handler. - /// - /// The to add. - /// The builder instance. - public void AddFilter(IFilter filter); + /// + /// Adds a filter to the handler. + /// + /// The to add. + /// The builder instance. + public void AddFilter(IFilter filter); - /// - /// Adds multiple filters to the handler. - /// - /// The filters to add. - /// The builder instance. - public void AddFilters(params IFilter[] filters); + /// + /// Adds multiple filters to the handler. + /// + /// The filters to add. + /// The builder instance. + public void AddFilters(params IFilter[] filters); - /// - /// Sets a state keeper for the handler using a specific state and key resolver. - /// - /// The key resolver. - /// The state value. - /// The state value. - /// The builder instance. - public void SetState(TValue? state) - where TKey : IStateKeyResolver, new() - where TValue : IEquatable; + /// + /// Sets a state keeper for the handler using a specific state and key resolver. + /// + /// The key resolver. + /// The state value. + /// The state value. + /// The builder instance. + public void SetState(TValue? state) + where TKey : IStateKeyResolver, new() + where TValue : IEquatable; - /// - /// Adds a targeted filter for a specific filter target type. - /// - /// The type of the filter target. - /// Function to get the filter target from an update. - /// The filter to add. - /// The builder instance. - public void AddTargetedFilter(Func getFilterringTarget, IFilter filter) - where TFilterTarget : class; + /// + /// Adds a targeted filter for a specific filter target type. + /// + /// The type of the filter target. + /// Function to get the filter target from an update. + /// The filter to add. + /// The builder instance. + public void AddTargetedFilter(Func getFilterringTarget, IFilter filter) + where TFilterTarget : class; - /// - /// Adds multiple targeted filters for a specific filter target type. - /// - /// The type of the filter target. - /// Function to get the filter target from an update. - /// The filters to add. - /// The builder instance. - public void AddTargetedFilters(Func getFilterringTarget, params IFilter[] filters) - where TFilterTarget : class; - } + /// + /// Adds multiple targeted filters for a specific filter target type. + /// + /// The type of the filter target. + /// Function to get the filter target from an update. + /// The filters to add. + /// The builder instance. + public void AddTargetedFilters(Func getFilterringTarget, params IFilter[] filters) + where TFilterTarget : class; } diff --git a/src/Telegrator/Core/Handlers/Building/IRegularHandlerBuilder.cs b/src/Telegrator/Core/Handlers/Building/IRegularHandlerBuilder.cs index 517d9d1..dc7fccf 100644 --- a/src/Telegrator/Core/Handlers/Building/IRegularHandlerBuilder.cs +++ b/src/Telegrator/Core/Handlers/Building/IRegularHandlerBuilder.cs @@ -1,17 +1,16 @@ using Telegrator.Handlers.Building; -namespace Telegrator.Core.Handlers.Building +namespace Telegrator.Core.Handlers.Building; + +/// +/// Defines a builder for regular handler logic for a specific update type. +/// +/// The type of update to handle. +public interface IRegularHandlerBuilder : IHandlerBuilder where TUpdate : class { /// - /// Defines a builder for regular handler logic for a specific update type. + /// Builds the handler logic using the specified execution delegate. /// - /// The type of update to handle. - public interface IRegularHandlerBuilder : IHandlerBuilder where TUpdate : class - { - /// - /// Builds the handler logic using the specified execution delegate. - /// - /// The delegate to execute the handler logic. - public IHandlersCollection Build(AbstractHandlerAction executeHandler); - } + /// The delegate to execute the handler logic. + public IHandlersCollection Build(AbstractHandlerAction executeHandler); } diff --git a/src/Telegrator/Core/Handlers/Building/UpdateValidateFilter.cs b/src/Telegrator/Core/Handlers/Building/UpdateValidateFilter.cs index c903d52..a363db7 100644 --- a/src/Telegrator/Core/Handlers/Building/UpdateValidateFilter.cs +++ b/src/Telegrator/Core/Handlers/Building/UpdateValidateFilter.cs @@ -1,41 +1,40 @@ using Telegram.Bot.Types; using Telegrator.Core.Filters; -namespace Telegrator.Core.Handlers.Building +namespace Telegrator.Core.Handlers.Building; + +/// +/// Delegate for validating an update in a filter context. +/// +/// The filter execution context. +/// True if the update is valid; otherwise, false. +public delegate bool UpdateValidateAction(FilterExecutionContext context); + +/// +/// Filter that uses a delegate to validate updates. +/// +public class UpdateValidateFilter : IFilter { /// - /// Delegate for validating an update in a filter context. + /// Gets a value indicating whether this filter is collectable. Always false for this filter. /// - /// The filter execution context. - /// True if the update is valid; otherwise, false. - public delegate bool UpdateValidateAction(FilterExecutionContext context); + public bool IsCollectible => false; + private readonly UpdateValidateAction UpdateValidateAction; /// - /// Filter that uses a delegate to validate updates. + /// Initializes a new instance of the class. /// - public class UpdateValidateFilter : IFilter + /// The validation delegate to use. + public UpdateValidateFilter(UpdateValidateAction updateValidateAction) { - /// - /// Gets a value indicating whether this filter is collectable. Always false for this filter. - /// - public bool IsCollectible => false; - private readonly UpdateValidateAction UpdateValidateAction; - - /// - /// Initializes a new instance of the class. - /// - /// The validation delegate to use. - public UpdateValidateFilter(UpdateValidateAction updateValidateAction) - { - UpdateValidateAction = updateValidateAction; - } - - /// - /// Determines whether the filter can pass for the given context using the validation delegate. - /// - /// The filter execution context. - /// True if the filter passes; otherwise, false. - public bool CanPass(FilterExecutionContext info) - => UpdateValidateAction.Invoke(info); + UpdateValidateAction = updateValidateAction; } + + /// + /// Determines whether the filter can pass for the given context using the validation delegate. + /// + /// The filter execution context. + /// True if the filter passes; otherwise, false. + public bool CanPass(FilterExecutionContext info) + => UpdateValidateAction.Invoke(info); } diff --git a/src/Telegrator/Core/Handlers/EmptyHandlerContainer.cs b/src/Telegrator/Core/Handlers/EmptyHandlerContainer.cs index 20f9880..d5d60ee 100644 --- a/src/Telegrator/Core/Handlers/EmptyHandlerContainer.cs +++ b/src/Telegrator/Core/Handlers/EmptyHandlerContainer.cs @@ -3,29 +3,28 @@ using Telegram.Bot.Types; using Telegrator.Core.Filters; using Telegrator.Core.States; -namespace Telegrator.Core.Handlers +namespace Telegrator.Core.Handlers; + +/// +/// Represents an empty handler container that throws for all members. +/// +public class EmptyHandlerContainer : IHandlerContainer { - /// - /// Represents an empty handler container that throws for all members. - /// - public class EmptyHandlerContainer : IHandlerContainer - { - /// - public Update HandlingUpdate => throw new NotImplementedException(); + /// + public Update HandlingUpdate => throw new NotImplementedException(); - /// - public ITelegramBotClient Client => throw new NotImplementedException(); + /// + public ITelegramBotClient Client => throw new NotImplementedException(); - /// - public Dictionary ExtraData => throw new NotImplementedException(); + /// + public Dictionary ExtraData => throw new NotImplementedException(); - /// - public CompletedFiltersList CompletedFilters => throw new NotImplementedException(); + /// + public CompletedFiltersList CompletedFilters => throw new NotImplementedException(); - /// - public IAwaitingProvider AwaitingProvider => throw new NotImplementedException(); + /// + public IAwaitingProvider AwaitingProvider => throw new NotImplementedException(); - /// - public IStateStorage StateStorage => throw new NotImplementedException(); - } + /// + public IStateStorage StateStorage => throw new NotImplementedException(); } diff --git a/src/Telegrator/Core/Handlers/HandlerLifetimeToken.cs b/src/Telegrator/Core/Handlers/HandlerLifetimeToken.cs index 27af6ab..03c14e6 100644 --- a/src/Telegrator/Core/Handlers/HandlerLifetimeToken.cs +++ b/src/Telegrator/Core/Handlers/HandlerLifetimeToken.cs @@ -1,27 +1,26 @@ -namespace Telegrator.Core.Handlers +namespace Telegrator.Core.Handlers; + +/// +/// Represents a token that tracks the lifetime of a handler instance. +/// +public class HandlerLifetimeToken { /// - /// Represents a token that tracks the lifetime of a handler instance. + /// Event triggered when the handler's lifetime has ended. /// - public class HandlerLifetimeToken + public event Action? OnLifetimeEnded; + + /// + /// Gets a value indicating whether the handler's lifetime has ended. + /// + public bool IsEnded { get; private set; } + + /// + /// Marks the handler's lifetime as ended and triggers the event. + /// + public void LifetimeEnded() { - /// - /// Event triggered when the handler's lifetime has ended. - /// - public event Action? OnLifetimeEnded; - - /// - /// Gets a value indicating whether the handler's lifetime has ended. - /// - public bool IsEnded { get; private set; } - - /// - /// Marks the handler's lifetime as ended and triggers the event. - /// - public void LifetimeEnded() - { - IsEnded = true; - OnLifetimeEnded?.Invoke(this); - } + IsEnded = true; + OnLifetimeEnded?.Invoke(this); } } diff --git a/src/Telegrator/Core/Handlers/IHandlerContainer.cs b/src/Telegrator/Core/Handlers/IHandlerContainer.cs index a2ce418..048ccf9 100644 --- a/src/Telegrator/Core/Handlers/IHandlerContainer.cs +++ b/src/Telegrator/Core/Handlers/IHandlerContainer.cs @@ -3,42 +3,41 @@ using Telegram.Bot.Types; using Telegrator.Core.Filters; using Telegrator.Core.States; -namespace Telegrator.Core.Handlers +namespace Telegrator.Core.Handlers; + +/// +/// Interface for handler containers that provide context and resources for update handlers. +/// Contains all necessary information and services that handlers need during execution. +/// +public interface IHandlerContainer { /// - /// Interface for handler containers that provide context and resources for update handlers. - /// Contains all necessary information and services that handlers need during execution. + /// Gets the being handled. /// - public interface IHandlerContainer - { - /// - /// Gets the being handled. - /// - public Update HandlingUpdate { get; } + public Update HandlingUpdate { get; } - /// - /// Gets the used for this handler. - /// - public ITelegramBotClient Client { get; } + /// + /// Gets the used for this handler. + /// + public ITelegramBotClient Client { get; } - /// - /// Gets the extra data associated with the handler execution. - /// - public Dictionary ExtraData { get; } + /// + /// Gets the extra data associated with the handler execution. + /// + public Dictionary ExtraData { get; } - /// - /// Gets the for this handler. - /// - public CompletedFiltersList CompletedFilters { get; } + /// + /// Gets the for this handler. + /// + public CompletedFiltersList CompletedFilters { get; } - /// - /// Gets the for awaiting operations. - /// - public IAwaitingProvider AwaitingProvider { get; } + /// + /// Gets the for awaiting operations. + /// + public IAwaitingProvider AwaitingProvider { get; } - /// - /// Gets the for state managment. - /// - public IStateStorage StateStorage { get; } - } + /// + /// Gets the for state managment. + /// + public IStateStorage StateStorage { get; } } diff --git a/src/Telegrator/Core/Handlers/IHandlerContainerFactory.cs b/src/Telegrator/Core/Handlers/IHandlerContainerFactory.cs index 1b7c601..f11ddec 100644 --- a/src/Telegrator/Core/Handlers/IHandlerContainerFactory.cs +++ b/src/Telegrator/Core/Handlers/IHandlerContainerFactory.cs @@ -1,18 +1,17 @@ using Telegrator.Core.Descriptors; -namespace Telegrator.Core.Handlers +namespace Telegrator.Core.Handlers; + +/// +/// Factory interface for creating handler containers. +/// Provides a way to create handler containers with specific providers and handler information. +/// +public interface IHandlerContainerFactory { /// - /// Factory interface for creating handler containers. - /// Provides a way to create handler containers with specific providers and handler information. + /// Creates a new for the specified awaiting provider and handler info. /// - public interface IHandlerContainerFactory - { - /// - /// Creates a new for the specified awaiting provider and handler info. - /// - /// The for the handler. - /// A new instance. - public IHandlerContainer CreateContainer(DescribedHandlerDescriptor handlerInfo); - } + /// The for the handler. + /// A new instance. + public IHandlerContainer CreateContainer(DescribedHandlerDescriptor handlerInfo); } diff --git a/src/Telegrator/Core/Handlers/UpdateHandlerBase.cs b/src/Telegrator/Core/Handlers/UpdateHandlerBase.cs index 070e74c..850826e 100644 --- a/src/Telegrator/Core/Handlers/UpdateHandlerBase.cs +++ b/src/Telegrator/Core/Handlers/UpdateHandlerBase.cs @@ -4,179 +4,178 @@ using Telegram.Bot.Types.Enums; using Telegrator.Core.Descriptors; using Telegrator.Handlers.Diagnostics; -namespace Telegrator.Core.Handlers +namespace Telegrator.Core.Handlers; + +/// +/// Base class for update handlers, providing execution and lifetime management for Telegram updates. +/// +public abstract class UpdateHandlerBase(UpdateType handlingUpdateType) : IUpdateHandlerBase { /// - /// Base class for update handlers, providing execution and lifetime management for Telegram updates. + /// Gets the that this handler processes. /// - public abstract class UpdateHandlerBase(UpdateType handlingUpdateType) : IUpdateHandlerBase + public UpdateType HandlingUpdateType { get; } = handlingUpdateType; + + /// + /// Gets the associated with this handler instance. + /// + public HandlerLifetimeToken LifetimeToken { get; } = new HandlerLifetimeToken(); + + /// + public static Result Ok => Result.Ok(); + + /// + public static Result Fault => Result.Fault(); + + /// + public static Result Next => Result.Next(); + + /// + /// Executes the handler logic and marks the lifetime as ended after execution. + /// + /// + /// The cancellation token. + /// A representing the asynchronous operation. + public async Task Execute(DescribedHandlerDescriptor described, CancellationToken cancellationToken = default) { - /// - /// Gets the that this handler processes. - /// - public UpdateType HandlingUpdateType { get; } = handlingUpdateType; + if (LifetimeToken.IsEnded) + throw new Exception(); - /// - /// Gets the associated with this handler instance. - /// - public HandlerLifetimeToken LifetimeToken { get; } = new HandlerLifetimeToken(); - - /// - public static Result Ok => Result.Ok(); - - /// - public static Result Fault => Result.Fault(); - - /// - public static Result Next => Result.Next(); - - /// - /// Executes the handler logic and marks the lifetime as ended after execution. - /// - /// - /// The cancellation token. - /// A representing the asynchronous operation. - public async Task Execute(DescribedHandlerDescriptor described, CancellationToken cancellationToken = default) + try { - if (LifetimeToken.IsEnded) - throw new Exception(); + // Creating container + IHandlerContainer container = GetContainer(described); + DescriptorAspectsSet? aspects = described.From.Aspects; + + // Executing pre processor + if (aspects != null) + { + try + { + Result? preResult = await aspects + .ExecutePre(this, container, cancellationToken) + .ConfigureAwait(false); + + if (!preResult.Positive) + return preResult; + } + catch (NotImplementedException) + { + _ = 0xBAD + 0xC0DE; + } + } try { - // Creating container - IHandlerContainer container = GetContainer(described); - DescriptorAspectsSet? aspects = described.From.Aspects; + // Executing handler + Result execResult = await ExecuteInternal(container, cancellationToken).ConfigureAwait(false); + if (!execResult.Positive) + return execResult; + } + catch (NotImplementedException) + { + _ = 0xBAD + 0xC0DE; + } - // Executing pre processor + try + { + // Executing post processor if (aspects != null) { - try - { - Result? preResult = await aspects - .ExecutePre(this, container, cancellationToken) - .ConfigureAwait(false); - - if (!preResult.Positive) - return preResult; - } - catch (NotImplementedException) - { - _ = 0xBAD + 0xC0DE; - } - } - - try - { - // Executing handler - Result execResult = await ExecuteInternal(container, cancellationToken).ConfigureAwait(false); - if (!execResult.Positive) - return execResult; - } - catch (NotImplementedException) - { - _ = 0xBAD + 0xC0DE; - } - - try - { - // Executing post processor - if (aspects != null) - { - Result postResult = await aspects - .ExecutePost(this, container, cancellationToken) - .ConfigureAwait(false); - - if (!postResult.Positive) - return postResult; - } - } - catch (NotImplementedException) - { - _ = 0xBAD + 0xC0DE; - } - - // Success - return Result.Ok(); - } - catch (OperationCanceledException) - { - // Cancelled - _ = 0xBAD + 0xC0DE; - return Result.Ok(); - } - catch (Exception exception) - { - try - { - await described.UpdateRouter - .HandleErrorAsync(described.Client, exception, HandleErrorSource.HandleUpdateError, cancellationToken) + Result postResult = await aspects + .ExecutePost(this, container, cancellationToken) .ConfigureAwait(false); - } - catch (NotImplementedException) - { - _ = 0xBAD + 0xC0DE; - } - return Result.Fault(); + if (!postResult.Positive) + return postResult; + } } - finally + catch (NotImplementedException) { - LifetimeToken.LifetimeEnded(); + _ = 0xBAD + 0xC0DE; } + + // Success + return Result.Ok(); } - - private IHandlerContainer GetContainer(DescribedHandlerDescriptor handlerInfo) + catch (OperationCanceledException) { - if (this is IHandlerContainerFactory handlerDefainedContainerFactory) - return handlerDefainedContainerFactory.CreateContainer(handlerInfo); - - if (handlerInfo.UpdateRouter.DefaultContainerFactory is not null) - return handlerInfo.UpdateRouter.DefaultContainerFactory.CreateContainer(handlerInfo); - - throw new Exception(); + // Cancelled + _ = 0xBAD + 0xC0DE; + return Result.Ok(); } - - /// - /// Executes the handler logic for the given container and cancellation token. - /// - /// The for the update. - /// The cancellation token. - /// A representing the asynchronous operation. - protected abstract Task ExecuteInternal(IHandlerContainer container, CancellationToken cancellationToken); - - /// - /// Dispose resources of this handler. Override if needed - /// - /// - /// Return if dispose was successfull and garbage collecting for this object can be supressed - protected virtual bool Dispose(bool disposing) + catch (Exception exception) { - return false; + try + { + await described.UpdateRouter + .HandleErrorAsync(described.Client, exception, HandleErrorSource.HandleUpdateError, cancellationToken) + .ConfigureAwait(false); + } + catch (NotImplementedException) + { + _ = 0xBAD + 0xC0DE; + } + + return Result.Fault(); } - - /// - /// Handles failed filters during handler describing. - /// Use to control how router should treat this fail. - /// to silently continue decribing. - /// to stop\break desribing sequence. - /// - /// - /// - /// - /// - public virtual Task FiltersFallback(FiltersFallbackReport report, ITelegramBotClient client, CancellationToken cancellationToken = default) + finally { - return Task.FromResult(Result.Ok()); - } - - /// - public void Dispose() - { - if (LifetimeToken.IsEnded) - return; - - if (Dispose(true)) - GC.SuppressFinalize(this); + LifetimeToken.LifetimeEnded(); } } + + private IHandlerContainer GetContainer(DescribedHandlerDescriptor handlerInfo) + { + if (this is IHandlerContainerFactory handlerDefainedContainerFactory) + return handlerDefainedContainerFactory.CreateContainer(handlerInfo); + + if (handlerInfo.UpdateRouter.DefaultContainerFactory is not null) + return handlerInfo.UpdateRouter.DefaultContainerFactory.CreateContainer(handlerInfo); + + throw new Exception(); + } + + /// + /// Executes the handler logic for the given container and cancellation token. + /// + /// The for the update. + /// The cancellation token. + /// A representing the asynchronous operation. + protected abstract Task ExecuteInternal(IHandlerContainer container, CancellationToken cancellationToken); + + /// + /// Dispose resources of this handler. Override if needed + /// + /// + /// Return if dispose was successfull and garbage collecting for this object can be supressed + protected virtual bool Dispose(bool disposing) + { + return false; + } + + /// + /// Handles failed filters during handler describing. + /// Use to control how router should treat this fail. + /// to silently continue decribing. + /// to stop\break desribing sequence. + /// + /// + /// + /// + /// + public virtual Task FiltersFallback(FiltersFallbackReport report, ITelegramBotClient client, CancellationToken cancellationToken = default) + { + return Task.FromResult(Result.Ok()); + } + + /// + public void Dispose() + { + if (LifetimeToken.IsEnded) + return; + + if (Dispose(true)) + GC.SuppressFinalize(this); + } } diff --git a/src/Telegrator/Core/IAwaitingProvider.cs b/src/Telegrator/Core/IAwaitingProvider.cs index ef4cfe9..7484aa1 100644 --- a/src/Telegrator/Core/IAwaitingProvider.cs +++ b/src/Telegrator/Core/IAwaitingProvider.cs @@ -1,17 +1,16 @@ using Telegrator.Core.Descriptors; -namespace Telegrator.Core +namespace Telegrator.Core; + +/// +/// Provider for managing awaiting handlers that can wait for specific update types. +/// +public interface IAwaitingProvider : IHandlersProvider { /// - /// Provider for managing awaiting handlers that can wait for specific update types. + /// Registers the usage of a handler and returns a disposable object to manage its lifetime. /// - public interface IAwaitingProvider : IHandlersProvider - { - /// - /// Registers the usage of a handler and returns a disposable object to manage its lifetime. - /// - /// The to use. - /// An that manages the handler's usage lifetime. - public IDisposable UseHandler(HandlerDescriptor handlerDescriptor); - } + /// The to use. + /// An that manages the handler's usage lifetime. + public IDisposable UseHandler(HandlerDescriptor handlerDescriptor); } diff --git a/src/Telegrator/Core/ICollectingProvider.cs b/src/Telegrator/Core/ICollectingProvider.cs index 9f58160..aea174a 100644 --- a/src/Telegrator/Core/ICollectingProvider.cs +++ b/src/Telegrator/Core/ICollectingProvider.cs @@ -1,14 +1,13 @@ -namespace Telegrator.Core +namespace Telegrator.Core; + +/// +/// Interface for providers that collect and manage handler collections. +/// Provides access to a collection of handlers for various processing operations. +/// +public interface ICollectingProvider { /// - /// Interface for providers that collect and manage handler collections. - /// Provides access to a collection of handlers for various processing operations. + /// Gets the collection of handlers managed by this provider. /// - public interface ICollectingProvider - { - /// - /// Gets the collection of handlers managed by this provider. - /// - public IHandlersCollection Handlers { get; } - } + public IHandlersCollection Handlers { get; } } diff --git a/src/Telegrator/Core/ICustomDescriptorsProvider.cs b/src/Telegrator/Core/ICustomDescriptorsProvider.cs index 22d9461..6b24553 100644 --- a/src/Telegrator/Core/ICustomDescriptorsProvider.cs +++ b/src/Telegrator/Core/ICustomDescriptorsProvider.cs @@ -1,17 +1,16 @@ using Telegrator.Core.Descriptors; -namespace Telegrator.Core +namespace Telegrator.Core; + +/// +/// Interface for classes that can provide custom handler descriptors. +/// Allows classes to define their own handler description logic beyond the standard reflection-based approach. +/// +public interface ICustomDescriptorsProvider { /// - /// Interface for classes that can provide custom handler descriptors. - /// Allows classes to define their own handler description logic beyond the standard reflection-based approach. + /// Describes the handlers provided by this class. /// - public interface ICustomDescriptorsProvider - { - /// - /// Describes the handlers provided by this class. - /// - /// A collection of handler descriptors. - public IEnumerable DescribeHandlers(); - } + /// A collection of handler descriptors. + public IEnumerable DescribeHandlers(); } diff --git a/src/Telegrator/Core/IHandlersCollection.cs b/src/Telegrator/Core/IHandlersCollection.cs index 8b8efea..184b82a 100644 --- a/src/Telegrator/Core/IHandlersCollection.cs +++ b/src/Telegrator/Core/IHandlersCollection.cs @@ -1,41 +1,40 @@ using Telegram.Bot.Types.Enums; using Telegrator.Core.Descriptors; -namespace Telegrator.Core +namespace Telegrator.Core; + +/// +/// Collection class for managing handler descriptors organized by update type. +/// Provides functionality for collecting, adding, and organizing handlers. +/// +public interface IHandlersCollection { /// - /// Collection class for managing handler descriptors organized by update type. - /// Provides functionality for collecting, adding, and organizing handlers. + /// Gets the collection of 's allowed by registered handlers /// - public interface IHandlersCollection - { - /// - /// Gets the collection of 's allowed by registered handlers - /// - public IEnumerable AllowedTypes { get; } + public IEnumerable AllowedTypes { get; } - /// - /// Gets the collection of keys for the handler lists. - /// - public IEnumerable Keys { get; } - - /// - /// Gets the collection of values. - /// - public IEnumerable Values { get; } - - /// - /// Gets the for the specified . - /// - /// The update type key. - /// The handler descriptor list for the given update type. - public HandlerDescriptorList this[UpdateType updateType] { get; } + /// + /// Gets the collection of keys for the handler lists. + /// + public IEnumerable Keys { get; } + + /// + /// Gets the collection of values. + /// + public IEnumerable Values { get; } + + /// + /// Gets the for the specified . + /// + /// The update type key. + /// The handler descriptor list for the given update type. + public HandlerDescriptorList this[UpdateType updateType] { get; } - /// - /// Adds a to the collection and returns the updated collection. - /// - /// The handler descriptor to add. - /// The updated . - public IHandlersCollection AddDescriptor(HandlerDescriptor descriptor); - } + /// + /// Adds a to the collection and returns the updated collection. + /// + /// The handler descriptor to add. + /// The updated . + public IHandlersCollection AddDescriptor(HandlerDescriptor descriptor); } diff --git a/src/Telegrator/Core/IHandlersManager.cs b/src/Telegrator/Core/IHandlersManager.cs index 036af35..fb36737 100644 --- a/src/Telegrator/Core/IHandlersManager.cs +++ b/src/Telegrator/Core/IHandlersManager.cs @@ -1,11 +1,10 @@ -namespace Telegrator.Core -{ - /// - /// Combines and . - /// Provides functionality of collecting, organizing and resolving handlers instances. - /// - public interface IHandlersManager : IHandlersCollection, IHandlersProvider - { +namespace Telegrator.Core; + +/// +/// Combines and . +/// Provides functionality of collecting, organizing and resolving handlers instances. +/// +public interface IHandlersManager : IHandlersCollection, IHandlersProvider +{ - } } diff --git a/src/Telegrator/Core/IHandlersProvider.cs b/src/Telegrator/Core/IHandlersProvider.cs index d51bd2b..1e32c2d 100644 --- a/src/Telegrator/Core/IHandlersProvider.cs +++ b/src/Telegrator/Core/IHandlersProvider.cs @@ -2,39 +2,38 @@ using Telegrator.Core.Descriptors; using Telegrator.Core.Handlers; -namespace Telegrator.Core +namespace Telegrator.Core; + +/// +/// Provides methods to retrieve and describe handler information for updates. +/// +public interface IHandlersProvider { /// - /// Provides methods to retrieve and describe handler information for updates. + /// Gets the collection of 's allowed by registered handlers /// - public interface IHandlersProvider - { - /// - /// Gets the collection of 's allowed by registered handlers - /// - public IEnumerable AllowedTypes { get; } + public IEnumerable AllowedTypes { get; } - /// - /// - /// - /// - /// - /// - public bool TryGetDescriptorList(UpdateType updateType, out HandlerDescriptorList? list); + /// + /// + /// + /// + /// + /// + public bool TryGetDescriptorList(UpdateType updateType, out HandlerDescriptorList? list); - /// - /// Instantiates a handler for the given descriptor, using the appropriate creation strategy based on descriptor type. - /// Supports singleton, implicit, keyed, and general descriptor types with different instantiation patterns. - /// - /// The handler descriptor. - /// - /// An instance of for the descriptor - public UpdateHandlerBase GetHandlerInstance(HandlerDescriptor descriptor, CancellationToken cancellationToken = default); + /// + /// Instantiates a handler for the given descriptor, using the appropriate creation strategy based on descriptor type. + /// Supports singleton, implicit, keyed, and general descriptor types with different instantiation patterns. + /// + /// The handler descriptor. + /// + /// An instance of for the descriptor + public UpdateHandlerBase GetHandlerInstance(HandlerDescriptor descriptor, CancellationToken cancellationToken = default); - /// - /// Determines whether the provider contains any handlers. - /// - /// True if the provider is empty; otherwise, false. - public bool IsEmpty(); - } + /// + /// Determines whether the provider contains any handlers. + /// + /// True if the provider is empty; otherwise, false. + public bool IsEmpty(); } diff --git a/src/Telegrator/Core/IRouterExceptionHandler.cs b/src/Telegrator/Core/IRouterExceptionHandler.cs index 2277919..bea9b0d 100644 --- a/src/Telegrator/Core/IRouterExceptionHandler.cs +++ b/src/Telegrator/Core/IRouterExceptionHandler.cs @@ -1,21 +1,20 @@ using Telegram.Bot; using Telegram.Bot.Polling; -namespace Telegrator.Core +namespace Telegrator.Core; + +/// +/// Interface for handling exceptions that occur during update routing operations. +/// Provides a centralized way to handle and log errors that occur during bot operation. +/// +public interface IRouterExceptionHandler { /// - /// Interface for handling exceptions that occur during update routing operations. - /// Provides a centralized way to handle and log errors that occur during bot operation. + /// Handles exceptions that occur during update routing. /// - public interface IRouterExceptionHandler - { - /// - /// Handles exceptions that occur during update routing. - /// - /// The instance. - /// The exception that occurred. - /// The indicating the source of the error. - /// The cancellation token. - public void HandleException(ITelegramBotClient botClient, Exception exception, HandleErrorSource source, CancellationToken cancellationToken); - } + /// The instance. + /// The exception that occurred. + /// The indicating the source of the error. + /// The cancellation token. + public void HandleException(ITelegramBotClient botClient, Exception exception, HandleErrorSource source, CancellationToken cancellationToken); } diff --git a/src/Telegrator/Core/ITelegramBotInfo.cs b/src/Telegrator/Core/ITelegramBotInfo.cs index 72f2d06..f32f8f8 100644 --- a/src/Telegrator/Core/ITelegramBotInfo.cs +++ b/src/Telegrator/Core/ITelegramBotInfo.cs @@ -1,16 +1,15 @@ using Telegram.Bot.Types; -namespace Telegrator.Core +namespace Telegrator.Core; + +/// +/// Interface for providing bot information and metadata. +/// Contains information about the bot user and provides initialization capabilities. +/// +public interface ITelegramBotInfo { /// - /// Interface for providing bot information and metadata. - /// Contains information about the bot user and provides initialization capabilities. + /// Gets the representing the bot. /// - public interface ITelegramBotInfo - { - /// - /// Gets the representing the bot. - /// - public User User { get; } - } + public User User { get; } } diff --git a/src/Telegrator/Core/IUpdateHandlersPool.cs b/src/Telegrator/Core/IUpdateHandlersPool.cs index c19f229..1b22da4 100644 --- a/src/Telegrator/Core/IUpdateHandlersPool.cs +++ b/src/Telegrator/Core/IUpdateHandlersPool.cs @@ -1,38 +1,37 @@ using Telegrator.Core.Descriptors; -namespace Telegrator.Core +namespace Telegrator.Core; + +/// +/// Represents a delegate for when a handler is enqueued. +/// +/// The for the enqueued handler. +public delegate void HandlerEnqueued(DescribedHandlerDescriptor args); + +/// +/// Represents a delegate for when a handler is executing. +/// +/// The for the executing handler. +public delegate void HandlerExecuting(DescribedHandlerDescriptor args); + +/// +/// Provides a pool for managing the execution and queuing of update handlers. +/// +public interface IUpdateHandlersPool : IDisposable { /// - /// Represents a delegate for when a handler is enqueued. + /// Occurs when a handler is enqueued. /// - /// The for the enqueued handler. - public delegate void HandlerEnqueued(DescribedHandlerDescriptor args); + public event HandlerEnqueued? HandlerEnqueued; + + /// + /// Occurs when a handler is entering execution. + /// + public event HandlerExecuting? HandlerExecuting; /// - /// Represents a delegate for when a handler is executing. + /// Enqueues a collection of handlers for execution. /// - /// The for the executing handler. - public delegate void HandlerExecuting(DescribedHandlerDescriptor args); - - /// - /// Provides a pool for managing the execution and queuing of update handlers. - /// - public interface IUpdateHandlersPool : IDisposable - { - /// - /// Occurs when a handler is enqueued. - /// - public event HandlerEnqueued? HandlerEnqueued; - - /// - /// Occurs when a handler is entering execution. - /// - public event HandlerExecuting? HandlerExecuting; - - /// - /// Enqueues a collection of handlers for execution. - /// - /// The handlers to enqueue. - public Task Enqueue(params IEnumerable handlers); - } + /// The handlers to enqueue. + public Task Enqueue(params IEnumerable handlers); } diff --git a/src/Telegrator/Core/IUpdateRouter.cs b/src/Telegrator/Core/IUpdateRouter.cs index f6748c7..39ba7fa 100644 --- a/src/Telegrator/Core/IUpdateRouter.cs +++ b/src/Telegrator/Core/IUpdateRouter.cs @@ -2,47 +2,46 @@ using Telegrator.Core.Handlers; using Telegrator.Core.States; -namespace Telegrator.Core -{ +namespace Telegrator.Core; + +/// +/// Interface for update routers that handle incoming updates and manage handler execution. +/// Combines update handling capabilities with polling provider functionality and exception handling. +/// +public interface IUpdateRouter : IUpdateHandler +{ /// - /// Interface for update routers that handle incoming updates and manage handler execution. - /// Combines update handling capabilities with polling provider functionality and exception handling. + /// Gets the for the router. /// - public interface IUpdateRouter : IUpdateHandler - { - /// - /// Gets the for the router. - /// - public TelegratorOptions Options { get; } - - /// - /// Gets the that manages handler execution. - /// - public IUpdateHandlersPool HandlersPool { get; } + public TelegratorOptions Options { get; } + + /// + /// Gets the that manages handler execution. + /// + public IUpdateHandlersPool HandlersPool { get; } - /// - /// Gets the that manages handlers for polling. - /// - public IHandlersProvider HandlersProvider { get; } + /// + /// Gets the that manages handlers for polling. + /// + public IHandlersProvider HandlersProvider { get; } - /// - /// Gets the that manages awaiting handlers for polling. - /// - public IAwaitingProvider AwaitingProvider { get; } + /// + /// Gets the that manages awaiting handlers for polling. + /// + public IAwaitingProvider AwaitingProvider { get; } - /// - /// Gets the that manages storing of handlers state. - /// - public IStateStorage StateStorage { get; } + /// + /// Gets the that manages storing of handlers state. + /// + public IStateStorage StateStorage { get; } - /// - /// Gets or sets the for handling exceptions. - /// - public IRouterExceptionHandler? ExceptionHandler { get; set; } + /// + /// Gets or sets the for handling exceptions. + /// + public IRouterExceptionHandler? ExceptionHandler { get; set; } - /// - /// Default hand;er container factory - /// - public IHandlerContainerFactory? DefaultContainerFactory { get; set; } - } + /// + /// Default hand;er container factory + /// + public IHandlerContainerFactory? DefaultContainerFactory { get; set; } } diff --git a/src/Telegrator/Enums.cs b/src/Telegrator/Enums.cs index ca875f2..86d2527 100644 --- a/src/Telegrator/Enums.cs +++ b/src/Telegrator/Enums.cs @@ -1,134 +1,133 @@ -namespace Telegrator +namespace Telegrator; + +/// +/// Enumeration of dice types supported by Telegram. +/// Used for filtering dice messages and determining dice emoji representations. +/// +public enum DiceType { /// - /// Enumeration of dice types supported by Telegram. - /// Used for filtering dice messages and determining dice emoji representations. + /// Standard dice (🎲). /// - public enum DiceType - { - /// - /// Standard dice (🎲). - /// - Dice, - - /// - /// Darts (🎯). - /// - Darts, - - /// - /// Bowling (🎳). - /// - Bowling, - - /// - /// Basketball (🏀). - /// - Basketball, - - /// - /// Football (⚽). - /// - Football, - - /// - /// Casino slot machine (🎰). - /// - Casino - } - + Dice, + /// - /// Flags version of - /// Type of the , from which the message or inline query was sent + /// Darts (🎯). /// - [Flags] - public enum ChatTypeFlags - { - /// - /// Normal one-to-one chat with a user or bot - /// - Private = 0x1, - - /// - /// Normal group chat - /// - Group = 0x2, - - /// - /// A channel - /// - Channel = 0x4, - - /// - /// A supergroup - /// - Supergroup = 0x8, - - /// - /// Value possible only in : private chat with the inline query sender - /// - Sender - } - - /* + Darts, + /// - /// Messages from where this filter was originated + /// Bowling (🎳). /// - public enum FilterOrigin - { - /// - /// None, empty filter - /// - None, - - /// - /// From update validator filter - /// - Validator, - - /// - /// From state machine filter - /// - StateKeeper, - - /// - /// From regular - /// - Regualr - } - */ - - /* + Bowling, + /// - /// Levels of debug writing + /// Basketball (🏀). /// - [Flags] - public enum DebugLevel - { - /// - /// None to write - /// - None = 0x0, - - /// - /// Write debug messages from filters execution - /// - Filters = 0x1, - - /// - /// Write debug messages from handlers providers execution - /// - Providers = 0x2, - - /// - /// Write debug messages from update router's execution - /// - Router = 0x4, - - /// - /// Write debug messages from handlers pool execution - /// - HandlersPool = 0x8 - } - */ + Basketball, + + /// + /// Football (⚽). + /// + Football, + + /// + /// Casino slot machine (🎰). + /// + Casino } + +/// +/// Flags version of +/// Type of the , from which the message or inline query was sent +/// +[Flags] +public enum ChatTypeFlags +{ + /// + /// Normal one-to-one chat with a user or bot + /// + Private = 0x1, + + /// + /// Normal group chat + /// + Group = 0x2, + + /// + /// A channel + /// + Channel = 0x4, + + /// + /// A supergroup + /// + Supergroup = 0x8, + + /// + /// Value possible only in : private chat with the inline query sender + /// + Sender +} + +/* +/// +/// Messages from where this filter was originated +/// +public enum FilterOrigin +{ + /// + /// None, empty filter + /// + None, + + /// + /// From update validator filter + /// + Validator, + + /// + /// From state machine filter + /// + StateKeeper, + + /// + /// From regular + /// + Regualr +} +*/ + +/* +/// +/// Levels of debug writing +/// +[Flags] +public enum DebugLevel +{ + /// + /// None to write + /// + None = 0x0, + + /// + /// Write debug messages from filters execution + /// + Filters = 0x1, + + /// + /// Write debug messages from handlers providers execution + /// + Providers = 0x2, + + /// + /// Write debug messages from update router's execution + /// + Router = 0x4, + + /// + /// Write debug messages from handlers pool execution + /// + HandlersPool = 0x8 +} +*/ diff --git a/src/Telegrator/Exceptions.cs b/src/Telegrator/Exceptions.cs index 1b0f270..0505eca 100644 --- a/src/Telegrator/Exceptions.cs +++ b/src/Telegrator/Exceptions.cs @@ -1,52 +1,51 @@ using Telegrator.Core.Descriptors; -namespace Telegrator +namespace Telegrator; + +/// +/// Exception thrown when attempting to modify a frozen collection. +/// +public class CollectionFrozenException : Exception { /// - /// Exception thrown when attempting to modify a frozen collection. + /// Initializes a new instance of the class. /// - public class CollectionFrozenException : Exception - { - /// - /// Initializes a new instance of the class. - /// - public CollectionFrozenException() - : base("Can't change a frozen collection.") { } - } + public CollectionFrozenException() + : base("Can't change a frozen collection.") { } +} + +/// +/// Exception thrown when a type is not a valid filter type. +/// +public class NotFilterTypeException : Exception +{ + /// + /// Initializes a new instance of the class. + /// + /// The type that is not a filter type. + public NotFilterTypeException(Type type) + : base(string.Format("\"{0}\" is not a filter type", type.Name)) { } +} + +/// +/// Exception thrown when a handler execution fails. +/// Contains information about the handler and the inner exception. +/// +public class HandlerFaultedException : Exception +{ + /// + /// The handler info associated with the faulted handler. + /// + public readonly DescribedHandlerDescriptor HandlerInfo; /// - /// Exception thrown when a type is not a valid filter type. + /// Initializes a new instance of the class. /// - public class NotFilterTypeException : Exception + /// The handler info. + /// The inner exception. + public HandlerFaultedException(DescribedHandlerDescriptor handlerInfo, Exception inner) + : base(string.Format("Handler's \"{0}\" execution was faulted", handlerInfo.DisplayString), inner) { - /// - /// Initializes a new instance of the class. - /// - /// The type that is not a filter type. - public NotFilterTypeException(Type type) - : base(string.Format("\"{0}\" is not a filter type", type.Name)) { } - } - - /// - /// Exception thrown when a handler execution fails. - /// Contains information about the handler and the inner exception. - /// - public class HandlerFaultedException : Exception - { - /// - /// The handler info associated with the faulted handler. - /// - public readonly DescribedHandlerDescriptor HandlerInfo; - - /// - /// Initializes a new instance of the class. - /// - /// The handler info. - /// The inner exception. - public HandlerFaultedException(DescribedHandlerDescriptor handlerInfo, Exception inner) - : base(string.Format("Handler's \"{0}\" execution was faulted", handlerInfo.DisplayString), inner) - { - HandlerInfo = handlerInfo; - } + HandlerInfo = handlerInfo; } } diff --git a/src/Telegrator/Filters/CallbackQueryFilters.cs b/src/Telegrator/Filters/CallbackQueryFilters.cs index e92eead..6c09bb8 100644 --- a/src/Telegrator/Filters/CallbackQueryFilters.cs +++ b/src/Telegrator/Filters/CallbackQueryFilters.cs @@ -2,72 +2,71 @@ using Telegram.Bot.Types; using Telegrator.Core.Filters; -namespace Telegrator.Filters +namespace Telegrator.Filters; + +/// +/// Filter thet checks 's data +/// +public class CallbackDataFilter : Filter { + private readonly string _data; + /// - /// Filter thet checks 's data + /// Initialize new instance of /// - public class CallbackDataFilter : Filter + /// + public CallbackDataFilter(string data) { - private readonly string _data; - - /// - /// Initialize new instance of - /// - /// - public CallbackDataFilter(string data) - { - _data = data; - } - - /// - public override bool CanPass(FilterExecutionContext context) - { - return context.Input.Data == _data; - } + _data = data; } - /// - /// Filter that checks if belongs to a specific message - /// - public class CallbackInlineIdFilter : Filter + /// + public override bool CanPass(FilterExecutionContext context) { - private readonly string _inlineMessageId; - - /// - /// Initialize new instance of - /// - /// - public CallbackInlineIdFilter(string inlineMessageId) - { - _inlineMessageId = inlineMessageId; - } - - /// - public override bool CanPass(FilterExecutionContext context) - { - return context.Input.InlineMessageId == _inlineMessageId; - } - } - - /// - /// Filters callback queries by matching their data with a regular expression. - /// - public class CallbackRegexFilter : RegexFilterBase - { - /// - /// Initializes a new instance of the class with a pattern and options. - /// - /// The regex pattern. - /// The regex options. - public CallbackRegexFilter(string pattern, RegexOptions regexOptions = default) - : base(clb => clb.Data, pattern, regexOptions) { } - - /// - /// Initializes a new instance of the class with a regex object. - /// - /// The regex object. - public CallbackRegexFilter(Regex regex) - : base(clb => clb.Data, regex) { } + return context.Input.Data == _data; } } + +/// +/// Filter that checks if belongs to a specific message +/// +public class CallbackInlineIdFilter : Filter +{ + private readonly string _inlineMessageId; + + /// + /// Initialize new instance of + /// + /// + public CallbackInlineIdFilter(string inlineMessageId) + { + _inlineMessageId = inlineMessageId; + } + + /// + public override bool CanPass(FilterExecutionContext context) + { + return context.Input.InlineMessageId == _inlineMessageId; + } +} + +/// +/// Filters callback queries by matching their data with a regular expression. +/// +public class CallbackRegexFilter : RegexFilterBase +{ + /// + /// Initializes a new instance of the class with a pattern and options. + /// + /// The regex pattern. + /// The regex options. + public CallbackRegexFilter(string pattern, RegexOptions regexOptions = default) + : base(clb => clb.Data, pattern, regexOptions) { } + + /// + /// Initializes a new instance of the class with a regex object. + /// + /// The regex object. + public CallbackRegexFilter(Regex regex) + : base(clb => clb.Data, regex) { } +} diff --git a/src/Telegrator/Filters/CommandAliasFilter.cs b/src/Telegrator/Filters/CommandAliasFilter.cs index 7ae0af3..e20ac39 100644 --- a/src/Telegrator/Filters/CommandAliasFilter.cs +++ b/src/Telegrator/Filters/CommandAliasFilter.cs @@ -2,31 +2,30 @@ using Telegrator.Core.Filters; using Telegrator.Handlers; -namespace Telegrator.Filters +namespace Telegrator.Filters; + +/// +/// Filter that checks if a command matches any of the specified aliases. +/// Requires a to be applied first to extract the command. +/// +/// The command aliases to check against. +public class CommandAlliasFilter(params string[] alliases) : Filter { /// - /// Filter that checks if a command matches any of the specified aliases. - /// Requires a to be applied first to extract the command. + /// Gets the command that was received and extracted by the . /// - /// The command aliases to check against. - public class CommandAlliasFilter(params string[] alliases) : Filter - { - /// - /// Gets the command that was received and extracted by the . - /// - public string ReceivedCommand { get; private set; } = string.Empty; + public string ReceivedCommand { get; private set; } = string.Empty; - /// - /// Checks if the received command matches any of the specified aliases. - /// This filter requires a to be applied first - /// to extract the command from the message. - /// - /// The filter execution context containing the completed filters. - /// True if the command matches any of the specified aliases; otherwise, false. - public override bool CanPass(FilterExecutionContext context) - { - ReceivedCommand = context.CompletedFilters.Get(0).ReceivedCommand; - return alliases.Contains(ReceivedCommand, StringComparer.InvariantCultureIgnoreCase); - } + /// + /// Checks if the received command matches any of the specified aliases. + /// This filter requires a to be applied first + /// to extract the command from the message. + /// + /// The filter execution context containing the completed filters. + /// True if the command matches any of the specified aliases; otherwise, false. + public override bool CanPass(FilterExecutionContext context) + { + ReceivedCommand = context.CompletedFilters.Get(0).ReceivedCommand; + return alliases.Contains(ReceivedCommand, StringComparer.InvariantCultureIgnoreCase); } } diff --git a/src/Telegrator/Filters/CommandArgumentFilter.cs b/src/Telegrator/Filters/CommandArgumentFilter.cs index 762af62..1f13e8a 100644 --- a/src/Telegrator/Filters/CommandArgumentFilter.cs +++ b/src/Telegrator/Filters/CommandArgumentFilter.cs @@ -3,203 +3,202 @@ using Telegram.Bot.Types; using Telegrator.Core.Filters; using Telegrator.Handlers; -namespace Telegrator.Filters +namespace Telegrator.Filters; + +/// +/// Abstract base class for filters that operate on command arguments. +/// Provides functionality to extract and validate command arguments from message text. +/// +/// The index of the argument to filter (0-based). +public abstract class CommandArgumentFilterBase(int index) : Filter { /// - /// Abstract base class for filters that operate on command arguments. - /// Provides functionality to extract and validate command arguments from message text. + /// Gets the chosen argument at the specified index. /// - /// The index of the argument to filter (0-based). - public abstract class CommandArgumentFilterBase(int index) : Filter + protected string Target { get; private set; } = null!; + + /// + /// Determines whether the filter can pass by extracting the command argument and validating it. + /// + /// The filter execution context containing the message. + /// True if the argument exists and passes validation; otherwise, false. + public override bool CanPass(FilterExecutionContext context) { - /// - /// Gets the chosen argument at the specified index. - /// - protected string Target { get; private set; } = null!; + CommandHandlerAttribute attr = context.CompletedFilters.Get(0); + string[] args = attr.Arguments ??= context.Input.SplitArgs(); + Target = args.ElementAtOrDefault(index); - /// - /// Determines whether the filter can pass by extracting the command argument and validating it. - /// - /// The filter execution context containing the message. - /// True if the argument exists and passes validation; otherwise, false. - public override bool CanPass(FilterExecutionContext context) - { - CommandHandlerAttribute attr = context.CompletedFilters.Get(0); - string[] args = attr.Arguments ??= context.Input.SplitArgs(); - Target = args.ElementAtOrDefault(index); + if (Target == null) + return false; - if (Target == null) - return false; - - return CanPassNext(context); - } - - /// - /// Determines whether the filter can pass for the given context. - /// - /// The filter execution context. - /// True if the filter passes; otherwise, false. - protected abstract bool CanPassNext(FilterExecutionContext context); + return CanPassNext(context); } /// - /// Filter that checks if a command has arguments count >= . + /// Determines whether the filter can pass for the given context. /// - /// - public class ArgumentCountFilter(int count) : Filter + /// The filter execution context. + /// True if the filter passes; otherwise, false. + protected abstract bool CanPassNext(FilterExecutionContext context); +} + +/// +/// Filter that checks if a command has arguments count >= . +/// +/// +public class ArgumentCountFilter(int count) : Filter +{ + private readonly int Count = count; + + /// + public override bool CanPass(FilterExecutionContext context) { - private readonly int Count = count; - - /// - public override bool CanPass(FilterExecutionContext context) - { - CommandHandlerAttribute attr = context.CompletedFilters.Get(0); - string[] args = attr.Arguments ??= context.Input.SplitArgs(); - return args.Length >= Count; - } - } - - /// - /// Filter that checks if a command argument starts with a specified content. - /// - /// The content to check if the argument starts with. - /// The string comparison type to use for the check. - /// The index of the argument to check (0-based). - public class ArgumentStartsWithFilter(string content, StringComparison comparison = StringComparison.InvariantCulture, int index = 0) : CommandArgumentFilterBase(index) - { - /// - /// The content to check if the argument starts with. - /// - protected readonly string Content = content; - - /// - /// The string comparison type to use for the check. - /// - protected readonly StringComparison Comparison = comparison; - - /// - /// Checks if the command argument starts with the specified content using the configured comparison. - /// - /// The filter execution context (unused). - /// True if the argument starts with the specified content; otherwise, false. - protected override bool CanPassNext(FilterExecutionContext _) - => Target.StartsWith(Content, Comparison); - } - - /// - /// Filter that checks if a command argument ends with a specified content. - /// - /// The content to check if the argument ends with. - /// The string comparison type to use for the check. - /// The index of the argument to check (0-based). - public class ArgumentEndsWithFilter(string content, StringComparison comparison = StringComparison.InvariantCulture, int index = 0) : CommandArgumentFilterBase(index) - { - /// - /// The content to check if the argument ends with. - /// - protected readonly string Content = content; - - /// - /// The string comparison type to use for the check. - /// - protected readonly StringComparison Comparison = comparison; - - /// - /// Checks if the command argument ends with the specified content using the configured comparison. - /// - /// The filter execution context (unused). - /// True if the argument ends with the specified content; otherwise, false. - protected override bool CanPassNext(FilterExecutionContext _) - => Target.EndsWith(Content, Comparison); - } - - /// - /// Filter that checks if a command argument contains a specified content. - /// - /// The content to check if the argument contains. - /// The string comparison type to use for the check. - /// The index of the argument to check (0-based). - public class ArgumentContainsFilter(string content, StringComparison comparison = StringComparison.InvariantCulture, int index = 0) : CommandArgumentFilterBase(index) - { - /// - /// The content to check if the argument contains. - /// - protected readonly string Content = content; - - /// - /// The string comparison type to use for the check. - /// - protected readonly StringComparison Comparison = comparison; - - /// - /// Checks if the command argument contains the specified content using the configured comparison. - /// - /// The filter execution context (unused). - /// True if the argument contains the specified content; otherwise, false. - protected override bool CanPassNext(FilterExecutionContext _) - => Target.IndexOf(Content, Comparison) >= 0; - } - - /// - /// Filter that checks if a command argument equals a specified content. - /// - /// The content to check if the argument equals. - /// The string comparison type to use for the check. - /// The index of the argument to check (0-based). - public class ArgumentEqualsFilter(string content, StringComparison comparison = StringComparison.InvariantCulture, int index = 0) : CommandArgumentFilterBase(index) - { - /// - /// The content to check if the argument equals. - /// - protected readonly string Content = content; - - /// - /// The string comparison type to use for the check. - /// - protected readonly StringComparison Comparison = comparison; - - /// - /// Checks if the command argument equals the specified content using the configured comparison. - /// - /// The filter execution context (unused). - /// True if the argument equals the specified content; otherwise, false. - protected override bool CanPassNext(FilterExecutionContext _) - => Target.Equals(Content, Comparison); - } - - /// - /// Filter that checks if a command argument matches a regular expression pattern. - /// - /// The regular expression to match against the argument. - /// The index of the argument to check (0-based). - public class ArgumentRegexFilter(Regex regex, int index = 0) : CommandArgumentFilterBase(index) - { - private readonly Regex _regex = regex; - - /// - /// Gets the match found by the regex. - /// - public Match Match { get; private set; } = null!; - - /// - /// Initializes a new instance of with a regex pattern. - /// - /// The regular expression pattern to match. - /// The regex options to use. - /// The timeout for the regex match operation. - /// The index of the argument to check (0-based). - public ArgumentRegexFilter(string pattern, RegexOptions options = RegexOptions.None, TimeSpan matchTimeout = default, int index = 0) - : this(new Regex(pattern, options, matchTimeout), index) { } - - /// - /// Checks if the command argument matches the regular expression pattern. - /// - /// The filter execution context. - /// True if the argument matches the regex pattern; otherwise, false. - protected override bool CanPassNext(FilterExecutionContext context) - { - Match = _regex.Match(Target); - return Match.Success; - } + CommandHandlerAttribute attr = context.CompletedFilters.Get(0); + string[] args = attr.Arguments ??= context.Input.SplitArgs(); + return args.Length >= Count; + } +} + +/// +/// Filter that checks if a command argument starts with a specified content. +/// +/// The content to check if the argument starts with. +/// The string comparison type to use for the check. +/// The index of the argument to check (0-based). +public class ArgumentStartsWithFilter(string content, StringComparison comparison = StringComparison.InvariantCulture, int index = 0) : CommandArgumentFilterBase(index) +{ + /// + /// The content to check if the argument starts with. + /// + protected readonly string Content = content; + + /// + /// The string comparison type to use for the check. + /// + protected readonly StringComparison Comparison = comparison; + + /// + /// Checks if the command argument starts with the specified content using the configured comparison. + /// + /// The filter execution context (unused). + /// True if the argument starts with the specified content; otherwise, false. + protected override bool CanPassNext(FilterExecutionContext _) + => Target.StartsWith(Content, Comparison); +} + +/// +/// Filter that checks if a command argument ends with a specified content. +/// +/// The content to check if the argument ends with. +/// The string comparison type to use for the check. +/// The index of the argument to check (0-based). +public class ArgumentEndsWithFilter(string content, StringComparison comparison = StringComparison.InvariantCulture, int index = 0) : CommandArgumentFilterBase(index) +{ + /// + /// The content to check if the argument ends with. + /// + protected readonly string Content = content; + + /// + /// The string comparison type to use for the check. + /// + protected readonly StringComparison Comparison = comparison; + + /// + /// Checks if the command argument ends with the specified content using the configured comparison. + /// + /// The filter execution context (unused). + /// True if the argument ends with the specified content; otherwise, false. + protected override bool CanPassNext(FilterExecutionContext _) + => Target.EndsWith(Content, Comparison); +} + +/// +/// Filter that checks if a command argument contains a specified content. +/// +/// The content to check if the argument contains. +/// The string comparison type to use for the check. +/// The index of the argument to check (0-based). +public class ArgumentContainsFilter(string content, StringComparison comparison = StringComparison.InvariantCulture, int index = 0) : CommandArgumentFilterBase(index) +{ + /// + /// The content to check if the argument contains. + /// + protected readonly string Content = content; + + /// + /// The string comparison type to use for the check. + /// + protected readonly StringComparison Comparison = comparison; + + /// + /// Checks if the command argument contains the specified content using the configured comparison. + /// + /// The filter execution context (unused). + /// True if the argument contains the specified content; otherwise, false. + protected override bool CanPassNext(FilterExecutionContext _) + => Target.IndexOf(Content, Comparison) >= 0; +} + +/// +/// Filter that checks if a command argument equals a specified content. +/// +/// The content to check if the argument equals. +/// The string comparison type to use for the check. +/// The index of the argument to check (0-based). +public class ArgumentEqualsFilter(string content, StringComparison comparison = StringComparison.InvariantCulture, int index = 0) : CommandArgumentFilterBase(index) +{ + /// + /// The content to check if the argument equals. + /// + protected readonly string Content = content; + + /// + /// The string comparison type to use for the check. + /// + protected readonly StringComparison Comparison = comparison; + + /// + /// Checks if the command argument equals the specified content using the configured comparison. + /// + /// The filter execution context (unused). + /// True if the argument equals the specified content; otherwise, false. + protected override bool CanPassNext(FilterExecutionContext _) + => Target.Equals(Content, Comparison); +} + +/// +/// Filter that checks if a command argument matches a regular expression pattern. +/// +/// The regular expression to match against the argument. +/// The index of the argument to check (0-based). +public class ArgumentRegexFilter(Regex regex, int index = 0) : CommandArgumentFilterBase(index) +{ + private readonly Regex _regex = regex; + + /// + /// Gets the match found by the regex. + /// + public Match Match { get; private set; } = null!; + + /// + /// Initializes a new instance of with a regex pattern. + /// + /// The regular expression pattern to match. + /// The regex options to use. + /// The timeout for the regex match operation. + /// The index of the argument to check (0-based). + public ArgumentRegexFilter(string pattern, RegexOptions options = RegexOptions.None, TimeSpan matchTimeout = default, int index = 0) + : this(new Regex(pattern, options, matchTimeout), index) { } + + /// + /// Checks if the command argument matches the regular expression pattern. + /// + /// The filter execution context. + /// True if the argument matches the regex pattern; otherwise, false. + protected override bool CanPassNext(FilterExecutionContext context) + { + Match = _regex.Match(Target); + return Match.Success; } } diff --git a/src/Telegrator/Filters/EnvironmentFilters.cs b/src/Telegrator/Filters/EnvironmentFilters.cs index e07c445..ecfa286 100644 --- a/src/Telegrator/Filters/EnvironmentFilters.cs +++ b/src/Telegrator/Filters/EnvironmentFilters.cs @@ -2,124 +2,123 @@ using Telegram.Bot.Types; using Telegrator.Core.Filters; -namespace Telegrator.Filters +namespace Telegrator.Filters; + +/// +/// Abstract base class for filters that operate based on the current environment. +/// Provides functionality to detect debug vs release environments. +/// +public abstract class EnvironmentFilter : Filter { /// - /// Abstract base class for filters that operate based on the current environment. - /// Provides functionality to detect debug vs release environments. + /// Gets a value indicating whether the current environment is debug mode. + /// This is set during static initialization based on the DEBUG conditional compilation symbol. /// - public abstract class EnvironmentFilter : Filter - { - /// - /// Gets a value indicating whether the current environment is debug mode. - /// This is set during static initialization based on the DEBUG conditional compilation symbol. - /// - protected static bool IsCurrentEnvDebug { get; private set; } = false; - - /// - /// Static constructor that initializes the environment detection. - /// - static EnvironmentFilter() - => SetIsCurrentEnvDebug(); - - /// - /// Sets the debug environment flag. This method is only compiled in DEBUG builds. - /// - [Conditional("DEBUG")] - private static void SetIsCurrentEnvDebug() - => IsCurrentEnvDebug = true; - } + protected static bool IsCurrentEnvDebug { get; private set; } = false; /// - /// Filter that only passes in debug environment builds. + /// Static constructor that initializes the environment detection. /// - public class IsDebugEnvironmentFilter() : EnvironmentFilter - { - /// - /// Checks if the current environment is debug mode. - /// - /// The filter execution context (unused). - /// True if the current environment is debug mode; otherwise, false. - public override bool CanPass(FilterExecutionContext _) - => IsCurrentEnvDebug; - } + static EnvironmentFilter() + => SetIsCurrentEnvDebug(); /// - /// Filter that only passes in release environment builds. + /// Sets the debug environment flag. This method is only compiled in DEBUG builds. /// - public class IsReleaseEnvironmentFilter() : EnvironmentFilter - { - /// - /// Checks if the current environment is release mode. - /// - /// The filter execution context (unused). - /// True if the current environment is release mode; otherwise, false. - public override bool CanPass(FilterExecutionContext _) - => !IsCurrentEnvDebug; - } + [Conditional("DEBUG")] + private static void SetIsCurrentEnvDebug() + => IsCurrentEnvDebug = true; +} + +/// +/// Filter that only passes in debug environment builds. +/// +public class IsDebugEnvironmentFilter() : EnvironmentFilter +{ + /// + /// Checks if the current environment is debug mode. + /// + /// The filter execution context (unused). + /// True if the current environment is debug mode; otherwise, false. + public override bool CanPass(FilterExecutionContext _) + => IsCurrentEnvDebug; +} + +/// +/// Filter that only passes in release environment builds. +/// +public class IsReleaseEnvironmentFilter() : EnvironmentFilter +{ + /// + /// Checks if the current environment is release mode. + /// + /// The filter execution context (unused). + /// True if the current environment is release mode; otherwise, false. + public override bool CanPass(FilterExecutionContext _) + => !IsCurrentEnvDebug; +} + +/// +/// Filter that checks environment variable values. +/// +/// The environment variable name to check. +/// The expected value of the environment variable (optional). +/// The string comparison type to use for value matching. +public class EnvironmentVariableFilter(string variable, string? value, StringComparison comparison) : Filter +{ + /// + /// The environment variable name to check. + /// + private readonly string _variable = variable; + + /// + /// The expected value of the environment variable (optional). + /// + private readonly string? _value = value; + + /// + /// The string comparison type to use for value matching. + /// + private readonly StringComparison _comparison = comparison; /// - /// Filter that checks environment variable values. + /// Initializes a new instance of the class with a specific value. /// /// The environment variable name to check. - /// The expected value of the environment variable (optional). - /// The string comparison type to use for value matching. - public class EnvironmentVariableFilter(string variable, string? value, StringComparison comparison) : Filter + /// The expected value of the environment variable. + public EnvironmentVariableFilter(string variable, string? value) + : this(variable, value, StringComparison.InvariantCulture) { } + + /// + /// Initializes a new instance of the class that checks for non-null values. + /// + /// The environment variable name to check. + public EnvironmentVariableFilter(string variable) + : this(variable, "{NOT_NULL}", StringComparison.InvariantCulture) { } + + /// + /// Initializes a new instance of the class with custom comparison. + /// + /// The environment variable name to check. + /// The string comparison type to use. + public EnvironmentVariableFilter(string variable, StringComparison comparison) + : this(variable, "{NOT_NULL}", comparison) { } + + /// + /// Checks if the environment variable matches the expected criteria. + /// + /// The filter execution context (unused). + /// True if the environment variable matches the criteria; otherwise, false. + public override bool CanPass(FilterExecutionContext _) { - /// - /// The environment variable name to check. - /// - private readonly string _variable = variable; - - /// - /// The expected value of the environment variable (optional). - /// - private readonly string? _value = value; - - /// - /// The string comparison type to use for value matching. - /// - private readonly StringComparison _comparison = comparison; + string? envValue = Environment.GetEnvironmentVariable(_variable); - /// - /// Initializes a new instance of the class with a specific value. - /// - /// The environment variable name to check. - /// The expected value of the environment variable. - public EnvironmentVariableFilter(string variable, string? value) - : this(variable, value, StringComparison.InvariantCulture) { } + if (envValue == null) + return _value == null; - /// - /// Initializes a new instance of the class that checks for non-null values. - /// - /// The environment variable name to check. - public EnvironmentVariableFilter(string variable) - : this(variable, "{NOT_NULL}", StringComparison.InvariantCulture) { } + if (_value == "{NOT_NULL}") + return true; - /// - /// Initializes a new instance of the class with custom comparison. - /// - /// The environment variable name to check. - /// The string comparison type to use. - public EnvironmentVariableFilter(string variable, StringComparison comparison) - : this(variable, "{NOT_NULL}", comparison) { } - - /// - /// Checks if the environment variable matches the expected criteria. - /// - /// The filter execution context (unused). - /// True if the environment variable matches the criteria; otherwise, false. - public override bool CanPass(FilterExecutionContext _) - { - string? envValue = Environment.GetEnvironmentVariable(_variable); - - if (envValue == null) - return _value == null; - - if (_value == "{NOT_NULL}") - return true; - - return envValue.Equals(_value, _comparison); - } + return envValue.Equals(_value, _comparison); } } diff --git a/src/Telegrator/Filters/Filter.cs b/src/Telegrator/Filters/Filter.cs index e9f9845..4514174 100644 --- a/src/Telegrator/Filters/Filter.cs +++ b/src/Telegrator/Filters/Filter.cs @@ -1,135 +1,134 @@ using Telegrator.Core.Filters; -namespace Telegrator.Filters +namespace Telegrator.Filters; + +/// +/// Base class for filters, providing logical operations and collectability. +/// +/// The type of the input for the filter. +public abstract class Filter : IFilter where T : class { /// - /// Base class for filters, providing logical operations and collectability. + /// Creates a filter from a function. /// - /// The type of the input for the filter. - public abstract class Filter : IFilter where T : class - { - /// - /// Creates a filter from a function. - /// - /// The filter function. - /// A instance. - public static Filter If(Func, bool> filter) - => new FunctionFilter(filter); - - /// - /// Creates a filter that always passes. - /// - /// An instance. - public static AnyFilter Any() - => new AnyFilter(); - - /// - /// Creates a filter that inverts the result of this filter. - /// - /// A instance. - public Filter Not() - => new ReverseFilter(this); - - /// - /// Creates a filter that inverts the result of this filter. - /// - /// - /// A instance. - public static Filter Not(IFilter filter) where Q : class - => new ReverseFilter(filter); - - /// - /// Creates a filter that passes only if both this and the specified filter pass. - /// - /// The filter to combine with. - /// An instance. - public AndFilter And(IFilter filter) - => new AndFilter(this, filter); - - /// - /// Creates a filter that passes if either this or the specified filter pass. - /// - /// The filter to combine with. - /// An instance. - public OrFilter Or(IFilter filter) - => new OrFilter(this, filter); - - /// - /// Creates a filter that passes if either this or the specified filter pass. - /// - /// - /// - /// - /// An instance. - public static OrFilter Or(IFilter left, IFilter right) where Q : class - => new OrFilter(left, right); - - /// - /// Gets a value indicating whether this filter is collectible. - /// - public bool IsCollectible => GetType().HasPublicProperties(); - - /// - /// Determines whether the filter can pass for the given context. - /// - /// The filter execution context. - /// True if the filter passes; otherwise, false. - public abstract bool CanPass(FilterExecutionContext context); - - /// - /// Implicitly creates from function - /// - /// - public static implicit operator Filter(Func, bool> filter) => Filter.If(filter); - } + /// The filter function. + /// A instance. + public static Filter If(Func, bool> filter) + => new FunctionFilter(filter); /// - /// A filter that always passes. + /// Creates a filter that always passes. /// - /// The type of the input for the filter. - public class AnyFilter : Filter where T : class - { - /// - public override bool CanPass(FilterExecutionContext context) - => true; - } + /// An instance. + public static AnyFilter Any() + => new AnyFilter(); /// - /// A filter that inverts the result of another filter. + /// Creates a filter that inverts the result of this filter. /// - /// The type of the input for the filter. - public class ReverseFilter : Filter where T : class - { - private readonly IFilter filter; - - /// - /// Initializes a new instance of the class. - /// - /// The filter to invert. - public ReverseFilter(IFilter filter) - => this.filter = filter; - - /// - public override bool CanPass(FilterExecutionContext context) - => !filter.CanPass(context); - } + /// A instance. + public Filter Not() + => new ReverseFilter(this); /// - /// A filter that uses a function to determine if it passes. + /// Creates a filter that inverts the result of this filter. /// - /// The type of the input for the filter. - public class FunctionFilter : Filter where T : class - { - private readonly Func, bool>? FilterFunc; - /// - /// Initializes a new instance of the class. - /// - /// The filter function. - public FunctionFilter(Func, bool> funcFilter) - => FilterFunc = funcFilter; + /// + /// A instance. + public static Filter Not(IFilter filter) where Q : class + => new ReverseFilter(filter); - /// - public override bool CanPass(FilterExecutionContext context) - => context.Input != null && FilterFunc != null && FilterFunc(context); - } + /// + /// Creates a filter that passes only if both this and the specified filter pass. + /// + /// The filter to combine with. + /// An instance. + public AndFilter And(IFilter filter) + => new AndFilter(this, filter); + + /// + /// Creates a filter that passes if either this or the specified filter pass. + /// + /// The filter to combine with. + /// An instance. + public OrFilter Or(IFilter filter) + => new OrFilter(this, filter); + + /// + /// Creates a filter that passes if either this or the specified filter pass. + /// + /// + /// + /// + /// An instance. + public static OrFilter Or(IFilter left, IFilter right) where Q : class + => new OrFilter(left, right); + + /// + /// Gets a value indicating whether this filter is collectible. + /// + public bool IsCollectible => GetType().HasPublicProperties(); + + /// + /// Determines whether the filter can pass for the given context. + /// + /// The filter execution context. + /// True if the filter passes; otherwise, false. + public abstract bool CanPass(FilterExecutionContext context); + + /// + /// Implicitly creates from function + /// + /// + public static implicit operator Filter(Func, bool> filter) => Filter.If(filter); +} + +/// +/// A filter that always passes. +/// +/// The type of the input for the filter. +public class AnyFilter : Filter where T : class +{ + /// + public override bool CanPass(FilterExecutionContext context) + => true; +} + +/// +/// A filter that inverts the result of another filter. +/// +/// The type of the input for the filter. +public class ReverseFilter : Filter where T : class +{ + private readonly IFilter filter; + + /// + /// Initializes a new instance of the class. + /// + /// The filter to invert. + public ReverseFilter(IFilter filter) + => this.filter = filter; + + /// + public override bool CanPass(FilterExecutionContext context) + => !filter.CanPass(context); +} + +/// +/// A filter that uses a function to determine if it passes. +/// +/// The type of the input for the filter. +public class FunctionFilter : Filter where T : class +{ + private readonly Func, bool>? FilterFunc; + /// + /// Initializes a new instance of the class. + /// + /// The filter function. + public FunctionFilter(Func, bool> funcFilter) + => FilterFunc = funcFilter; + + /// + public override bool CanPass(FilterExecutionContext context) + => context.Input != null && FilterFunc != null && FilterFunc(context); } diff --git a/src/Telegrator/Filters/JoinedFilter.cs b/src/Telegrator/Filters/JoinedFilter.cs index 0602def..dd1646c 100644 --- a/src/Telegrator/Filters/JoinedFilter.cs +++ b/src/Telegrator/Filters/JoinedFilter.cs @@ -1,54 +1,53 @@ using Telegrator.Core.Filters; -namespace Telegrator.Filters +namespace Telegrator.Filters; + +/// +/// Base class for filters that join multiple filters together. +/// +/// The type of the input for the filter. +public abstract class JoinedFilter(params IFilter[] filters) : Filter, IJoinedFilter where T : class { /// - /// Base class for filters that join multiple filters together. + /// Gets the array of joined filters. /// - /// The type of the input for the filter. - public abstract class JoinedFilter(params IFilter[] filters) : Filter, IJoinedFilter where T : class - { - /// - /// Gets the array of joined filters. - /// - public IFilter[] Filters { get; } = filters; - } - - /// - /// A filter that passes only if both joined filters pass. - /// - /// The type of the input for the filter. - public class AndFilter : JoinedFilter where T : class - { - /// - /// Initializes a new instance of the class. - /// - /// The left filter. - /// The right filter. - public AndFilter(IFilter leftFilter, IFilter rightFilter) - : base(leftFilter, rightFilter) { } - - /// - public override bool CanPass(FilterExecutionContext context) - => Filters[0].CanPass(context) && Filters[1].CanPass(context); - } - - /// - /// A filter that passes if at least one of the joined filters passes. - /// - /// The type of the input for the filter. - public class OrFilter : JoinedFilter where T : class - { - /// - /// Initializes a new instance of the class. - /// - /// The left filter. - /// The right filter. - public OrFilter(IFilter leftFilter, IFilter rightFilter) - : base(leftFilter, rightFilter) { } - - /// - public override bool CanPass(FilterExecutionContext context) - => Filters[0].CanPass(context) || Filters[1].CanPass(context); - } + public IFilter[] Filters { get; } = filters; +} + +/// +/// A filter that passes only if both joined filters pass. +/// +/// The type of the input for the filter. +public class AndFilter : JoinedFilter where T : class +{ + /// + /// Initializes a new instance of the class. + /// + /// The left filter. + /// The right filter. + public AndFilter(IFilter leftFilter, IFilter rightFilter) + : base(leftFilter, rightFilter) { } + + /// + public override bool CanPass(FilterExecutionContext context) + => Filters[0].CanPass(context) && Filters[1].CanPass(context); +} + +/// +/// A filter that passes if at least one of the joined filters passes. +/// +/// The type of the input for the filter. +public class OrFilter : JoinedFilter where T : class +{ + /// + /// Initializes a new instance of the class. + /// + /// The left filter. + /// The right filter. + public OrFilter(IFilter leftFilter, IFilter rightFilter) + : base(leftFilter, rightFilter) { } + + /// + public override bool CanPass(FilterExecutionContext context) + => Filters[0].CanPass(context) || Filters[1].CanPass(context); } diff --git a/src/Telegrator/Filters/MentionedFilter.cs b/src/Telegrator/Filters/MentionedFilter.cs index 2cf6425..3888f76 100644 --- a/src/Telegrator/Filters/MentionedFilter.cs +++ b/src/Telegrator/Filters/MentionedFilter.cs @@ -2,63 +2,62 @@ using Telegram.Bot.Types.Enums; using Telegrator.Core.Filters; -namespace Telegrator.Filters +namespace Telegrator.Filters; + +/// +/// Filter that checks if a message contains a mention of the bot or a specific user. +/// Requires a to be applied first to identify mention entities. +/// +public class MentionedFilter : MessageFilterBase { /// - /// Filter that checks if a message contains a mention of the bot or a specific user. - /// Requires a to be applied first to identify mention entities. + /// The username to check for in the mention (null means check for bot's username). /// - public class MentionedFilter : MessageFilterBase + private readonly string? Mention; + + /// + /// Initializes a new instance of the class that checks for bot mentions. + /// + public MentionedFilter() { - /// - /// The username to check for in the mention (null means check for bot's username). - /// - private readonly string? Mention; + Mention = null; + } - /// - /// Initializes a new instance of the class that checks for bot mentions. - /// - public MentionedFilter() - { - Mention = null; - } - - /// - /// Initializes a new instance of the class that checks for specific user mentions. - /// - /// The username to check for in the mention. - public MentionedFilter(string mention) - { - Mention = mention.TrimStart('@'); - } - - /// - /// Checks if the message contains a mention of the specified user or bot. - /// This filter requires a to be applied first - /// to identify mention entities in the message. - /// - /// The filter execution context containing the message and completed filters. - /// True if the message contains the specified mention; otherwise, false. - /// Thrown when the bot username is null and no specific mention is provided. - protected override bool CanPassNext(FilterExecutionContext context) - { - if (Target.Text == null) - return false; - - string userName = Mention ?? context.BotInfo.User.Username ?? throw new ArgumentNullException(nameof(context), "MentionedFilter requires BotInfo to be initialized"); - IEnumerable entities = context.CompletedFilters - .Get() - .SelectMany(ent => ent.FoundEntities) - .Where(ent => ent.Type == MessageEntityType.Mention); - - foreach (MessageEntity ent in entities) - { - string mention = Target.Text.Substring(ent.Offset + 1, ent.Length - 1); - if (mention == userName) - return true; - } + /// + /// Initializes a new instance of the class that checks for specific user mentions. + /// + /// The username to check for in the mention. + public MentionedFilter(string mention) + { + Mention = mention.TrimStart('@'); + } + /// + /// Checks if the message contains a mention of the specified user or bot. + /// This filter requires a to be applied first + /// to identify mention entities in the message. + /// + /// The filter execution context containing the message and completed filters. + /// True if the message contains the specified mention; otherwise, false. + /// Thrown when the bot username is null and no specific mention is provided. + protected override bool CanPassNext(FilterExecutionContext context) + { + if (Target.Text == null) return false; + + string userName = Mention ?? context.BotInfo.User.Username ?? throw new ArgumentNullException(nameof(context), "MentionedFilter requires BotInfo to be initialized"); + IEnumerable entities = context.CompletedFilters + .Get() + .SelectMany(ent => ent.FoundEntities) + .Where(ent => ent.Type == MessageEntityType.Mention); + + foreach (MessageEntity ent in entities) + { + string mention = Target.Text.Substring(ent.Offset + 1, ent.Length - 1); + if (mention == userName) + return true; } + + return false; } } diff --git a/src/Telegrator/Filters/MessageChatFilters.cs b/src/Telegrator/Filters/MessageChatFilters.cs index 9bb8d81..7ad03be 100644 --- a/src/Telegrator/Filters/MessageChatFilters.cs +++ b/src/Telegrator/Filters/MessageChatFilters.cs @@ -3,218 +3,217 @@ using Telegram.Bot.Types.Enums; using Telegram.Bot.Types.ReplyMarkups; using Telegrator.Core.Filters; -namespace Telegrator.Filters +namespace Telegrator.Filters; + +/// +/// Base class for filters that operate on the chat of the message being processed. +/// +public abstract class MessageChatFilter : MessageFilterBase { /// - /// Base class for filters that operate on the chat of the message being processed. + /// Gets the chat of the message being processed. /// - public abstract class MessageChatFilter : MessageFilterBase - { - /// - /// Gets the chat of the message being processed. - /// - public Chat Chat { get; private set; } = null!; + public Chat Chat { get; private set; } = null!; - /// - protected override bool CanPassNext(FilterExecutionContext context) + /// + protected override bool CanPassNext(FilterExecutionContext context) + { + Chat = Target.Chat; + return CanPassNext(context.CreateChild(Chat)); + } + + /// + /// Determines whether the filter passes for the given chat context. + /// + /// The filter execution context for the chat. + /// True if the filter passes; otherwise, false. + protected abstract bool CanPassNext(FilterExecutionContext context); +} + +/// +/// Filters messages whose chat is a forum. +/// +public class MessageChatIsForumFilter : MessageChatFilter +{ + /// + protected override bool CanPassNext(FilterExecutionContext _) + => Chat.IsForum; +} + +/// +/// Filters messages whose chat ID matches the specified value. +/// +public class MessageChatIdFilter(long id) : MessageChatFilter +{ + private readonly long Id = id; + + /// + protected override bool CanPassNext(FilterExecutionContext _) + => Chat.Id == Id; +} + +/// +/// Filters messages whose chat type matches the specified value. +/// +public class MessageChatTypeFilter : MessageChatFilter +{ + private readonly ChatType? Type; + private readonly ChatTypeFlags? Flags; + + /// + /// Initialize new instance of + /// + /// + public MessageChatTypeFilter(ChatType type) + => Type = type; + + /// + /// Initialize new instance of with + /// + /// + public MessageChatTypeFilter(ChatTypeFlags type) + => Flags = type; + + /// + protected override bool CanPassNext(FilterExecutionContext _) + { + if (Type.HasValue) + return Chat.Type == Type.Value; + + if (Flags != null) { - Chat = Target.Chat; - return CanPassNext(context.CreateChild(Chat)); + ChatTypeFlags? asFlag = ToFlag(Chat.Type); + return asFlag.HasValue && Flags.Value.HasFlag(asFlag.Value); } - /// - /// Determines whether the filter passes for the given chat context. - /// - /// The filter execution context for the chat. - /// True if the filter passes; otherwise, false. - protected abstract bool CanPassNext(FilterExecutionContext context); + return false; } - /// - /// Filters messages whose chat is a forum. - /// - public class MessageChatIsForumFilter : MessageChatFilter + private static ChatTypeFlags? ToFlag(ChatType type) => type switch { - /// - protected override bool CanPassNext(FilterExecutionContext _) - => Chat.IsForum; - } + ChatType.Channel => ChatTypeFlags.Channel, + ChatType.Group => ChatTypeFlags.Group, + ChatType.Supergroup => ChatTypeFlags.Supergroup, + ChatType.Sender => ChatTypeFlags.Sender, + ChatType.Private => ChatTypeFlags.Private, + _ => null + }; +} + +/// +/// Filters messages whose chat title matches the specified value. +/// +public class MessageChatTitleFilter : MessageChatFilter +{ + private readonly string? Title; + private readonly StringComparison Comparison = StringComparison.InvariantCulture; /// - /// Filters messages whose chat ID matches the specified value. + /// Initializes a new instance of the class. /// - public class MessageChatIdFilter(long id) : MessageChatFilter - { - private readonly long Id = id; - - /// - protected override bool CanPassNext(FilterExecutionContext _) - => Chat.Id == Id; - } + /// The chat title to match. + public MessageChatTitleFilter(string? title) => Title = title; /// - /// Filters messages whose chat type matches the specified value. + /// Initializes a new instance of the class with a specific string comparison. /// - public class MessageChatTypeFilter : MessageChatFilter + /// The chat title to match. + /// The string comparison to use. + public MessageChatTitleFilter(string? title, StringComparison comparison) + : this(title) => Comparison = comparison; + + /// + protected override bool CanPassNext(FilterExecutionContext _) { - private readonly ChatType? Type; - private readonly ChatTypeFlags? Flags; - - /// - /// Initialize new instance of - /// - /// - public MessageChatTypeFilter(ChatType type) - => Type = type; - - /// - /// Initialize new instance of with - /// - /// - public MessageChatTypeFilter(ChatTypeFlags type) - => Flags = type; - - /// - protected override bool CanPassNext(FilterExecutionContext _) - { - if (Type.HasValue) - return Chat.Type == Type.Value; - - if (Flags != null) - { - ChatTypeFlags? asFlag = ToFlag(Chat.Type); - return asFlag.HasValue && Flags.Value.HasFlag(asFlag.Value); - } - + if (Chat.Title == null) return false; - } - private static ChatTypeFlags? ToFlag(ChatType type) => type switch - { - ChatType.Channel => ChatTypeFlags.Channel, - ChatType.Group => ChatTypeFlags.Group, - ChatType.Supergroup => ChatTypeFlags.Supergroup, - ChatType.Sender => ChatTypeFlags.Sender, - ChatType.Private => ChatTypeFlags.Private, - _ => null - }; - } - - /// - /// Filters messages whose chat title matches the specified value. - /// - public class MessageChatTitleFilter : MessageChatFilter - { - private readonly string? Title; - private readonly StringComparison Comparison = StringComparison.InvariantCulture; - - /// - /// Initializes a new instance of the class. - /// - /// The chat title to match. - public MessageChatTitleFilter(string? title) => Title = title; - - /// - /// Initializes a new instance of the class with a specific string comparison. - /// - /// The chat title to match. - /// The string comparison to use. - public MessageChatTitleFilter(string? title, StringComparison comparison) - : this(title) => Comparison = comparison; - - /// - protected override bool CanPassNext(FilterExecutionContext _) - { - if (Chat.Title == null) - return false; - - return Chat.Title.Equals(Title, Comparison); - } - } - - /// - /// Filters messages whose chat username matches the specified value. - /// - public class MessageChatUsernameFilter : MessageChatFilter - { - private readonly string? UserName; - private readonly StringComparison Comparison = StringComparison.InvariantCulture; - - /// - /// Initializes a new instance of the class. - /// - /// The chat username to match. - public MessageChatUsernameFilter(string? userName) => UserName = userName; - - /// - /// Initializes a new instance of the class with a specific string comparison. - /// - /// The chat username to match. - /// The string comparison to use. - public MessageChatUsernameFilter(string? userName, StringComparison comparison) - : this(userName) => Comparison = comparison; - - /// - protected override bool CanPassNext(FilterExecutionContext _) - { - if (Chat.Username == null) - return false; - - return Chat.Username.Equals(UserName, Comparison); - } - } - - /// - /// Filters messages whose chat first and/or last name matches the specified values. - /// - public class MessageChatNameFilter : MessageChatFilter - { - private readonly string? FirstName; - private readonly string? LastName; - private readonly StringComparison Comparison = StringComparison.InvariantCulture; - - /// - /// Initializes a new instance of the class. - /// - /// The chat first name to match. - /// The chat last name to match. - public MessageChatNameFilter(string? firstName, string? lastName) - { - FirstName = firstName; - LastName = lastName; - } - - /// - /// Initializes a new instance of the class with a specific string comparison. - /// - /// The chat first name to match. - /// The chat last name to match. - /// The string comparison to use. - public MessageChatNameFilter(string? firstName, string? lastName, StringComparison comparison) - : this(firstName, lastName) => Comparison = comparison; - - /// - protected override bool CanPassNext(FilterExecutionContext _) - { - if (LastName != null) - { - if (Chat.LastName == null) - return false; - - if (Chat.LastName.Equals(LastName, Comparison)) - return false; - } - - if (FirstName != null) - { - if (Chat.FirstName == null) - return false; - - if (Chat.FirstName.Equals(FirstName, Comparison)) - return false; - } - - return true; - } + return Chat.Title.Equals(Title, Comparison); + } +} + +/// +/// Filters messages whose chat username matches the specified value. +/// +public class MessageChatUsernameFilter : MessageChatFilter +{ + private readonly string? UserName; + private readonly StringComparison Comparison = StringComparison.InvariantCulture; + + /// + /// Initializes a new instance of the class. + /// + /// The chat username to match. + public MessageChatUsernameFilter(string? userName) => UserName = userName; + + /// + /// Initializes a new instance of the class with a specific string comparison. + /// + /// The chat username to match. + /// The string comparison to use. + public MessageChatUsernameFilter(string? userName, StringComparison comparison) + : this(userName) => Comparison = comparison; + + /// + protected override bool CanPassNext(FilterExecutionContext _) + { + if (Chat.Username == null) + return false; + + return Chat.Username.Equals(UserName, Comparison); + } +} + +/// +/// Filters messages whose chat first and/or last name matches the specified values. +/// +public class MessageChatNameFilter : MessageChatFilter +{ + private readonly string? FirstName; + private readonly string? LastName; + private readonly StringComparison Comparison = StringComparison.InvariantCulture; + + /// + /// Initializes a new instance of the class. + /// + /// The chat first name to match. + /// The chat last name to match. + public MessageChatNameFilter(string? firstName, string? lastName) + { + FirstName = firstName; + LastName = lastName; + } + + /// + /// Initializes a new instance of the class with a specific string comparison. + /// + /// The chat first name to match. + /// The chat last name to match. + /// The string comparison to use. + public MessageChatNameFilter(string? firstName, string? lastName, StringComparison comparison) + : this(firstName, lastName) => Comparison = comparison; + + /// + protected override bool CanPassNext(FilterExecutionContext _) + { + if (LastName != null) + { + if (Chat.LastName == null) + return false; + + if (Chat.LastName.Equals(LastName, Comparison)) + return false; + } + + if (FirstName != null) + { + if (Chat.FirstName == null) + return false; + + if (Chat.FirstName.Equals(FirstName, Comparison)) + return false; + } + + return true; } } diff --git a/src/Telegrator/Filters/MessageFilters.cs b/src/Telegrator/Filters/MessageFilters.cs index 3cb06c8..9a7e7c9 100644 --- a/src/Telegrator/Filters/MessageFilters.cs +++ b/src/Telegrator/Filters/MessageFilters.cs @@ -3,273 +3,272 @@ using Telegram.Bot.Types; using Telegram.Bot.Types.Enums; using Telegrator.Core.Filters; -namespace Telegrator.Filters +namespace Telegrator.Filters; + +/// +/// Base abstract class for all filter of updates +/// +public abstract class MessageFilterBase : Filter { /// - /// Base abstract class for all filter of updates + /// Target message for filterring /// - public abstract class MessageFilterBase : Filter + protected Message Target { get; private set; } = null!; + + /// + protected virtual bool CanPassBase(FilterExecutionContext context) { - /// - /// Target message for filterring - /// - protected Message Target { get; private set; } = null!; + FromReplyChainFilter? repliedFilter = context.CompletedFilters.Get().SingleOrDefault(); + Target = repliedFilter?.Reply ?? context.Input; - /// - protected virtual bool CanPassBase(FilterExecutionContext context) - { - FromReplyChainFilter? repliedFilter = context.CompletedFilters.Get().SingleOrDefault(); - Target = repliedFilter?.Reply ?? context.Input; + if (Target is not { Id: > 0 }) + return false; - if (Target is not { Id: > 0 }) - return false; + return true; + } - return true; - } + /// + public override bool CanPass(FilterExecutionContext context) + { + if (!CanPassBase(context)) + return false; - /// - public override bool CanPass(FilterExecutionContext context) - { - if (!CanPassBase(context)) - return false; - - return CanPassNext(context); - } - - /// - /// Determines whether the filter can pass for the given context. - /// - /// - /// - protected abstract bool CanPassNext(FilterExecutionContext context); + return CanPassNext(context); } /// - /// Filters messages by their . + /// Determines whether the filter can pass for the given context. /// - public class MessageTypeFilter : MessageFilterBase + /// + /// + protected abstract bool CanPassNext(FilterExecutionContext context); +} + +/// +/// Filters messages by their . +/// +public class MessageTypeFilter : MessageFilterBase +{ + private readonly MessageType type; + + /// + /// Initializes a new instance of the class. + /// + /// The message type to filter by. + public MessageTypeFilter(MessageType type) => this.type = type; + + /// + protected override bool CanPassNext(FilterExecutionContext context) + => Target.Type == type; +} + +/// +/// Filters messages that are automatic forwards. +/// +public class IsAutomaticFormwardMessageFilter : MessageFilterBase +{ + /// + protected override bool CanPassNext(FilterExecutionContext context) + => Target.IsAutomaticForward; +} + +/// +/// Filters messages that are sent from offline. +/// +public class IsFromOfflineMessageFilter : MessageFilterBase +{ + /// + protected override bool CanPassNext(FilterExecutionContext context) + => Target.IsFromOffline; +} + +/// +/// Filters service messages (e.g., chat events). +/// +public class IsServiceMessageMessageFilter : MessageFilterBase +{ + /// + protected override bool CanPassNext(FilterExecutionContext context) + => Target.IsServiceMessage; +} + +/// +/// Filters messages that are topic messages. +/// +public class IsTopicMessageMessageFilter : MessageFilterBase +{ + /// + protected override bool CanPassNext(FilterExecutionContext context) + => Target.IsTopicMessage; +} + +/// +/// Filters messages by dice throw value and optionally by dice type. +/// +public class DiceThrowedFilter : MessageFilterBase +{ + private readonly DiceType Dice; + private readonly int Value; + + /// + /// Initializes a new instance of the class for a specific value. + /// + /// The dice value to filter by. + public DiceThrowedFilter(int value) { - private readonly MessageType type; - - /// - /// Initializes a new instance of the class. - /// - /// The message type to filter by. - public MessageTypeFilter(MessageType type) => this.type = type; - - /// - protected override bool CanPassNext(FilterExecutionContext context) - => Target.Type == type; + Value = value; } /// - /// Filters messages that are automatic forwards. + /// Initializes a new instance of the class for a specific dice type and value. /// - public class IsAutomaticFormwardMessageFilter : MessageFilterBase + /// The dice type to filter by. + /// The dice value to filter by. + public DiceThrowedFilter(DiceType diceType, int value) : this(value) => Dice = diceType; + + /// + protected override bool CanPassNext(FilterExecutionContext context) { - /// - protected override bool CanPassNext(FilterExecutionContext context) - => Target.IsAutomaticForward; + if (Target.Dice == null) + return false; + + if (Target.Dice.Emoji != GetEmojyForDiceType(Dice)) + return false; + + return Target.Dice.Value == Value; + } + + private static string? GetEmojyForDiceType(DiceType? diceType) => diceType switch + { + DiceType.Dice => "🎲", + DiceType.Darts => "🎯", + DiceType.Bowling => "🎳", + DiceType.Basketball => "🏀", + DiceType.Football => "⚽", + DiceType.Casino => "🎰", + _ => null + }; +} + +/// +/// Filters messages by matching their text with a regular expression. +/// +public class MessageRegexFilter : RegexFilterBase +{ + /// + /// Initializes a new instance of the class with a pattern and options. + /// + /// The regex pattern. + /// The regex options. + public MessageRegexFilter(string pattern, RegexOptions regexOptions = default) + : base(msg => msg.Text, pattern, regexOptions) { } + + /// + /// Initializes a new instance of the class with a regex object. + /// + /// The regex object. + public MessageRegexFilter(Regex regex) + : base(msg => msg.Text, regex) { } +} + +/// +/// Filters messages that contain a specific entity type, content, offset, or length. +/// +public class MessageHasEntityFilter : MessageFilterBase +{ + private readonly StringComparison _stringComparison = StringComparison.CurrentCulture; + private readonly MessageEntityType? EntityType; + private readonly string? Content; + private readonly int? Offset; + private readonly int? Length; + + /// + /// Gets the entities found in the message that match the filter. + /// + public MessageEntity[] FoundEntities { get; set; } = null!; + + /// + /// Initializes a new instance of the class for a specific entity type. + /// + /// The entity type to filter by. + public MessageHasEntityFilter(MessageEntityType type) + { + EntityType = type; } /// - /// Filters messages that are sent from offline. + /// Initializes a new instance of the class for a specific entity type, offset, and length. /// - public class IsFromOfflineMessageFilter : MessageFilterBase + /// The entity type to filter by. + /// The offset to filter by. + /// The length to filter by. + public MessageHasEntityFilter(MessageEntityType type, int? offset, int? length) { - /// - protected override bool CanPassNext(FilterExecutionContext context) - => Target.IsFromOffline; + EntityType = type; + Offset = offset; + Length = length; } /// - /// Filters service messages (e.g., chat events). + /// Initializes a new instance of the class for a specific entity type and content. /// - public class IsServiceMessageMessageFilter : MessageFilterBase + /// The entity type to filter by. + /// The content to filter by. + /// The string comparison to use. + public MessageHasEntityFilter(MessageEntityType type, string? content, StringComparison stringComparison = StringComparison.CurrentCulture) { - /// - protected override bool CanPassNext(FilterExecutionContext context) - => Target.IsServiceMessage; + EntityType = type; + Content = content; + _stringComparison = stringComparison; } /// - /// Filters messages that are topic messages. + /// Initializes a new instance of the class for a specific entity type, offset, length, and content. /// - public class IsTopicMessageMessageFilter : MessageFilterBase + /// The entity type to filter by. + /// The offset to filter by. + /// The length to filter by. + /// The content to filter by. + /// The string comparison to use. + public MessageHasEntityFilter(MessageEntityType type, int? offset, int? length, string? content, StringComparison stringComparison = StringComparison.CurrentCulture) { - /// - protected override bool CanPassNext(FilterExecutionContext context) - => Target.IsTopicMessage; + EntityType = type; + Offset = offset; + Length = length; + Content = content; + _stringComparison = stringComparison; } - /// - /// Filters messages by dice throw value and optionally by dice type. - /// - public class DiceThrowedFilter : MessageFilterBase + /// + protected override bool CanPassNext(FilterExecutionContext context) { - private readonly DiceType Dice; - private readonly int Value; + if (context.Input is not { Entities.Length: > 0 }) + return false; - /// - /// Initializes a new instance of the class for a specific value. - /// - /// The dice value to filter by. - public DiceThrowedFilter(int value) + FoundEntities = Target.Entities.Where(entity => FilterEntity(Target.Text, entity)).ToArray(); + return FoundEntities.Length != 0; + } + + private bool FilterEntity(string? text, MessageEntity entity) + { + if (EntityType != null && entity.Type != EntityType) + return false; + + if (Offset != null && entity.Offset != Offset) + return false; + + if (Length != null && entity.Length != Length) + return false; + + if (Content != null) { - Value = value; - } - - /// - /// Initializes a new instance of the class for a specific dice type and value. - /// - /// The dice type to filter by. - /// The dice value to filter by. - public DiceThrowedFilter(DiceType diceType, int value) : this(value) => Dice = diceType; - - /// - protected override bool CanPassNext(FilterExecutionContext context) - { - if (Target.Dice == null) + if (text is not { Length: > 0 }) return false; - if (Target.Dice.Emoji != GetEmojyForDiceType(Dice)) + if (!text.Substring(entity.Offset, entity.Length).Equals(Content, _stringComparison)) return false; - - return Target.Dice.Value == Value; } - private static string? GetEmojyForDiceType(DiceType? diceType) => diceType switch - { - DiceType.Dice => "🎲", - DiceType.Darts => "🎯", - DiceType.Bowling => "🎳", - DiceType.Basketball => "🏀", - DiceType.Football => "⚽", - DiceType.Casino => "🎰", - _ => null - }; - } - - /// - /// Filters messages by matching their text with a regular expression. - /// - public class MessageRegexFilter : RegexFilterBase - { - /// - /// Initializes a new instance of the class with a pattern and options. - /// - /// The regex pattern. - /// The regex options. - public MessageRegexFilter(string pattern, RegexOptions regexOptions = default) - : base(msg => msg.Text, pattern, regexOptions) { } - - /// - /// Initializes a new instance of the class with a regex object. - /// - /// The regex object. - public MessageRegexFilter(Regex regex) - : base(msg => msg.Text, regex) { } - } - - /// - /// Filters messages that contain a specific entity type, content, offset, or length. - /// - public class MessageHasEntityFilter : MessageFilterBase - { - private readonly StringComparison _stringComparison = StringComparison.CurrentCulture; - private readonly MessageEntityType? EntityType; - private readonly string? Content; - private readonly int? Offset; - private readonly int? Length; - - /// - /// Gets the entities found in the message that match the filter. - /// - public MessageEntity[] FoundEntities { get; set; } = null!; - - /// - /// Initializes a new instance of the class for a specific entity type. - /// - /// The entity type to filter by. - public MessageHasEntityFilter(MessageEntityType type) - { - EntityType = type; - } - - /// - /// Initializes a new instance of the class for a specific entity type, offset, and length. - /// - /// The entity type to filter by. - /// The offset to filter by. - /// The length to filter by. - public MessageHasEntityFilter(MessageEntityType type, int? offset, int? length) - { - EntityType = type; - Offset = offset; - Length = length; - } - - /// - /// Initializes a new instance of the class for a specific entity type and content. - /// - /// The entity type to filter by. - /// The content to filter by. - /// The string comparison to use. - public MessageHasEntityFilter(MessageEntityType type, string? content, StringComparison stringComparison = StringComparison.CurrentCulture) - { - EntityType = type; - Content = content; - _stringComparison = stringComparison; - } - - /// - /// Initializes a new instance of the class for a specific entity type, offset, length, and content. - /// - /// The entity type to filter by. - /// The offset to filter by. - /// The length to filter by. - /// The content to filter by. - /// The string comparison to use. - public MessageHasEntityFilter(MessageEntityType type, int? offset, int? length, string? content, StringComparison stringComparison = StringComparison.CurrentCulture) - { - EntityType = type; - Offset = offset; - Length = length; - Content = content; - _stringComparison = stringComparison; - } - - /// - protected override bool CanPassNext(FilterExecutionContext context) - { - if (context.Input is not { Entities.Length: > 0 }) - return false; - - FoundEntities = Target.Entities.Where(entity => FilterEntity(Target.Text, entity)).ToArray(); - return FoundEntities.Length != 0; - } - - private bool FilterEntity(string? text, MessageEntity entity) - { - if (EntityType != null && entity.Type != EntityType) - return false; - - if (Offset != null && entity.Offset != Offset) - return false; - - if (Length != null && entity.Length != Length) - return false; - - if (Content != null) - { - if (text is not { Length: > 0 }) - return false; - - if (!text.Substring(entity.Offset, entity.Length).Equals(Content, _stringComparison)) - return false; - } - - return true; - } + return true; } } diff --git a/src/Telegrator/Filters/MessageRepliedFilters.cs b/src/Telegrator/Filters/MessageRepliedFilters.cs index b24b6bb..1e41a39 100644 --- a/src/Telegrator/Filters/MessageRepliedFilters.cs +++ b/src/Telegrator/Filters/MessageRepliedFilters.cs @@ -1,102 +1,101 @@ using Telegram.Bot.Types; using Telegrator.Core.Filters; -namespace Telegrator.Filters +namespace Telegrator.Filters; + +/// +/// Filter that checks if message has appropriate reply chain. +/// DOES NOT SHiFT MESSAGE FILTERS TARGET +/// +/// The depth of reply chain to traverse (default: 1). +public class MessageHasReplyFilter(int replyDepth = 1) : Filter { /// - /// Filter that checks if message has appropriate reply chain. - /// DOES NOT SHiFT MESSAGE FILTERS TARGET + /// Gets the replied message at the specified depth in the reply chain. /// - /// The depth of reply chain to traverse (default: 1). - public class MessageHasReplyFilter(int replyDepth = 1) : Filter - { - /// - /// Gets the replied message at the specified depth in the reply chain. - /// - public Message Reply { get; private set; } = null!; - - /// - /// Gets the depth of reply chain traversal. - /// - public int ReplyDepth { get; private set; } = replyDepth; - - /// - /// Determines if the message can pass through the filter by first validating - /// the reply chain and then applying specific filter logic. - /// - /// The filter execution context containing the message. - /// True if the message passes both reply validation and specific filter criteria; otherwise, false. - public override bool CanPass(FilterExecutionContext context) - { - Message reply = context.Input; - for (int i = ReplyDepth; i > 0; i--) - { - if (reply.ReplyToMessage is not { Id: > 0 } replyMessage) - return false; - - reply = replyMessage; - } - - Reply = reply; - return true; - } - } + public Message Reply { get; private set; } = null!; /// - /// 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. + /// Gets the depth of reply chain traversal. /// - /// - public class FromReplyChainFilter(int replyDepth = 1) : Filter - { - /// - /// Gets the replied message at the specified depth in the reply chain. - /// - public Message Reply { get; private set; } = null!; - - /// - /// Gets the depth of reply chain traversal. - /// - public int ReplyDepth { get; private set; } = replyDepth; - - /// - /// Determines if the message can pass through the filter by first validating - /// the reply chain and then applying specific filter logic. - /// - /// The filter execution context containing the message. - /// True if the message passes both reply validation and specific filter criteria; otherwise, false. - public override bool CanPass(FilterExecutionContext context) - { - Message reply = context.Input; - for (int i = ReplyDepth; i > 0; i--) - { - if (reply.ReplyToMessage is not { Id: > 0 } replyMessage) - return false; - - reply = replyMessage; - } - - Reply = reply; - return true; - } - } + public int ReplyDepth { get; private set; } = replyDepth; /// - /// Filter that checks if the replied message was sent by the bot itself. - /// ( ! ): REQUIRES before + /// Determines if the message can pass through the filter by first validating + /// the reply chain and then applying specific filter logic. /// - public class MeRepliedFilter : Filter + /// The filter execution context containing the message. + /// True if the message passes both reply validation and specific filter criteria; otherwise, false. + public override bool CanPass(FilterExecutionContext context) { - /// - /// Checks if the replied message was sent by the bot. - /// - /// The filter execution context containing bot information. - /// True if the replied message was sent by the bot; otherwise, false. - public override bool CanPass(FilterExecutionContext context) + Message reply = context.Input; + for (int i = ReplyDepth; i > 0; i--) { - MessageHasReplyFilter repliedFilter = context.CompletedFilters.Get(0); - return context.BotInfo.User == repliedFilter.Reply.From; + if (reply.ReplyToMessage is not { Id: > 0 } replyMessage) + return false; + + reply = replyMessage; } + + Reply = reply; + return true; + } +} + +/// +/// Helper filter class for filters that operate on replied messages. +/// Provides functionality to traverse reply chains and access replied message content +/// and shifts any next message filter to filter the content of found reply. +/// +/// +public class FromReplyChainFilter(int replyDepth = 1) : Filter +{ + /// + /// Gets the replied message at the specified depth in the reply chain. + /// + public Message Reply { get; private set; } = null!; + + /// + /// Gets the depth of reply chain traversal. + /// + public int ReplyDepth { get; private set; } = replyDepth; + + /// + /// Determines if the message can pass through the filter by first validating + /// the reply chain and then applying specific filter logic. + /// + /// The filter execution context containing the message. + /// True if the message passes both reply validation and specific filter criteria; otherwise, false. + public override bool CanPass(FilterExecutionContext context) + { + Message reply = context.Input; + for (int i = ReplyDepth; i > 0; i--) + { + if (reply.ReplyToMessage is not { Id: > 0 } replyMessage) + return false; + + reply = replyMessage; + } + + Reply = reply; + return true; + } +} + +/// +/// Filter that checks if the replied message was sent by the bot itself. +/// ( ! ): REQUIRES before +/// +public class MeRepliedFilter : Filter +{ + /// + /// Checks if the replied message was sent by the bot. + /// + /// The filter execution context containing bot information. + /// True if the replied message was sent by the bot; otherwise, false. + public override bool CanPass(FilterExecutionContext context) + { + MessageHasReplyFilter repliedFilter = context.CompletedFilters.Get(0); + return context.BotInfo.User == repliedFilter.Reply.From; } } diff --git a/src/Telegrator/Filters/MessageSenderFilters.cs b/src/Telegrator/Filters/MessageSenderFilters.cs index 1380fed..fca6636 100644 --- a/src/Telegrator/Filters/MessageSenderFilters.cs +++ b/src/Telegrator/Filters/MessageSenderFilters.cs @@ -1,182 +1,181 @@ using Telegram.Bot.Types; using Telegrator.Core.Filters; -namespace Telegrator.Filters +namespace Telegrator.Filters; + +/// +/// Abstract base class for filters that operate on message senders. +/// Provides functionality to access and validate the user who sent the message. +/// +public abstract class MessageSenderFilter : MessageFilterBase { /// - /// Abstract base class for filters that operate on message senders. - /// Provides functionality to access and validate the user who sent the message. + /// Gets the user who sent the message. /// - public abstract class MessageSenderFilter : MessageFilterBase - { - /// - /// Gets the user who sent the message. - /// - public User User { get; private set; } = null!; - - /// - /// Determines if the message can pass through the filter by validating - /// that the message has a valid sender. - /// - /// The filter execution context containing the message. - /// True if the message has a valid sender; otherwise, false. - public override bool CanPass(FilterExecutionContext context) - { - if (!base.CanPassBase(context)) - return false; - - User = Target.From!; - if (User is not { Id: > 0 }) - return false; - - return CanPassNext(context); - } - } + public User User { get; private set; } = null!; /// - /// Filter that checks if the message sender has a specific username. + /// Determines if the message can pass through the filter by validating + /// that the message has a valid sender. /// - /// The username to check for. - public class FromUsernameFilter(string username) : MessageSenderFilter + /// The filter execution context containing the message. + /// True if the message has a valid sender; otherwise, false. + public override bool CanPass(FilterExecutionContext context) { - /// - /// The username to check for. - /// - private readonly string _username = username; - - /// - /// The string comparison type to use for username matching. - /// - private readonly StringComparison _comparison = StringComparison.InvariantCulture; + if (!base.CanPassBase(context)) + return false; - /// - /// Initializes a new instance of the class with custom string comparison. - /// - /// The username to check for. - /// The string comparison type to use. - public FromUsernameFilter(string username, StringComparison comparison) - : this(username) => _comparison = comparison; + User = Target.From!; + if (User is not { Id: > 0 }) + return false; - /// - /// Checks if the message sender has the specified username. - /// - /// The filter execution context (unused). - /// True if the sender has the specified username; otherwise, false. - protected override bool CanPassNext(FilterExecutionContext context) - => User.Username != null && User.Username.Equals(_username, _comparison); - } - - /// - /// Filter that checks if the message sender has specific first and/or last name. - /// - /// The first name to check for. - /// The last name to check for (optional). - /// The string comparison type to use. - public class FromUserFilter(string firstName, string? lastName, StringComparison comparison) : MessageSenderFilter - { - /// - /// The first name to check for. - /// - private readonly string _firstName = firstName; - - /// - /// The last name to check for (optional). - /// - private readonly string? _lastName = lastName; - - /// - /// The string comparison type to use for name matching. - /// - private readonly StringComparison _comparison = comparison; - - /// - /// Initializes a new instance of the class with first and last name. - /// - /// The first name to check for. - /// The last name to check for. - public FromUserFilter(string firstName, string lastName) - : this(firstName, lastName, StringComparison.InvariantCulture) { } - - /// - /// Initializes a new instance of the class with first name only. - /// - /// The first name to check for. - public FromUserFilter(string firstName) - : this(firstName, null, StringComparison.InvariantCulture) { } - - /// - /// Initializes a new instance of the class with first name and custom comparison. - /// - /// The first name to check for. - /// The string comparison type to use. - public FromUserFilter(string firstName, StringComparison comparison) - : this(firstName, null, comparison) { } - - /// - /// Checks if the message sender has the specified first and/or last name. - /// - /// The filter execution context (unused). - /// True if the sender has the specified name(s); otherwise, false. - protected override bool CanPassNext(FilterExecutionContext context) - { - if (User.LastName != null) - { - if (_lastName == null) - return false; - - if (!_firstName.Equals(User.LastName, _comparison)) - return false; - } - - return User.FirstName.Equals(_firstName, _comparison); - } - } - - /// - /// Filter that checks if the message sender has a specific user ID. - /// - /// The user ID to check for. - public class FromUserIdFilter(long userId) : MessageSenderFilter - { - /// - /// The user ID to check for. - /// - private readonly long _userId = userId; - - /// - /// Checks if the message sender has the specified user ID. - /// - /// The filter execution context (unused). - /// True if the sender has the specified user ID; otherwise, false. - protected override bool CanPassNext(FilterExecutionContext _) - => User.Id == _userId; - } - - /// - /// Filter that checks if the message was sent by a bot. - /// - public class FromBotFilter() : MessageSenderFilter - { - /// - /// Checks if the message was sent by a bot. - /// - /// The filter execution context (unused). - /// True if the message was sent by a bot; otherwise, false. - protected override bool CanPassNext(FilterExecutionContext _) - => User.IsBot; - } - - /// - /// Filter that checks if the message was sent by a premium user. - /// - public class FromPremiumUserFilter() : MessageSenderFilter - { - /// - /// Checks if the message was sent by a premium user. - /// - /// The filter execution context (unused). - /// True if the message was sent by a premium user; otherwise, false. - protected override bool CanPassNext(FilterExecutionContext _) - => User.IsPremium; + return CanPassNext(context); } } + +/// +/// Filter that checks if the message sender has a specific username. +/// +/// The username to check for. +public class FromUsernameFilter(string username) : MessageSenderFilter +{ + /// + /// The username to check for. + /// + private readonly string _username = username; + + /// + /// The string comparison type to use for username matching. + /// + private readonly StringComparison _comparison = StringComparison.InvariantCulture; + + /// + /// Initializes a new instance of the class with custom string comparison. + /// + /// The username to check for. + /// The string comparison type to use. + public FromUsernameFilter(string username, StringComparison comparison) + : this(username) => _comparison = comparison; + + /// + /// Checks if the message sender has the specified username. + /// + /// The filter execution context (unused). + /// True if the sender has the specified username; otherwise, false. + protected override bool CanPassNext(FilterExecutionContext context) + => User.Username != null && User.Username.Equals(_username, _comparison); +} + +/// +/// Filter that checks if the message sender has specific first and/or last name. +/// +/// The first name to check for. +/// The last name to check for (optional). +/// The string comparison type to use. +public class FromUserFilter(string firstName, string? lastName, StringComparison comparison) : MessageSenderFilter +{ + /// + /// The first name to check for. + /// + private readonly string _firstName = firstName; + + /// + /// The last name to check for (optional). + /// + private readonly string? _lastName = lastName; + + /// + /// The string comparison type to use for name matching. + /// + private readonly StringComparison _comparison = comparison; + + /// + /// Initializes a new instance of the class with first and last name. + /// + /// The first name to check for. + /// The last name to check for. + public FromUserFilter(string firstName, string lastName) + : this(firstName, lastName, StringComparison.InvariantCulture) { } + + /// + /// Initializes a new instance of the class with first name only. + /// + /// The first name to check for. + public FromUserFilter(string firstName) + : this(firstName, null, StringComparison.InvariantCulture) { } + + /// + /// Initializes a new instance of the class with first name and custom comparison. + /// + /// The first name to check for. + /// The string comparison type to use. + public FromUserFilter(string firstName, StringComparison comparison) + : this(firstName, null, comparison) { } + + /// + /// Checks if the message sender has the specified first and/or last name. + /// + /// The filter execution context (unused). + /// True if the sender has the specified name(s); otherwise, false. + protected override bool CanPassNext(FilterExecutionContext context) + { + if (User.LastName != null) + { + if (_lastName == null) + return false; + + if (!_firstName.Equals(User.LastName, _comparison)) + return false; + } + + return User.FirstName.Equals(_firstName, _comparison); + } +} + +/// +/// Filter that checks if the message sender has a specific user ID. +/// +/// The user ID to check for. +public class FromUserIdFilter(long userId) : MessageSenderFilter +{ + /// + /// The user ID to check for. + /// + private readonly long _userId = userId; + + /// + /// Checks if the message sender has the specified user ID. + /// + /// The filter execution context (unused). + /// True if the sender has the specified user ID; otherwise, false. + protected override bool CanPassNext(FilterExecutionContext _) + => User.Id == _userId; +} + +/// +/// Filter that checks if the message was sent by a bot. +/// +public class FromBotFilter() : MessageSenderFilter +{ + /// + /// Checks if the message was sent by a bot. + /// + /// The filter execution context (unused). + /// True if the message was sent by a bot; otherwise, false. + protected override bool CanPassNext(FilterExecutionContext _) + => User.IsBot; +} + +/// +/// Filter that checks if the message was sent by a premium user. +/// +public class FromPremiumUserFilter() : MessageSenderFilter +{ + /// + /// Checks if the message was sent by a premium user. + /// + /// The filter execution context (unused). + /// True if the message was sent by a premium user; otherwise, false. + protected override bool CanPassNext(FilterExecutionContext _) + => User.IsPremium; +} diff --git a/src/Telegrator/Filters/MessageTextFilters.cs b/src/Telegrator/Filters/MessageTextFilters.cs index 10c90da..07c0728 100644 --- a/src/Telegrator/Filters/MessageTextFilters.cs +++ b/src/Telegrator/Filters/MessageTextFilters.cs @@ -1,185 +1,183 @@ using Telegram.Bot.Types; using Telegrator.Core.Filters; -using static System.Net.Mime.MediaTypeNames; -namespace Telegrator.Filters +namespace Telegrator.Filters; + +/// +/// Abstract base class for filters that operate on message text content. +/// Provides common functionality for extracting and validating message text. +/// +public abstract class MessageTextFilter : MessageFilterBase { /// - /// Abstract base class for filters that operate on message text content. - /// Provides common functionality for extracting and validating message text. + /// Gets the current message being processed by the filter. /// - public abstract class MessageTextFilter : MessageFilterBase - { - /// - /// Gets the current message being processed by the filter. - /// - public Message Message { get; private set; } = null!; - - /// - /// Gets the extracted text content from the current message. - /// - public string Text { get; private set; } = null!; - - /// - /// Determines if the message can pass through the filter by validating the message - /// and extracting its text content for further processing. - /// - /// The filter execution context containing the message update. - /// True if the message is valid and can be processed further; otherwise, false. - public override bool CanPass(FilterExecutionContext context) - { - if (!base.CanPassBase(context)) - return false; - - if (Target is not { Id: > 0, Text.Length: > 0 }) - return false; - - Text = Target.Text; - return CanPassNext(context); - } - } + public Message Message { get; private set; } = null!; /// - /// Filter that checks if the message text starts with a specified content. + /// Gets the extracted text content from the current message. /// - /// The content to check if the message text starts with. - /// The string comparison type to use for the check. - public class TextStartsWithFilter(string content, StringComparison comparison = StringComparison.InvariantCulture) : MessageTextFilter - { - /// - /// The content to check if the message text starts with. - /// - protected readonly string Content = content; - - /// - /// The string comparison type to use for the check. - /// - protected readonly StringComparison Comparison = comparison; - - /// - /// Checks if the message text starts with the specified content using the configured comparison. - /// - /// The filter execution context (unused). - /// True if the text starts with the specified content; otherwise, false. - protected override bool CanPassNext(FilterExecutionContext _) - => Text.StartsWith(Content, Comparison); - } + public string Text { get; private set; } = null!; /// - /// Filter that checks if the message text ends with a specified content. + /// Determines if the message can pass through the filter by validating the message + /// and extracting its text content for further processing. /// - /// The content to check if the message text ends with. - /// The string comparison type to use for the check. - public class TextEndsWithFilter(string content, StringComparison comparison = StringComparison.InvariantCulture) : MessageTextFilter + /// The filter execution context containing the message update. + /// True if the message is valid and can be processed further; otherwise, false. + public override bool CanPass(FilterExecutionContext context) { - /// - /// The content to check if the message text ends with. - /// - protected readonly string Content = content; - - /// - /// The string comparison type to use for the check. - /// - protected readonly StringComparison Comparison = comparison; + if (!base.CanPassBase(context)) + return false; - /// - /// Checks if the message text ends with the specified content using the configured comparison. - /// - /// The filter execution context (unused). - /// True if the text ends with the specified content; otherwise, false. - protected override bool CanPassNext(FilterExecutionContext _) - => Text.EndsWith(Content, Comparison); - } + if (Target is not { Id: > 0, Text.Length: > 0 }) + return false; - /// - /// Filter that checks if the message text contains a specified content. - /// - /// The content to check if the message text contains. - /// The string comparison type to use for the check. - public class TextContainsFilter(string content, StringComparison comparison = StringComparison.InvariantCulture) : MessageTextFilter - { - /// - /// The content to check if the message text contains. - /// - protected readonly string Content = content; - - /// - /// The string comparison type to use for the check. - /// - protected readonly StringComparison Comparison = comparison; - - /// - /// Checks if the message text contains the specified content using the configured comparison. - /// - /// The filter execution context (unused). - /// True if the text contains the specified content; otherwise, false. - protected override bool CanPassNext(FilterExecutionContext _) - => Text.IndexOf(Content, Comparison) >= 0; - } - - /// - /// Filter that checks if the message text equals a specified content. - /// - /// The content to check if the message text equals. - /// The string comparison type to use for the check. - public class TextEqualsFilter(string content, StringComparison comparison = StringComparison.InvariantCulture) : MessageTextFilter - { - /// - /// The content to check if the message text equals. - /// - protected readonly string Content = content; - - /// - /// The string comparison type to use for the check. - /// - protected readonly StringComparison Comparison = comparison; - - /// - /// Checks if the message text equals the specified content using the configured comparison. - /// - /// The filter execution context (unused). - /// True if the text equals the specified content; otherwise, false. - protected override bool CanPassNext(FilterExecutionContext _) - => Text.Equals(Content, Comparison); - } - - /// - /// Filter that checks if the message text is not null or empty. - /// - public class TextNotNullOrEmptyFilter() : MessageTextFilter - { - /// - /// Checks if the message text is not null or empty. - /// - /// The filter execution context (unused). - /// True if the text is not null or empty; otherwise, false. - protected override bool CanPassNext(FilterExecutionContext _) - => !string.IsNullOrEmpty(Text); - } - - /// - /// Filter that checks if the message text contains a 'word'. - /// 'Word' must be a separate member of the text, and not have any alphabetic characters next to it. - /// - public class TextContainsWordFilter(string word, StringComparison comparison = StringComparison.InvariantCulture, int startIndex = 0) : MessageTextFilter - { - /// - /// The content to check if the message text equals. - /// - protected readonly string Word = word; - - /// - /// The string comparison type to use for the check. - /// - protected readonly StringComparison Comparison = comparison; - - /// - /// The search starting position. - /// - protected readonly int StartIndex = startIndex; - - /// - protected override bool CanPassNext(FilterExecutionContext context) - => Text.ContainsWord(Word, Comparison, StartIndex); + Text = Target.Text; + return CanPassNext(context); } } + +/// +/// Filter that checks if the message text starts with a specified content. +/// +/// The content to check if the message text starts with. +/// The string comparison type to use for the check. +public class TextStartsWithFilter(string content, StringComparison comparison = StringComparison.InvariantCulture) : MessageTextFilter +{ + /// + /// The content to check if the message text starts with. + /// + protected readonly string Content = content; + + /// + /// The string comparison type to use for the check. + /// + protected readonly StringComparison Comparison = comparison; + + /// + /// Checks if the message text starts with the specified content using the configured comparison. + /// + /// The filter execution context (unused). + /// True if the text starts with the specified content; otherwise, false. + protected override bool CanPassNext(FilterExecutionContext _) + => Text.StartsWith(Content, Comparison); +} + +/// +/// Filter that checks if the message text ends with a specified content. +/// +/// The content to check if the message text ends with. +/// The string comparison type to use for the check. +public class TextEndsWithFilter(string content, StringComparison comparison = StringComparison.InvariantCulture) : MessageTextFilter +{ + /// + /// The content to check if the message text ends with. + /// + protected readonly string Content = content; + + /// + /// The string comparison type to use for the check. + /// + protected readonly StringComparison Comparison = comparison; + + /// + /// Checks if the message text ends with the specified content using the configured comparison. + /// + /// The filter execution context (unused). + /// True if the text ends with the specified content; otherwise, false. + protected override bool CanPassNext(FilterExecutionContext _) + => Text.EndsWith(Content, Comparison); +} + +/// +/// Filter that checks if the message text contains a specified content. +/// +/// The content to check if the message text contains. +/// The string comparison type to use for the check. +public class TextContainsFilter(string content, StringComparison comparison = StringComparison.InvariantCulture) : MessageTextFilter +{ + /// + /// The content to check if the message text contains. + /// + protected readonly string Content = content; + + /// + /// The string comparison type to use for the check. + /// + protected readonly StringComparison Comparison = comparison; + + /// + /// Checks if the message text contains the specified content using the configured comparison. + /// + /// The filter execution context (unused). + /// True if the text contains the specified content; otherwise, false. + protected override bool CanPassNext(FilterExecutionContext _) + => Text.IndexOf(Content, Comparison) >= 0; +} + +/// +/// Filter that checks if the message text equals a specified content. +/// +/// The content to check if the message text equals. +/// The string comparison type to use for the check. +public class TextEqualsFilter(string content, StringComparison comparison = StringComparison.InvariantCulture) : MessageTextFilter +{ + /// + /// The content to check if the message text equals. + /// + protected readonly string Content = content; + + /// + /// The string comparison type to use for the check. + /// + protected readonly StringComparison Comparison = comparison; + + /// + /// Checks if the message text equals the specified content using the configured comparison. + /// + /// The filter execution context (unused). + /// True if the text equals the specified content; otherwise, false. + protected override bool CanPassNext(FilterExecutionContext _) + => Text.Equals(Content, Comparison); +} + +/// +/// Filter that checks if the message text is not null or empty. +/// +public class TextNotNullOrEmptyFilter() : MessageTextFilter +{ + /// + /// Checks if the message text is not null or empty. + /// + /// The filter execution context (unused). + /// True if the text is not null or empty; otherwise, false. + protected override bool CanPassNext(FilterExecutionContext _) + => !string.IsNullOrEmpty(Text); +} + +/// +/// Filter that checks if the message text contains a 'word'. +/// 'Word' must be a separate member of the text, and not have any alphabetic characters next to it. +/// +public class TextContainsWordFilter(string word, StringComparison comparison = StringComparison.InvariantCulture, int startIndex = 0) : MessageTextFilter +{ + /// + /// The content to check if the message text equals. + /// + protected readonly string Word = word; + + /// + /// The string comparison type to use for the check. + /// + protected readonly StringComparison Comparison = comparison; + + /// + /// The search starting position. + /// + protected readonly int StartIndex = startIndex; + + /// + protected override bool CanPassNext(FilterExecutionContext context) + => Text.ContainsWord(Word, Comparison, StartIndex); +} diff --git a/src/Telegrator/Filters/RegexFilters.cs b/src/Telegrator/Filters/RegexFilters.cs index 04aa083..690414d 100644 --- a/src/Telegrator/Filters/RegexFilters.cs +++ b/src/Telegrator/Filters/RegexFilters.cs @@ -1,58 +1,57 @@ using System.Text.RegularExpressions; using Telegrator.Core.Filters; -namespace Telegrator.Filters +namespace Telegrator.Filters; + +/// +/// Base class for filters that use regular expressions to match text in updates. +/// +/// The type of the input for the filter. +public abstract class RegexFilterBase : Filter where T : class { + private readonly Func getString; + private readonly Regex regex; + /// - /// Base class for filters that use regular expressions to match text in updates. + /// Gets the collection of matches found by the regex. /// - /// The type of the input for the filter. - public abstract class RegexFilterBase : Filter where T : class + public MatchCollection Matches { get; private set; } = null!; + + /// + /// Initializes a new instance of the class with a regex object. + /// + /// Function to extract the string to match from the input. + /// The regex object to use for matching. + protected RegexFilterBase(Func getString, Regex regex) { - private readonly Func getString; - private readonly Regex regex; + this.getString = getString; + this.regex = regex; + } - /// - /// Gets the collection of matches found by the regex. - /// - public MatchCollection Matches { get; private set; } = null!; + /// + /// Initializes a new instance of the class with a pattern and options. + /// + /// Function to extract the string to match from the input. + /// The regex pattern. + /// The regex options. + protected RegexFilterBase(Func getString, string pattern, RegexOptions regexOptions = default) + { + this.getString = getString; + regex = new Regex(pattern, regexOptions); + } - /// - /// Initializes a new instance of the class with a regex object. - /// - /// Function to extract the string to match from the input. - /// The regex object to use for matching. - protected RegexFilterBase(Func getString, Regex regex) - { - this.getString = getString; - this.regex = regex; - } + /// + /// Determines whether the regex matches the text extracted from the input. + /// + /// The filter execution context. + /// True if the regex matches; otherwise, false. + public override bool CanPass(FilterExecutionContext context) + { + string? text = getString.Invoke(context.Input); + if (string.IsNullOrEmpty(text)) + return false; - /// - /// Initializes a new instance of the class with a pattern and options. - /// - /// Function to extract the string to match from the input. - /// The regex pattern. - /// The regex options. - protected RegexFilterBase(Func getString, string pattern, RegexOptions regexOptions = default) - { - this.getString = getString; - regex = new Regex(pattern, regexOptions); - } - - /// - /// Determines whether the regex matches the text extracted from the input. - /// - /// The filter execution context. - /// True if the regex matches; otherwise, false. - public override bool CanPass(FilterExecutionContext context) - { - string? text = getString.Invoke(context.Input); - if (string.IsNullOrEmpty(text)) - return false; - - Matches = regex.Matches(text); - return Matches.Count > 0; - } + Matches = regex.Matches(text); + return Matches.Count > 0; } } diff --git a/src/Telegrator/Filters/StateKeyFilter.cs b/src/Telegrator/Filters/StateKeyFilter.cs index 3594cfd..541f562 100644 --- a/src/Telegrator/Filters/StateKeyFilter.cs +++ b/src/Telegrator/Filters/StateKeyFilter.cs @@ -2,43 +2,42 @@ using Telegrator.Core.Filters; using Telegrator.Core.States; -namespace Telegrator.Filters +namespace Telegrator.Filters; + +/// +/// Filters updates by comparing a resolved state key with a target key. +/// +/// The type of the key resolver used to get state key. +/// The type of the key used for state resolution. +public class StateKeyFilter : Filter + where TKey : IStateKeyResolver, new() + where TValue : IEquatable { + private readonly TValue? TargetKey; + /// - /// Filters updates by comparing a resolved state key with a target key. + /// Initializes a new instance of the class. /// - /// The type of the key resolver used to get state key. - /// The type of the key used for state resolution. - public class StateKeyFilter : Filter - where TKey : IStateKeyResolver, new() - where TValue : IEquatable + /// The target key to compare with. + public StateKeyFilter(TValue? targetKey) { - private readonly TValue? TargetKey; + TargetKey = targetKey; + } - /// - /// Initializes a new instance of the class. - /// - /// The target key to compare with. - public StateKeyFilter(TValue? targetKey) - { - TargetKey = targetKey; - } + /// + public override bool CanPass(FilterExecutionContext context) + { + string? key = new TKey().ResolveKey(context.Input); + if (key is null) + return TargetKey is null; - /// - public override bool CanPass(FilterExecutionContext context) - { - string? key = new TKey().ResolveKey(context.Input); - if (key is null) - return TargetKey is null; + TValue? value = context.UpdateRouter.StateStorage.GetAsync(key).Result; + if (value is null) + return TargetKey is null; - TValue? value = context.UpdateRouter.StateStorage.GetAsync(key).Result; - if (value is null) - return TargetKey is null; + if (TargetKey is null) + return false; - if (TargetKey is null) - return false; - - return TargetKey.Equals(value); - } + return TargetKey.Equals(value); } } diff --git a/src/Telegrator/Handlers/AnyUpdateHandler.cs b/src/Telegrator/Handlers/AnyUpdateHandler.cs index 1c39182..22119bb 100644 --- a/src/Telegrator/Handlers/AnyUpdateHandler.cs +++ b/src/Telegrator/Handlers/AnyUpdateHandler.cs @@ -4,29 +4,28 @@ using Telegrator.Attributes; using Telegrator.Core.Filters; using Telegrator.Core.Handlers; -namespace Telegrator.Handlers +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. +/// +/// +public class AnyUpdateHandlerAttribute(int importance = -1) : UpdateHandlerAttribute(UpdateType.Unknown, importance) { /// - /// Attribute that marks a handler to process any type of update. - /// This handler will be triggered for all incoming updates regardless of their type. + /// Always returns true, allowing any update to pass through this filter. /// - /// - public class AnyUpdateHandlerAttribute(int importance = -1) : UpdateHandlerAttribute(UpdateType.Unknown, importance) - { - /// - /// Always returns true, allowing any update to pass through this filter. - /// - /// The filter execution context (unused). - /// Always returns true to allow any update. - public override bool CanPass(FilterExecutionContext context) => true; - } - - /// - /// Abstract base class for handlers that can process any type of update. - /// Provides a foundation for creating handlers that respond to all incoming updates. - /// - public abstract class AnyUpdateHandler() : AbstractUpdateHandler(UpdateType.Unknown) - { - - } + /// The filter execution context (unused). + /// Always returns true to allow any update. + public override bool CanPass(FilterExecutionContext context) => true; +} + +/// +/// Abstract base class for handlers that can process any type of update. +/// Provides a foundation for creating handlers that respond to all incoming updates. +/// +public abstract class AnyUpdateHandler() : AbstractUpdateHandler(UpdateType.Unknown) +{ + } diff --git a/src/Telegrator/Handlers/Building/AwaiterHandler.cs b/src/Telegrator/Handlers/Building/AwaiterHandler.cs index 9a562eb..efc140e 100644 --- a/src/Telegrator/Handlers/Building/AwaiterHandler.cs +++ b/src/Telegrator/Handlers/Building/AwaiterHandler.cs @@ -3,67 +3,66 @@ using Telegram.Bot.Types.Enums; using Telegrator.Core.Descriptors; using Telegrator.Core.Handlers; -namespace Telegrator.Handlers.Building +namespace Telegrator.Handlers.Building; + +/// +/// Internal handler used for awaiting specific update types. +/// Provides synchronization mechanism for waiting for updates of a particular type. +/// +/// The type of update this awaiter handler waits for. +internal class AwaiterHandler(UpdateType handlingUpdateType) : UpdateHandlerBase(handlingUpdateType), IHandlerContainerFactory, IDisposable { /// - /// Internal handler used for awaiting specific update types. - /// Provides synchronization mechanism for waiting for updates of a particular type. + /// Manual reset event used for synchronization. /// - /// The type of update this awaiter handler waits for. - internal class AwaiterHandler(UpdateType handlingUpdateType) : UpdateHandlerBase(handlingUpdateType), IHandlerContainerFactory, IDisposable + private ManualResetEventSlim ResetEvent = new ManualResetEventSlim(false); + + /// + /// Gets the update that triggered this awaiter handler. + /// + public Update HandlingUpdate { get; private set; } = null!; + + /// + /// Waits for the specified update type to be received. + /// + /// The cancellation token to cancel the wait operation. + public void Wait(CancellationToken cancellationToken) { - /// - /// Manual reset event used for synchronization. - /// - private ManualResetEventSlim ResetEvent = new ManualResetEventSlim(false); - - /// - /// Gets the update that triggered this awaiter handler. - /// - public Update HandlingUpdate { get; private set; } = null!; + ResetEvent.Reset(); + ResetEvent.Wait(cancellationToken); + } - /// - /// Waits for the specified update type to be received. - /// - /// The cancellation token to cancel the wait operation. - public void Wait(CancellationToken cancellationToken) - { - ResetEvent.Reset(); - ResetEvent.Wait(cancellationToken); - } + /// + /// Creates a handler container for this awaiter handler. + /// + /// The handler information containing the update. + /// An empty handler container. + public IHandlerContainer CreateContainer(DescribedHandlerDescriptor describedHandler) + { + HandlingUpdate = describedHandler.HandlingUpdate; + return new EmptyHandlerContainer(); + } - /// - /// Creates a handler container for this awaiter handler. - /// - /// The handler information containing the update. - /// An empty handler container. - public IHandlerContainer CreateContainer(DescribedHandlerDescriptor describedHandler) - { - HandlingUpdate = describedHandler.HandlingUpdate; - return new EmptyHandlerContainer(); - } + /// + /// Executes the awaiter handler by setting the reset event. + /// + /// The handler container (unused). + /// The cancellation token (unused). + /// A completed task. + protected override Task ExecuteInternal(IHandlerContainer container, CancellationToken cancellation) + { + ResetEvent.Set(); + return Task.FromResult(Result.Ok()); + } - /// - /// Executes the awaiter handler by setting the reset event. - /// - /// The handler container (unused). - /// The cancellation token (unused). - /// A completed task. - protected override Task ExecuteInternal(IHandlerContainer container, CancellationToken cancellation) - { - ResetEvent.Set(); - return Task.FromResult(Result.Ok()); - } - - /// - protected override bool Dispose(bool disposing) - { - if (!disposing) - return true; - - ResetEvent.Dispose(); - ResetEvent = null!; + /// + protected override bool Dispose(bool disposing) + { + if (!disposing) return true; - } + + ResetEvent.Dispose(); + ResetEvent = null!; + return true; } } diff --git a/src/Telegrator/Handlers/Building/AwaiterHandlerBuilder.cs b/src/Telegrator/Handlers/Building/AwaiterHandlerBuilder.cs index 2b87b73..21a5368 100644 --- a/src/Telegrator/Handlers/Building/AwaiterHandlerBuilder.cs +++ b/src/Telegrator/Handlers/Building/AwaiterHandlerBuilder.cs @@ -7,80 +7,79 @@ using Telegrator.Core.States; using Telegrator.Filters; using Telegrator.States; -namespace Telegrator.Handlers.Building +namespace Telegrator.Handlers.Building; + +/// +/// Builder class for creating awaiter handlers that can wait for specific update types. +/// Provides fluent API for configuring filters, state keepers, and other handler properties. +/// +/// The type of update to await. +public class AwaiterHandlerBuilder : HandlerBuilderBase, IAwaiterHandlerBuilder where TUpdate : class { /// - /// Builder class for creating awaiter handlers that can wait for specific update types. - /// Provides fluent API for configuring filters, state keepers, and other handler properties. + /// The awaiting provider for managing handler registration. /// - /// The type of update to await. - public class AwaiterHandlerBuilder : HandlerBuilderBase, IAwaiterHandlerBuilder where TUpdate : class + private readonly IAwaitingProvider HandlerProvider; + + /// + /// The update that triggered the awaiter creation. + /// + private readonly Update HandlingUpdate; + + /// + /// Initializes a new instance of the class. + /// + /// The type of update to await. + /// The update that triggered the awaiter creation. + /// The awaiting provider for managing handler registration. + /// Thrown when the update type is not valid for TUpdate. + public AwaiterHandlerBuilder(UpdateType updateType, Update handlingUpdate, IAwaitingProvider handlerProvider) : base(typeof(AwaiterHandler), updateType, null) { - /// - /// The awaiting provider for managing handler registration. - /// - private readonly IAwaitingProvider HandlerProvider; + if (!updateType.IsValidUpdateObject()) + throw new Exception(); + + HandlerProvider = handlerProvider; + HandlingUpdate = handlingUpdate; + } + + /// + /// Awaits for an update of the specified type using the default sender ID resolver. + /// + /// The cancellation token to cancel the wait operation. + /// The awaited update of type TUpdate. + public async Task Await(CancellationToken cancellationToken = default) + => await Await(new SenderIdResolver(), cancellationToken); + + /// + /// Awaits for an update of the specified type using a custom state key resolver. + /// + /// The state key resolver to use for filtering updates. + /// The cancellation token to cancel the wait operation. + /// The awaited update of type TUpdate. + public async Task Await(IStateKeyResolver keyResolver, CancellationToken cancellationToken = default) + { + string? handlingKey = keyResolver.ResolveKey(HandlingUpdate); + if (handlingKey is null) + throw new InvalidOperationException("Cannot await update with resolved key as NULL"); + + Filters.Add(Filter.If(ctx => + { + string? key = keyResolver.ResolveKey(ctx.Update); + if (key is null) + return false; + + return key == handlingKey; + })); - /// - /// The update that triggered the awaiter creation. - /// - private readonly Update HandlingUpdate; - - /// - /// Initializes a new instance of the class. - /// - /// The type of update to await. - /// The update that triggered the awaiter creation. - /// The awaiting provider for managing handler registration. - /// Thrown when the update type is not valid for TUpdate. - public AwaiterHandlerBuilder(UpdateType updateType, Update handlingUpdate, IAwaitingProvider handlerProvider) : base(typeof(AwaiterHandler), updateType, null) + AwaiterHandler handlerInstance = new AwaiterHandler(UpdateType); + HandlerDescriptor descriptor = BuildImplicitDescriptor(handlerInstance); + + using (HandlerProvider.UseHandler(descriptor)) { - if (!updateType.IsValidUpdateObject()) - throw new Exception(); - - HandlerProvider = handlerProvider; - HandlingUpdate = handlingUpdate; + handlerInstance.Wait(cancellationToken); } - /// - /// Awaits for an update of the specified type using the default sender ID resolver. - /// - /// The cancellation token to cancel the wait operation. - /// The awaited update of type TUpdate. - public async Task Await(CancellationToken cancellationToken = default) - => await Await(new SenderIdResolver(), cancellationToken); - - /// - /// Awaits for an update of the specified type using a custom state key resolver. - /// - /// The state key resolver to use for filtering updates. - /// The cancellation token to cancel the wait operation. - /// The awaited update of type TUpdate. - public async Task Await(IStateKeyResolver keyResolver, CancellationToken cancellationToken = default) - { - string? handlingKey = keyResolver.ResolveKey(HandlingUpdate); - if (handlingKey is null) - throw new InvalidOperationException("Cannot await update with resolved key as NULL"); - - Filters.Add(Filter.If(ctx => - { - string? key = keyResolver.ResolveKey(ctx.Update); - if (key is null) - return false; - - return key == handlingKey; - })); - - AwaiterHandler handlerInstance = new AwaiterHandler(UpdateType); - HandlerDescriptor descriptor = BuildImplicitDescriptor(handlerInstance); - - using (HandlerProvider.UseHandler(descriptor)) - { - handlerInstance.Wait(cancellationToken); - } - - await Task.CompletedTask; - return handlerInstance.HandlingUpdate.GetActualUpdateObject(); - } + await Task.CompletedTask; + return handlerInstance.HandlingUpdate.GetActualUpdateObject(); } } diff --git a/src/Telegrator/Handlers/Building/BuildedAbstractHandler.cs b/src/Telegrator/Handlers/Building/BuildedAbstractHandler.cs index d700f66..159910e 100644 --- a/src/Telegrator/Handlers/Building/BuildedAbstractHandler.cs +++ b/src/Telegrator/Handlers/Building/BuildedAbstractHandler.cs @@ -1,37 +1,36 @@ using Telegram.Bot.Types.Enums; using Telegrator.Core.Handlers; -namespace Telegrator.Handlers.Building +namespace Telegrator.Handlers.Building; + +/// +/// Internal handler class that wraps a delegate action for execution. +/// Used for dynamically created handlers that execute custom actions. +/// +/// The type of update being handled. +internal class BuildedAbstractHandler : AbstractUpdateHandler where TUpdate : class { /// - /// Internal handler class that wraps a delegate action for execution. - /// Used for dynamically created handlers that execute custom actions. + /// The delegate action to execute when the handler is invoked. /// - /// The type of update being handled. - internal class BuildedAbstractHandler : AbstractUpdateHandler where TUpdate : class + private readonly AbstractHandlerAction HandlerAction; + + /// + /// Initializes a new instance of the class. + /// + /// The type of update this handler processes. + /// The delegate action to execute. + public BuildedAbstractHandler(UpdateType handlingUpdateType, AbstractHandlerAction handlerAction) : base(handlingUpdateType) { - /// - /// The delegate action to execute when the handler is invoked. - /// - private readonly AbstractHandlerAction HandlerAction; - - /// - /// Initializes a new instance of the class. - /// - /// The type of update this handler processes. - /// The delegate action to execute. - public BuildedAbstractHandler(UpdateType handlingUpdateType, AbstractHandlerAction handlerAction) : base(handlingUpdateType) - { - HandlerAction = handlerAction; - } - - /// - /// Executes the wrapped handler action. - /// - /// The handler container with execution context. - /// The cancellation token. - /// A task representing the asynchronous execution. - public override Task Execute(IHandlerContainer container, CancellationToken cancellation) - => HandlerAction.Invoke(container, cancellation); + HandlerAction = handlerAction; } + + /// + /// Executes the wrapped handler action. + /// + /// The handler container with execution context. + /// The cancellation token. + /// A task representing the asynchronous execution. + public override Task Execute(IHandlerContainer container, CancellationToken cancellation) + => HandlerAction.Invoke(container, cancellation); } diff --git a/src/Telegrator/Handlers/Building/HandlerBuilder.cs b/src/Telegrator/Handlers/Building/HandlerBuilder.cs index 24d6ad9..0cb1fc6 100644 --- a/src/Telegrator/Handlers/Building/HandlerBuilder.cs +++ b/src/Telegrator/Handlers/Building/HandlerBuilder.cs @@ -2,49 +2,48 @@ using Telegrator.Core; using Telegrator.Core.Handlers.Building; -namespace Telegrator.Handlers.Building +namespace Telegrator.Handlers.Building; + +/// +/// Delegate for handler execution actions that take a container and cancellation token. +/// +/// The type of update being handled. +/// The handler container with execution context. +/// The cancellation token. +/// A task representing the asynchronous execution. +public delegate Task AbstractHandlerAction(IHandlerContainer container, CancellationToken cancellation) where TUpdate : class; + +/// +/// Builder class for creating regular handlers that can process updates. +/// Provides fluent API for configuring filters, state keepers, and other handler properties. +/// +/// The type of update to handle. +public class HandlerBuilder : HandlerBuilderBase, IRegularHandlerBuilder where TUpdate : class { /// - /// Delegate for handler execution actions that take a container and cancellation token. + /// Initializes a new instance of the class. /// - /// The type of update being handled. - /// The handler container with execution context. - /// The cancellation token. - /// A task representing the asynchronous execution. - public delegate Task AbstractHandlerAction(IHandlerContainer container, CancellationToken cancellation) where TUpdate : class; + /// The type of update this handler will process. + /// The collection to register the built handler with. + /// Thrown when the update type is not valid for TUpdate. + public HandlerBuilder(UpdateType updateType, IHandlersCollection handlerCollection) : base(typeof(BuildedAbstractHandler), updateType, handlerCollection) + { + if (!updateType.IsValidUpdateObject()) + throw new ArgumentException("\"UpdateType." + updateType + "\" is not valid type for \"" + nameof(TUpdate) + "\" update object", nameof(updateType)); + } /// - /// Builder class for creating regular handlers that can process updates. - /// Provides fluent API for configuring filters, state keepers, and other handler properties. + /// Builds an abstract handler with the specified execution action. /// - /// The type of update to handle. - public class HandlerBuilder : HandlerBuilderBase, IRegularHandlerBuilder where TUpdate : class + /// The delegate action to execute when the handler is invoked. + /// Thrown when executeHandler is null. + public IHandlersCollection Build(AbstractHandlerAction executeHandler) { - /// - /// Initializes a new instance of the class. - /// - /// The type of update this handler will process. - /// The collection to register the built handler with. - /// Thrown when the update type is not valid for TUpdate. - public HandlerBuilder(UpdateType updateType, IHandlersCollection handlerCollection) : base(typeof(BuildedAbstractHandler), updateType, handlerCollection) - { - if (!updateType.IsValidUpdateObject()) - throw new ArgumentException("\"UpdateType." + updateType + "\" is not valid type for \"" + nameof(TUpdate) + "\" update object", nameof(updateType)); - } + if (executeHandler == null) + throw new ArgumentNullException(nameof(executeHandler)); - /// - /// Builds an abstract handler with the specified execution action. - /// - /// The delegate action to execute when the handler is invoked. - /// Thrown when executeHandler is null. - public IHandlersCollection Build(AbstractHandlerAction executeHandler) - { - if (executeHandler == null) - throw new ArgumentNullException(nameof(executeHandler)); - - BuildedAbstractHandler instance = new BuildedAbstractHandler(UpdateType, executeHandler); - BuildImplicitDescriptor(instance); - return HandlerCollection!; - } + BuildedAbstractHandler instance = new BuildedAbstractHandler(UpdateType, executeHandler); + BuildImplicitDescriptor(instance); + return HandlerCollection!; } } diff --git a/src/Telegrator/Handlers/Building/TypesExtensions.cs b/src/Telegrator/Handlers/Building/TypesExtensions.cs index 476a992..575b393 100644 --- a/src/Telegrator/Handlers/Building/TypesExtensions.cs +++ b/src/Telegrator/Handlers/Building/TypesExtensions.cs @@ -3,104 +3,103 @@ using Telegrator.Core.Filters; using Telegrator.Core.Handlers.Building; using Telegrator.Core.States; -namespace Telegrator.Handlers.Building +namespace Telegrator.Handlers.Building; + +/// +/// Extension methods for handler builders. +/// Provides convenient methods for creating handlers and setting state keepers. +/// +public static partial class HandlerBuilderExtensions { - /// - /// Extension methods for handler builders. - /// Provides convenient methods for creating handlers and setting state keepers. - /// - public static partial class HandlerBuilderExtensions + /// + public static TBuilder SetUpdateValidating(this TBuilder handlerBuilder, UpdateValidateAction updateValidateAction) + where TBuilder : HandlerBuilderBase { - /// - public static TBuilder SetUpdateValidating(this TBuilder handlerBuilder, UpdateValidateAction updateValidateAction) - where TBuilder : HandlerBuilderBase - { - handlerBuilder.SetUpdateValidating(updateValidateAction); - return handlerBuilder; - } + handlerBuilder.SetUpdateValidating(updateValidateAction); + return handlerBuilder; + } - /// - public static TBuilder SetConcurreny(this TBuilder handlerBuilder, int concurrency) - where TBuilder : HandlerBuilderBase - { - handlerBuilder.SetConcurreny(concurrency); - return handlerBuilder; - } + /// + public static TBuilder SetConcurreny(this TBuilder handlerBuilder, int concurrency) + where TBuilder : HandlerBuilderBase + { + handlerBuilder.SetConcurreny(concurrency); + return handlerBuilder; + } - /// - public static TBuilder SetPriority(this TBuilder handlerBuilder, int priority) - where TBuilder : HandlerBuilderBase - { - handlerBuilder.SetPriority(priority); - return handlerBuilder; - } + /// + public static TBuilder SetPriority(this TBuilder handlerBuilder, int priority) + where TBuilder : HandlerBuilderBase + { + handlerBuilder.SetPriority(priority); + return handlerBuilder; + } - /// - public static TBuilder SetIndexer(this TBuilder handlerBuilder, int concurrency, int priority) - where TBuilder : HandlerBuilderBase - { - handlerBuilder.SetIndexer(concurrency, priority); - return handlerBuilder; - } + /// + public static TBuilder SetIndexer(this TBuilder handlerBuilder, int concurrency, int priority) + where TBuilder : HandlerBuilderBase + { + handlerBuilder.SetIndexer(concurrency, priority); + return handlerBuilder; + } - /// - public static TBuilder AddFilter(this TBuilder handlerBuilder, IFilter filter) - where TBuilder : HandlerBuilderBase - { - handlerBuilder.AddFilter(filter); - return handlerBuilder; - } + /// + public static TBuilder AddFilter(this TBuilder handlerBuilder, IFilter filter) + where TBuilder : HandlerBuilderBase + { + handlerBuilder.AddFilter(filter); + return handlerBuilder; + } - /// - public static TBuilder AddFilters(this TBuilder handlerBuilder, params IFilter[] filters) - where TBuilder : HandlerBuilderBase - { - handlerBuilder.AddFilters(filters); - return handlerBuilder; - } + /// + public static TBuilder AddFilters(this TBuilder handlerBuilder, params IFilter[] filters) + where TBuilder : HandlerBuilderBase + { + handlerBuilder.AddFilters(filters); + return handlerBuilder; + } - /// - public static TBuilder SetState(this TBuilder handlerBuilder, TValue? myState) - where TBuilder : HandlerBuilderBase - where TKey : IStateKeyResolver, new() - where TValue : IEquatable - { - handlerBuilder.SetState(myState); - return handlerBuilder; - } + /// + public static TBuilder SetState(this TBuilder handlerBuilder, TValue? myState) + where TBuilder : HandlerBuilderBase + where TKey : IStateKeyResolver, new() + where TValue : IEquatable + { + handlerBuilder.SetState(myState); + return handlerBuilder; + } - /// - /// Adds a targeted filter for a specific filter target type. - /// - /// - /// The type of the filter target. - /// - /// Function to get the filter target from an update. - /// The filter to add. - /// The builder instance. - public static TBuilder AddTargetedFilter(this TBuilder handlerBuilder, Func getFilterringTarget, IFilter filter) - where TBuilder : HandlerBuilderBase - where TFilterTarget : class - { - handlerBuilder.AddTargetedFilter(getFilterringTarget, filter); - return handlerBuilder; - } + /// + /// Adds a targeted filter for a specific filter target type. + /// + /// + /// The type of the filter target. + /// + /// Function to get the filter target from an update. + /// The filter to add. + /// The builder instance. + public static TBuilder AddTargetedFilter(this TBuilder handlerBuilder, Func getFilterringTarget, IFilter filter) + where TBuilder : HandlerBuilderBase + where TFilterTarget : class + { + handlerBuilder.AddTargetedFilter(getFilterringTarget, filter); + return handlerBuilder; + } - /// - /// Adds multiple targeted filters for a specific filter target type. - /// - /// - /// The type of the filter target. - /// - /// Function to get the filter target from an update. - /// The filters to add. - /// The builder instance. - public static TBuilder AddTargetedFilters(this TBuilder handlerBuilder, Func getFilterringTarget, params IFilter[] filters) - where TBuilder : HandlerBuilderBase - where TFilterTarget : class - { - handlerBuilder.AddTargetedFilters(getFilterringTarget, filters); - return handlerBuilder; - } + /// + /// Adds multiple targeted filters for a specific filter target type. + /// + /// + /// The type of the filter target. + /// + /// Function to get the filter target from an update. + /// The filters to add. + /// The builder instance. + public static TBuilder AddTargetedFilters(this TBuilder handlerBuilder, Func getFilterringTarget, params IFilter[] filters) + where TBuilder : HandlerBuilderBase + where TFilterTarget : class + { + handlerBuilder.AddTargetedFilters(getFilterringTarget, filters); + return handlerBuilder; } } diff --git a/src/Telegrator/Handlers/CallbackQueryHandler.cs b/src/Telegrator/Handlers/CallbackQueryHandler.cs index e129bf7..17dd5a5 100644 --- a/src/Telegrator/Handlers/CallbackQueryHandler.cs +++ b/src/Telegrator/Handlers/CallbackQueryHandler.cs @@ -5,123 +5,122 @@ using Telegrator.Attributes; using Telegrator.Core.Filters; using Telegrator.Core.Handlers; -namespace Telegrator.Handlers +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. +/// +/// +public sealed class CallbackQueryHandlerAttribute(int importance = 0) : UpdateHandlerAttribute(UpdateType.CallbackQuery, importance) { /// - /// 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. + /// Always returns true, allowing any callback query update to pass through this filter. /// - /// - public sealed class CallbackQueryHandlerAttribute(int importance = 0) : UpdateHandlerAttribute(UpdateType.CallbackQuery, importance) + /// The filter execution context (unused). + /// Always returns true to allow any callback query update. + public override bool CanPass(FilterExecutionContext context) => context.Input is { CallbackQuery: { } }; +} + +/// +/// Abstract base class for handlers that process callback query updates. +/// Provides a foundation for creating handlers that respond to user interactions with inline keyboards. +/// +public abstract class CallbackQueryHandler() : AbstractUpdateHandler(UpdateType.CallbackQuery) +{ + /// + /// Gets the type-specific data from the callback query. + /// Returns the data string, chat instance, or game short name depending on the callback query type. + /// + protected string TypeData { - /// - /// Always returns true, allowing any callback query update to pass through this filter. - /// - /// The filter execution context (unused). - /// Always returns true to allow any callback query update. - public override bool CanPass(FilterExecutionContext context) => context.Input is { CallbackQuery: { } }; + get => Input switch + { + { Data: { } data } => data, + { ChatInstance: { } chatInstance } => chatInstance, + { GameShortName: { } gameShortName } => gameShortName + }; } /// - /// Abstract base class for handlers that process callback query updates. - /// Provides a foundation for creating handlers that respond to user interactions with inline keyboards. + /// Sends a response message to the current chat. /// - public abstract class CallbackQueryHandler() : AbstractUpdateHandler(UpdateType.CallbackQuery) - { - /// - /// Gets the type-specific data from the callback query. - /// Returns the data string, chat instance, or game short name depending on the callback query type. - /// - protected string TypeData - { - get => Input switch - { - { Data: { } data } => data, - { ChatInstance: { } chatInstance } => chatInstance, - { GameShortName: { } gameShortName } => gameShortName - }; - } + /// The text of the message to send. + /// The parse mode for the message text. + /// The reply parameters for the message. + /// The reply markup for the message. + /// Options for link preview generation. + /// The thread ID for forum topics. + /// The message entities to include. + /// Whether to disable notification for the message. + /// Whether to protect the message content. + /// The message effect ID. + /// The business connection ID. + /// Whether to allow paid broadcast. + /// + /// + /// The cancellation token. + /// The sent message. + protected async Task Responce( + string text, + ParseMode parseMode = ParseMode.None, + ReplyParameters? replyParameters = null, + ReplyMarkup? replyMarkup = null, + LinkPreviewOptions? linkPreviewOptions = null, + int? messageThreadId = null, + IEnumerable? entities = null, + bool disableNotification = false, + bool protectContent = false, + string? messageEffectId = null, + string? businessConnectionId = null, + bool allowPaidBroadcast = false, + int? directMessageTopicId = null, + SuggestedPostParameters? suggestedPostParameters = null, + CancellationToken cancellationToken = default) + => await Container.Responce( + text, parseMode, replyParameters, + replyMarkup, linkPreviewOptions, + messageThreadId, entities, + disableNotification, protectContent, + messageEffectId, businessConnectionId, + allowPaidBroadcast, directMessageTopicId, + suggestedPostParameters, cancellationToken); - /// - /// Sends a response message to the current chat. - /// - /// The text of the message to send. - /// The parse mode for the message text. - /// The reply parameters for the message. - /// The reply markup for the message. - /// Options for link preview generation. - /// The thread ID for forum topics. - /// The message entities to include. - /// Whether to disable notification for the message. - /// Whether to protect the message content. - /// The message effect ID. - /// The business connection ID. - /// Whether to allow paid broadcast. - /// - /// - /// The cancellation token. - /// The sent message. - protected async Task Responce( - string text, - ParseMode parseMode = ParseMode.None, - ReplyParameters? replyParameters = null, - ReplyMarkup? replyMarkup = null, - LinkPreviewOptions? linkPreviewOptions = null, - int? messageThreadId = null, - IEnumerable? entities = null, - bool disableNotification = false, - bool protectContent = false, - string? messageEffectId = null, - string? businessConnectionId = null, - bool allowPaidBroadcast = false, - int? directMessageTopicId = null, - SuggestedPostParameters? suggestedPostParameters = null, - CancellationToken cancellationToken = default) - => await Container.Responce( - text, parseMode, replyParameters, - replyMarkup, linkPreviewOptions, - messageThreadId, entities, - disableNotification, protectContent, - messageEffectId, businessConnectionId, - allowPaidBroadcast, directMessageTopicId, - suggestedPostParameters, cancellationToken); - - /// - /// Edits the current callback message with new text. - /// - /// The new text of the message. - /// The parse mode for the message text. - /// The reply markup for the message. - /// The message entities to include. - /// Options for link preview generation. - /// The cancellation token. - /// The edited message. - protected async Task EditMessage( - string text, - ParseMode parseMode = ParseMode.None, - InlineKeyboardMarkup? replyMarkup = null, - IEnumerable? entities = null, - LinkPreviewOptions? linkPreviewOptions = null, - CancellationToken cancellationToken = default) - => await Container.EditMessage( - text, parseMode, replyMarkup, - entities, linkPreviewOptions, cancellationToken); - - /// - /// Answers the current callback query with optional alert or message. - /// - /// The text to display in the callback answer. - /// Whether to show an alert popup instead of a toast. - /// A URL that will be opened by the client. - /// The maximum amount of time in seconds that the result of the callback query may be cached client-side. - /// The cancellation token. - protected async Task Answer( - string? text = null, - bool showAlert = false, - string? url = null, - int cacheTime = 0, - CancellationToken cancellationToken = default) - => await Container.AnswerCallbackQuery( - text, showAlert, url, cacheTime, cancellationToken); - } + /// + /// Edits the current callback message with new text. + /// + /// The new text of the message. + /// The parse mode for the message text. + /// The reply markup for the message. + /// The message entities to include. + /// Options for link preview generation. + /// The cancellation token. + /// The edited message. + protected async Task EditMessage( + string text, + ParseMode parseMode = ParseMode.None, + InlineKeyboardMarkup? replyMarkup = null, + IEnumerable? entities = null, + LinkPreviewOptions? linkPreviewOptions = null, + CancellationToken cancellationToken = default) + => await Container.EditMessage( + text, parseMode, replyMarkup, + entities, linkPreviewOptions, cancellationToken); + + /// + /// Answers the current callback query with optional alert or message. + /// + /// The text to display in the callback answer. + /// Whether to show an alert popup instead of a toast. + /// A URL that will be opened by the client. + /// The maximum amount of time in seconds that the result of the callback query may be cached client-side. + /// The cancellation token. + protected async Task Answer( + string? text = null, + bool showAlert = false, + string? url = null, + int cacheTime = 0, + CancellationToken cancellationToken = default) + => await Container.AnswerCallbackQuery( + text, showAlert, url, cacheTime, cancellationToken); } diff --git a/src/Telegrator/Handlers/CommandHandler.cs b/src/Telegrator/Handlers/CommandHandler.cs index 26c0b28..fb8ec45 100644 --- a/src/Telegrator/Handlers/CommandHandler.cs +++ b/src/Telegrator/Handlers/CommandHandler.cs @@ -3,182 +3,181 @@ using Telegram.Bot.Types.Enums; using Telegrator.Attributes; using Telegrator.Core.Filters; -namespace Telegrator.Handlers +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 '/'). +/// +public class CommandHandlerAttribute(int importance = 1) : UpdateHandlerAttribute(UpdateType.Message, importance) { /// - /// Attribute that marks a handler to process command messages. - /// This handler will be triggered when users send bot commands (messages starting with '/'). + /// Gets the command that was extracted from the message (without the '/' prefix and bot username). /// - public class CommandHandlerAttribute(int importance = 1) : UpdateHandlerAttribute(UpdateType.Message, importance) - { - /// - /// Gets the command that was extracted from the message (without the '/' prefix and bot username). - /// - public string ReceivedCommand { get; private set; } = null!; - - /// - /// Message text splited by space characters - /// - public string[]? Arguments { get; internal set; } = null; - - /// - /// Checks if the update contains a valid bot command and extracts the command text. - /// - /// The filter execution context containing the update. - /// True if the update contains a valid bot command; otherwise, false. - public override bool CanPass(FilterExecutionContext context) - { - if (context.Input.Message is not { Entities.Length: > 0, Text.Length: > 0 } message) - return false; - - MessageEntity commandEntity = message.Entities[0]; - if (commandEntity.Type != MessageEntityType.BotCommand) - return false; - - if (commandEntity.Offset != 0) - return false; - - ReceivedCommand = message.Text.Substring(1, commandEntity.Length - 1); - if (ReceivedCommand.Contains('@')) - { - string[] split = ReceivedCommand.Split('@'); - ReceivedCommand = split[0]; - - if (!split.ElementAtOrDefault(1).Equals(context.BotInfo.User.Username)) - return false; - } - - return true; - } - } + public string ReceivedCommand { get; private set; } = null!; /// - /// Abstract base class for handlers that process command messages. - /// Provides functionality to extract and parse command arguments. + /// Message text splited by space characters /// - public abstract class CommandHandler : MessageHandler - { - /// - /// Cached array of command arguments. - /// - private string[]? _cmdArgsSplit; - - /// - /// Cached string representation of command arguments. - /// - private string? _argsString; - - /// - /// Gets the command that was extracted from the message. - /// - protected string ReceivedCommand - { - get => CompletedFilters.Get(0).ReceivedCommand; - } - - /// - /// Gets the arguments string (everything after the command). - /// - protected string ArgumentsString - { - get => _argsString ??= ArgsStringify(); - } - - /// - /// Gets the command arguments as an array of strings. - /// - protected string[] Arguments - { - get => _cmdArgsSplit ??= SplitArgs(); - } - - /// - /// Splits the command arguments into an array of strings. - /// - /// An array of command arguments. - private string[] SplitArgs() - { - if (Input.Text is not { Length: > 0 }) - return []; - - return Input.Text.Split([" "], StringSplitOptions.RemoveEmptyEntries).Skip(1).ToArray(); - } - - /// - /// Extracts the arguments string from the command message. - /// - /// The arguments string (everything after the command). - private string ArgsStringify() - { - if (Input.Text is not { Length: > 0 }) - return string.Empty; - - return Input.Text.Substring(ReceivedCommand.Length + 1); - } - } + public string[]? Arguments { get; internal set; } = null; /// - /// Abstract base class for branching handlers that process command messages. - /// Provides functionality to extract and parse command arguments for branching scenarios. + /// Checks if the update contains a valid bot command and extracts the command text. /// - public abstract class BranchingCommandHandler : BranchingMessageHandler + /// The filter execution context containing the update. + /// True if the update contains a valid bot command; otherwise, false. + public override bool CanPass(FilterExecutionContext context) { - /// - /// Cached array of command arguments. - /// - private string[]? _cmdArgsSplit; + if (context.Input.Message is not { Entities.Length: > 0, Text.Length: > 0 } message) + return false; + + MessageEntity commandEntity = message.Entities[0]; + if (commandEntity.Type != MessageEntityType.BotCommand) + return false; + + if (commandEntity.Offset != 0) + return false; + + ReceivedCommand = message.Text.Substring(1, commandEntity.Length - 1); + if (ReceivedCommand.Contains('@')) + { + string[] split = ReceivedCommand.Split('@'); + ReceivedCommand = split[0]; + + if (!split.ElementAtOrDefault(1).Equals(context.BotInfo.User.Username)) + return false; + } - /// - /// Cached string representation of command arguments. - /// - private string? _argsString; - - /// - /// Gets the command that was extracted from the message. - /// - protected string ReceivedCommand - { - get => CompletedFilters.Get(0).ReceivedCommand; - } - - /// - /// Gets the arguments string (everything after the command). - /// - protected string ArgumentsString - { - get => _argsString ??= ArgsStringify(); - } - - /// - /// Gets the command arguments as an array of strings. - /// - protected string[] Arguments - { - get => _cmdArgsSplit ??= SplitArgs(); - } - - /// - /// Splits the command arguments into an array of strings. - /// - /// An array of command arguments. - private string[] SplitArgs() - { - if (Input.Text is not { Length: > 0 }) - return []; - - return Input.Text.Split([" "], StringSplitOptions.RemoveEmptyEntries).Skip(1).ToArray(); - } - - /// - /// Extracts the arguments string from the command message. - /// - /// The arguments string (everything after the command). - private string ArgsStringify() - { - if (Input.Text is not { Length: > 0 }) - return string.Empty; - - return Input.Text.Substring(ReceivedCommand.Length + 1); - } + return true; + } +} + +/// +/// Abstract base class for handlers that process command messages. +/// Provides functionality to extract and parse command arguments. +/// +public abstract class CommandHandler : MessageHandler +{ + /// + /// Cached array of command arguments. + /// + private string[]? _cmdArgsSplit; + + /// + /// Cached string representation of command arguments. + /// + private string? _argsString; + + /// + /// Gets the command that was extracted from the message. + /// + protected string ReceivedCommand + { + get => CompletedFilters.Get(0).ReceivedCommand; + } + + /// + /// Gets the arguments string (everything after the command). + /// + protected string ArgumentsString + { + get => _argsString ??= ArgsStringify(); + } + + /// + /// Gets the command arguments as an array of strings. + /// + protected string[] Arguments + { + get => _cmdArgsSplit ??= SplitArgs(); + } + + /// + /// Splits the command arguments into an array of strings. + /// + /// An array of command arguments. + private string[] SplitArgs() + { + if (Input.Text is not { Length: > 0 }) + return []; + + return Input.Text.Split([" "], StringSplitOptions.RemoveEmptyEntries).Skip(1).ToArray(); + } + + /// + /// Extracts the arguments string from the command message. + /// + /// The arguments string (everything after the command). + private string ArgsStringify() + { + if (Input.Text is not { Length: > 0 }) + return string.Empty; + + return Input.Text.Substring(ReceivedCommand.Length + 1); + } +} + +/// +/// Abstract base class for branching handlers that process command messages. +/// Provides functionality to extract and parse command arguments for branching scenarios. +/// +public abstract class BranchingCommandHandler : BranchingMessageHandler +{ + /// + /// Cached array of command arguments. + /// + private string[]? _cmdArgsSplit; + + /// + /// Cached string representation of command arguments. + /// + private string? _argsString; + + /// + /// Gets the command that was extracted from the message. + /// + protected string ReceivedCommand + { + get => CompletedFilters.Get(0).ReceivedCommand; + } + + /// + /// Gets the arguments string (everything after the command). + /// + protected string ArgumentsString + { + get => _argsString ??= ArgsStringify(); + } + + /// + /// Gets the command arguments as an array of strings. + /// + protected string[] Arguments + { + get => _cmdArgsSplit ??= SplitArgs(); + } + + /// + /// Splits the command arguments into an array of strings. + /// + /// An array of command arguments. + private string[] SplitArgs() + { + if (Input.Text is not { Length: > 0 }) + return []; + + return Input.Text.Split([" "], StringSplitOptions.RemoveEmptyEntries).Skip(1).ToArray(); + } + + /// + /// Extracts the arguments string from the command message. + /// + /// The arguments string (everything after the command). + private string ArgsStringify() + { + if (Input.Text is not { Length: > 0 }) + return string.Empty; + + return Input.Text.Substring(ReceivedCommand.Length + 1); } } diff --git a/src/Telegrator/Handlers/Diagnostics/FilterFallbackInfo.cs b/src/Telegrator/Handlers/Diagnostics/FilterFallbackInfo.cs index 976efa6..d56cb32 100644 --- a/src/Telegrator/Handlers/Diagnostics/FilterFallbackInfo.cs +++ b/src/Telegrator/Handlers/Diagnostics/FilterFallbackInfo.cs @@ -1,36 +1,35 @@ using Telegram.Bot.Types; using Telegrator.Core.Filters; -namespace Telegrator.Handlers.Diagnostics +namespace Telegrator.Handlers.Diagnostics; + +/// +/// Contains information about a filter that failed during execution. +/// Provides details about the filter, its failure status, and any associated exception. +/// +/// The name of the filter. +/// The filter instance that failed. +/// Whether the filter failed. +/// The exception that occurred during filter execution, if any. +public class FilterFallbackInfo(string name, IFilter filter, bool failed, Exception? exception) { /// - /// Contains information about a filter that failed during execution. - /// Provides details about the filter, its failure status, and any associated exception. + /// Gets the name of the filter. /// - /// The name of the filter. - /// The filter instance that failed. - /// Whether the filter failed. - /// The exception that occurred during filter execution, if any. - public class FilterFallbackInfo(string name, IFilter filter, bool failed, Exception? exception) - { - /// - /// Gets the name of the filter. - /// - public string Name { get; } = name; + public string Name { get; } = name; - /// - /// Gets the filter instance that failed. - /// - public IFilter Filter { get; } = filter; + /// + /// Gets the filter instance that failed. + /// + public IFilter Filter { get; } = filter; - /// - /// Gets a value indicating whether the filter failed. - /// - public bool Failed { get; } = failed; + /// + /// Gets a value indicating whether the filter failed. + /// + public bool Failed { get; } = failed; - /// - /// Gets the exception that occurred during filter execution, if any. - /// - public Exception? Exception { get; } = exception; - } + /// + /// Gets the exception that occurred during filter execution, if any. + /// + public Exception? Exception { get; } = exception; } diff --git a/src/Telegrator/Handlers/Diagnostics/FiltersFallbackReport.cs b/src/Telegrator/Handlers/Diagnostics/FiltersFallbackReport.cs index 87a8a35..474f0ad 100644 --- a/src/Telegrator/Handlers/Diagnostics/FiltersFallbackReport.cs +++ b/src/Telegrator/Handlers/Diagnostics/FiltersFallbackReport.cs @@ -2,64 +2,63 @@ using Telegrator.Core.Descriptors; using Telegrator.Core.Filters; -namespace Telegrator.Handlers.Diagnostics +namespace Telegrator.Handlers.Diagnostics; + +/// +/// Represents a report of filter fallback information for debugging and error handling. +/// Contains detailed information about which filters failed and why during handler execution. +/// +/// The handler descriptor that generated this report. +/// The filter execution context. +public class FiltersFallbackReport(HandlerDescriptor descriptor, FilterExecutionContext context) { /// - /// Represents a report of filter fallback information for debugging and error handling. - /// Contains detailed information about which filters failed and why during handler execution. + /// Gets the handler descriptor associated with this fallback report. /// - /// The handler descriptor that generated this report. - /// The filter execution context. - public class FiltersFallbackReport(HandlerDescriptor descriptor, FilterExecutionContext context) + public HandlerDescriptor Descriptor { get; } = descriptor; + + /// + /// Gets the filter execution context that generated this report. + /// + public FilterExecutionContext Context { get; } = context; + + /// + /// Gets or sets the fallback information for the update validator filter. + /// + public FilterFallbackInfo? UpdateValidator { get; set; } + + /// + /// Gets or sets the fallback information for the state keeper validator filter. + /// + public FilterFallbackInfo? StateKeeperValidator { get; set; } + + /// + /// Gets the list of fallback information for update filters that failed. + /// + public List UpdateFilters { get; } = []; + + /// + /// Checks filter fail status by name + /// + /// + /// + public bool this[string name] => UpdateFilters.FirstOrDefault(f => f.Name == name)?.Failed ?? false; + + /// + /// Creates new instance of with default filter state as FAILED. + /// + /// + public ReportInspector AllFailed() { - /// - /// Gets the handler descriptor associated with this fallback report. - /// - public HandlerDescriptor Descriptor { get; } = descriptor; + return new ReportInspector(this, false); + } - /// - /// Gets the filter execution context that generated this report. - /// - public FilterExecutionContext Context { get; } = context; - - /// - /// Gets or sets the fallback information for the update validator filter. - /// - public FilterFallbackInfo? UpdateValidator { get; set; } - - /// - /// Gets or sets the fallback information for the state keeper validator filter. - /// - public FilterFallbackInfo? StateKeeperValidator { get; set; } - - /// - /// Gets the list of fallback information for update filters that failed. - /// - public List UpdateFilters { get; } = []; - - /// - /// Checks filter fail status by name - /// - /// - /// - public bool this[string name] => UpdateFilters.FirstOrDefault(f => f.Name == name)?.Failed ?? false; - - /// - /// Creates new instance of with default filter state as FAILED. - /// - /// - public ReportInspector AllFailed() - { - return new ReportInspector(this, false); - } - - /// - /// Creates new instance of with default filter state as PASSED. - /// - /// - public ReportInspector AllPassed() - { - return new ReportInspector(this, true); - } + /// + /// Creates new instance of with default filter state as PASSED. + /// + /// + public ReportInspector AllPassed() + { + return new ReportInspector(this, true); } } diff --git a/src/Telegrator/Handlers/Diagnostics/ReportInspector.cs b/src/Telegrator/Handlers/Diagnostics/ReportInspector.cs index afcc8cc..90c2b21 100644 --- a/src/Telegrator/Handlers/Diagnostics/ReportInspector.cs +++ b/src/Telegrator/Handlers/Diagnostics/ReportInspector.cs @@ -1,73 +1,72 @@ -namespace Telegrator.Handlers.Diagnostics +namespace Telegrator.Handlers.Diagnostics; + +/// +/// A class builder for pattern checking of +/// +/// +/// +public sealed class ReportInspector(FiltersFallbackReport report, bool defaulState) { + private readonly FiltersFallbackReport _report = report; + private readonly bool _defaulState = defaulState; + + private readonly List _ignore = []; + private readonly List _excepts = []; + /// - /// A class builder for pattern checking of + /// Adds a filter to the exclusion list. + /// Excluded filters are compared oppositely with the default state. /// - /// - /// - public sealed class ReportInspector(FiltersFallbackReport report, bool defaulState) + /// + /// + public ReportInspector Except(string name) { - private readonly FiltersFallbackReport _report = report; - private readonly bool _defaulState = defaulState; + _excepts.Add(name); + return this; + } - private readonly List _ignore = []; - private readonly List _excepts = []; + /// + /// Adds a filter to the ignore list. + /// Ignored filters are not checked and do not affect the final result. + /// + /// + /// + public ReportInspector Whenever(string name) + { + _ignore.Add(name); + return this; + } - /// - /// Adds a filter to the exclusion list. - /// Excluded filters are compared oppositely with the default state. - /// - /// - /// - public ReportInspector Except(string name) + /// + /// It goes through the report and compares it with the specified filter pattern. + /// + /// + public bool Match() + { + foreach (FilterFallbackInfo info in _report.UpdateFilters) { - _excepts.Add(name); - return this; - } + if (_ignore.Contains(info.Name)) + continue; - /// - /// Adds a filter to the ignore list. - /// Ignored filters are not checked and do not affect the final result. - /// - /// - /// - public ReportInspector Whenever(string name) - { - _ignore.Add(name); - return this; - } - - /// - /// It goes through the report and compares it with the specified filter pattern. - /// - /// - public bool Match() - { - foreach (FilterFallbackInfo info in _report.UpdateFilters) + if (_excepts.Contains(info.Name)) { - if (_ignore.Contains(info.Name)) - continue; + if (_defaulState == true && !info.Failed) + return false; - if (_excepts.Contains(info.Name)) - { - if (_defaulState == true && !info.Failed) - return false; - - if (_defaulState == false && info.Failed) - return false; - } - - if (!info.Failed != _defaulState) + if (_defaulState == false && info.Failed) return false; } - return true; + if (!info.Failed != _defaulState) + return false; } - /// - /// Casts inspector by executing - /// - /// - public static implicit operator bool(ReportInspector inspector) => inspector.Match(); + return true; } + + /// + /// Casts inspector by executing + /// + /// + public static implicit operator bool(ReportInspector inspector) => inspector.Match(); } diff --git a/src/Telegrator/Handlers/Diagnostics/TypesExtensions.cs b/src/Telegrator/Handlers/Diagnostics/TypesExtensions.cs index 5bc6f07..431552b 100644 --- a/src/Telegrator/Handlers/Diagnostics/TypesExtensions.cs +++ b/src/Telegrator/Handlers/Diagnostics/TypesExtensions.cs @@ -1,18 +1,17 @@ using Telegrator.Core.Attributes; -namespace Telegrator.Handlers.Diagnostics -{ - /// - /// Provides extension methods for - /// - public static partial class ReportInspectorExtensions - { - /// - public static ReportInspector Whenever(this ReportInspector inspector) where TAttribute : UpdateFilterAttributeBase - => inspector.Whenever(nameof(TAttribute)); +namespace Telegrator.Handlers.Diagnostics; - /// - public static ReportInspector Except(this ReportInspector inspector) where TAttribute : UpdateFilterAttributeBase - => inspector.Except(nameof(TAttribute)); - } +/// +/// Provides extension methods for +/// +public static partial class ReportInspectorExtensions +{ + /// + public static ReportInspector Whenever(this ReportInspector inspector) where TAttribute : UpdateFilterAttributeBase + => inspector.Whenever(nameof(TAttribute)); + + /// + public static ReportInspector Except(this ReportInspector inspector) where TAttribute : UpdateFilterAttributeBase + => inspector.Except(nameof(TAttribute)); } diff --git a/src/Telegrator/Handlers/HandlerContainer.cs b/src/Telegrator/Handlers/HandlerContainer.cs index 663df37..540c1fe 100644 --- a/src/Telegrator/Handlers/HandlerContainer.cs +++ b/src/Telegrator/Handlers/HandlerContainer.cs @@ -5,99 +5,98 @@ using Telegrator.Core.Descriptors; using Telegrator.Core.Filters; using Telegrator.Core.States; -namespace Telegrator.Handlers +namespace Telegrator.Handlers; + +/// +/// Container class that holds the context and data for handler execution. +/// Provides access to the update, client, filters, and other execution context. +/// +/// The type of update being handled. +public class HandlerContainer : IHandlerContainer where TUpdate : class { /// - /// Container class that holds the context and data for handler execution. - /// Provides access to the update, client, filters, and other execution context. + /// Gets the actual update object of type TUpdate. /// - /// The type of update being handled. - public class HandlerContainer : IHandlerContainer where TUpdate : class + public TUpdate ActualUpdate { get; } + + /// + public Update HandlingUpdate { get; } + + /// + public ITelegramBotClient Client { get; } + + /// + public Dictionary ExtraData { get; } + + /// + public CompletedFiltersList CompletedFilters { get; } + + /// + public IAwaitingProvider AwaitingProvider { get; } + + /// + public IStateStorage StateStorage { get; } + + /// + /// Initializes new instance of + /// + /// + public HandlerContainer(DescribedHandlerDescriptor handlerInfo) { - /// - /// Gets the actual update object of type TUpdate. - /// - public TUpdate ActualUpdate { get; } + ActualUpdate = handlerInfo.HandlingUpdate.GetActualUpdateObject(); + HandlingUpdate = handlerInfo.HandlingUpdate; + Client = handlerInfo.Client; + ExtraData = handlerInfo.ExtraData; + CompletedFilters = handlerInfo.CompletedFilters; + AwaitingProvider = handlerInfo.AwaitingProvider; + StateStorage = handlerInfo.StateStorage; + } - /// - public Update HandlingUpdate { get; } + /// + /// Initializes new instance of + /// + /// + /// + /// + /// + /// + /// + /// + public HandlerContainer(TUpdate actualUpdate, Update handlingUpdate, ITelegramBotClient client, Dictionary extraData, CompletedFiltersList filters, IAwaitingProvider awaitingProvider, IStateStorage stateStorage) + { + ActualUpdate = actualUpdate; + HandlingUpdate = handlingUpdate; + Client = client; + ExtraData = extraData; + CompletedFilters = filters; + AwaitingProvider = awaitingProvider; + StateStorage = stateStorage; + } - /// - public ITelegramBotClient Client { get; } + /// + /// Creates new container of specific update type from thos contatiner + /// + /// + /// + public HandlerContainer CreateChild() where QUpdate : class + { + return new HandlerContainer( + HandlingUpdate.GetActualUpdateObject(), + HandlingUpdate, Client, ExtraData, CompletedFilters, + AwaitingProvider, StateStorage); + } - /// - public Dictionary ExtraData { get; } - - /// - public CompletedFiltersList CompletedFilters { get; } - - /// - public IAwaitingProvider AwaitingProvider { get; } - - /// - public IStateStorage StateStorage { get; } - - /// - /// Initializes new instance of - /// - /// - public HandlerContainer(DescribedHandlerDescriptor handlerInfo) - { - ActualUpdate = handlerInfo.HandlingUpdate.GetActualUpdateObject(); - HandlingUpdate = handlerInfo.HandlingUpdate; - Client = handlerInfo.Client; - ExtraData = handlerInfo.ExtraData; - CompletedFilters = handlerInfo.CompletedFilters; - AwaitingProvider = handlerInfo.AwaitingProvider; - StateStorage = handlerInfo.StateStorage; - } - - /// - /// Initializes new instance of - /// - /// - /// - /// - /// - /// - /// - /// - public HandlerContainer(TUpdate actualUpdate, Update handlingUpdate, ITelegramBotClient client, Dictionary extraData, CompletedFiltersList filters, IAwaitingProvider awaitingProvider, IStateStorage stateStorage) - { - ActualUpdate = actualUpdate; - HandlingUpdate = handlingUpdate; - Client = client; - ExtraData = extraData; - CompletedFilters = filters; - AwaitingProvider = awaitingProvider; - StateStorage = stateStorage; - } - - /// - /// Creates new container of specific update type from thos contatiner - /// - /// - /// - public HandlerContainer CreateChild() where QUpdate : class - { - return new HandlerContainer( - HandlingUpdate.GetActualUpdateObject(), - HandlingUpdate, Client, ExtraData, CompletedFilters, - AwaitingProvider, StateStorage); - } - - /// - /// Creates new container of specific update type from existing container - /// - /// - /// - /// - public static HandlerContainer From(IHandlerContainer other) where QUpdate : class - { - return new HandlerContainer( - other.HandlingUpdate.GetActualUpdateObject(), - other.HandlingUpdate, other.Client, other.ExtraData, other.CompletedFilters, - other.AwaitingProvider, other.StateStorage); - } + /// + /// Creates new container of specific update type from existing container + /// + /// + /// + /// + public static HandlerContainer From(IHandlerContainer other) where QUpdate : class + { + return new HandlerContainer( + other.HandlingUpdate.GetActualUpdateObject(), + other.HandlingUpdate, other.Client, other.ExtraData, other.CompletedFilters, + other.AwaitingProvider, other.StateStorage); } } diff --git a/src/Telegrator/Handlers/IHandlerContainer.cs b/src/Telegrator/Handlers/IHandlerContainer.cs index e220eae..5e019fa 100644 --- a/src/Telegrator/Handlers/IHandlerContainer.cs +++ b/src/Telegrator/Handlers/IHandlerContainer.cs @@ -1,16 +1,15 @@ using Telegrator.Core.Handlers; -namespace Telegrator.Handlers +namespace Telegrator.Handlers; + +/// +/// Represents a handler container for a specific update type. +/// +/// The type of update handled by the container. +public interface IHandlerContainer : IHandlerContainer where TUpdate : class { /// - /// Represents a handler container for a specific update type. + /// Gets the actual update object of type . /// - /// The type of update handled by the container. - public interface IHandlerContainer : IHandlerContainer where TUpdate : class - { - /// - /// Gets the actual update object of type . - /// - public TUpdate ActualUpdate { get; } - } + public TUpdate ActualUpdate { get; } } diff --git a/src/Telegrator/Handlers/InlineQueryHandler.cs b/src/Telegrator/Handlers/InlineQueryHandler.cs index d6e0a7e..d25c5e5 100644 --- a/src/Telegrator/Handlers/InlineQueryHandler.cs +++ b/src/Telegrator/Handlers/InlineQueryHandler.cs @@ -5,104 +5,103 @@ using Telegrator.Attributes; using Telegrator.Core.Filters; using Telegrator.Core.Handlers; -namespace Telegrator.Handlers +namespace Telegrator.Handlers; + +/// +/// Attribute that marks a handler to process inline queries. +/// IMPORTANT! You can have only ONE instance of this handler. +/// +public class InlineQueryHandlerAttribute(int importance = 0) : UpdateHandlerAttribute(UpdateType.InlineQuery, importance) +{ + /// + public override bool CanPass(FilterExecutionContext context) => context.Input.InlineQuery is { } | context.Input.ChosenInlineResult is { }; +} + +/// +/// Abstract base class for handlers that process inline queries. +/// IMPORTANT! You can have only ONE instance of this handler. +/// +public abstract class InlineQueryHandler() : AbstractUpdateHandler(UpdateType.InlineQuery) { /// - /// Attribute that marks a handler to process inline queries. - /// IMPORTANT! You can have only ONE instance of this handler. + /// Handler container for the current update. /// - public class InlineQueryHandlerAttribute(int importance = 0) : UpdateHandlerAttribute(UpdateType.InlineQuery, importance) + protected IHandlerContainer QueryContainer { get; private set; } = null!; + + /// + /// Handler container for the current update. + /// + protected IHandlerContainer ChosenContainer { get; private set; } = null!; + + /// + /// Incoming update of type . + /// + protected InlineQuery InputQuery { get; private set; } = null!; + + /// + /// Incoming update of type . + /// + protected ChosenInlineResult InputChosen { get; private set; } = null!; + + /// + public override async Task Execute(IHandlerContainer container, CancellationToken cancellation) { - /// - public override bool CanPass(FilterExecutionContext context) => context.Input.InlineQuery is { } | context.Input.ChosenInlineResult is { }; + switch (container.HandlingUpdate.Type) + { + case UpdateType.InlineQuery: + { + QueryContainer = HandlerContainer.From(container); + InputQuery = QueryContainer.ActualUpdate; + return await Requested(QueryContainer, cancellation).ConfigureAwait(false); + } + + case UpdateType.ChosenInlineResult: + { + ChosenContainer = HandlerContainer.From(container); + InputChosen = ChosenContainer.ActualUpdate; + return await Chosen(ChosenContainer, cancellation).ConfigureAwait(false); + } + + default: + throw new NotImplementedException(); + } } /// - /// Abstract base class for handlers that process inline queries. - /// IMPORTANT! You can have only ONE instance of this handler. + /// Executes handler logic if received update is /// - public abstract class InlineQueryHandler() : AbstractUpdateHandler(UpdateType.InlineQuery) - { - /// - /// Handler container for the current update. - /// - protected IHandlerContainer QueryContainer { get; private set; } = null!; + /// + /// + /// + public abstract Task Requested(IHandlerContainer container, CancellationToken cancellation); - /// - /// Handler container for the current update. - /// - protected IHandlerContainer ChosenContainer { get; private set; } = null!; + /// + /// Executes handler logic if received update is + /// + /// + /// + /// + public abstract Task Chosen(IHandlerContainer container, CancellationToken cancellation); - /// - /// Incoming update of type . - /// - protected InlineQuery InputQuery { get; private set; } = null!; - - /// - /// Incoming update of type . - /// - protected ChosenInlineResult InputChosen { get; private set; } = null!; - - /// - public override async Task Execute(IHandlerContainer container, CancellationToken cancellation) - { - switch (container.HandlingUpdate.Type) - { - case UpdateType.InlineQuery: - { - QueryContainer = HandlerContainer.From(container); - InputQuery = QueryContainer.ActualUpdate; - return await Requested(QueryContainer, cancellation).ConfigureAwait(false); - } - - case UpdateType.ChosenInlineResult: - { - ChosenContainer = HandlerContainer.From(container); - InputChosen = ChosenContainer.ActualUpdate; - return await Chosen(ChosenContainer, cancellation).ConfigureAwait(false); - } - - default: - throw new NotImplementedException(); - } - } - - /// - /// Executes handler logic if received update is - /// - /// - /// - /// - public abstract Task Requested(IHandlerContainer container, CancellationToken cancellation); - - /// - /// Executes handler logic if received update is - /// - /// - /// - /// - public abstract Task Chosen(IHandlerContainer container, CancellationToken cancellation); - - /// - /// Answers inline query - /// - /// - /// - /// - /// - /// - /// - /// - protected async Task Answer( - IEnumerable results, - int? cacheTime = null, - bool isPersonal = false, - string? nextOffset = null, - InlineQueryResultsButton? button = null, - CancellationToken cancellationToken = default) - => await QueryContainer.AnswerInlineQuery( - results, cacheTime, - isPersonal, nextOffset, - button, cancellationToken); - } + /// + /// Answers inline query + /// + /// + /// + /// + /// + /// + /// + /// + protected async Task Answer( + IEnumerable results, + int? cacheTime = null, + bool isPersonal = false, + string? nextOffset = null, + InlineQueryResultsButton? button = null, + CancellationToken cancellationToken = default) + => await QueryContainer.AnswerInlineQuery( + results, cacheTime, + isPersonal, nextOffset, + button, cancellationToken); } diff --git a/src/Telegrator/Handlers/MessageHandler.cs b/src/Telegrator/Handlers/MessageHandler.cs index 800727c..387ebbc 100644 --- a/src/Telegrator/Handlers/MessageHandler.cs +++ b/src/Telegrator/Handlers/MessageHandler.cs @@ -5,205 +5,204 @@ using Telegrator.Attributes; using Telegrator.Core.Filters; using Telegrator.Core.Handlers; -namespace Telegrator.Handlers +namespace Telegrator.Handlers; + +/// +/// Attribute that marks a handler to process message updates. +/// This handler will be triggered when users send messages in chats. +/// +public class MessageHandlerAttribute(int importance = 0) : UpdateHandlerAttribute(UpdateType.Message, importance) { /// - /// Attribute that marks a handler to process message updates. - /// This handler will be triggered when users send messages in chats. + /// Checks if the update contains a valid message. /// - public class MessageHandlerAttribute(int importance = 0) : UpdateHandlerAttribute(UpdateType.Message, importance) - { - /// - /// Checks if the update contains a valid message. - /// - /// The filter execution context containing the update. - /// True if the update contains a message; otherwise, false. - public override bool CanPass(FilterExecutionContext context) => context.Input is { Message: { } }; - } - - /// - /// Abstract base class for handlers that process message updates. - /// Provides convenient methods for sending replies and responses to messages. - /// - public abstract class MessageHandler() : AbstractUpdateHandler(UpdateType.Message) - { - /// - /// Sends a reply message to the current message. - /// - /// The text of the message to send. - /// The parse mode for the message text. - /// The reply markup for the message. - /// Options for link preview generation. - /// The thread ID for forum topics. - /// The message entities to include. - /// Whether to disable notification for the message. - /// Whether to protect the message content. - /// The message effect ID. - /// The business connection ID. - /// Whether to allow paid broadcast. - /// - /// - /// The cancellation token. - /// The sent message. - protected async Task Reply( - string text, - ParseMode parseMode = ParseMode.None, - ReplyMarkup? replyMarkup = null, - LinkPreviewOptions? linkPreviewOptions = null, - int? messageThreadId = null, - IEnumerable? entities = null, - bool disableNotification = false, - bool protectContent = false, - string? messageEffectId = null, - string? businessConnectionId = null, - bool allowPaidBroadcast = false, - int? directMessageTopicId = null, - SuggestedPostParameters? suggestedPostParameters = null, - CancellationToken cancellationToken = default) - => await Container.Reply( - text, parseMode, - replyMarkup, linkPreviewOptions, - messageThreadId, entities, - disableNotification, protectContent, - messageEffectId, businessConnectionId, - allowPaidBroadcast, directMessageTopicId, - suggestedPostParameters, cancellationToken); - - /// - /// Sends a responce message to the current chat. - /// - /// The text of the message to send. - /// The parse mode for the message text. - /// The reply parameters for the message. - /// The reply markup for the message. - /// Options for link preview generation. - /// The thread ID for forum topics. - /// The message entities to include. - /// Whether to disable notification for the message. - /// Whether to protect the message content. - /// The message effect ID. - /// The business connection ID. - /// Whether to allow paid broadcast. - /// - /// - /// The cancellation token. - /// The sent message. - protected async Task Responce( - string text, - ParseMode parseMode = ParseMode.None, - ReplyParameters? replyParameters = null, - ReplyMarkup? replyMarkup = null, - LinkPreviewOptions? linkPreviewOptions = null, - int? messageThreadId = null, - IEnumerable? entities = null, - bool disableNotification = false, - bool protectContent = false, - string? messageEffectId = null, - string? businessConnectionId = null, - bool allowPaidBroadcast = false, - int? directMessageTopicId = null, - SuggestedPostParameters? suggestedPostParameters = null, - CancellationToken cancellationToken = default) - => await Container.Responce( - text, parseMode, replyParameters, - replyMarkup, linkPreviewOptions, - messageThreadId, entities, - disableNotification, protectContent, - messageEffectId, businessConnectionId, - allowPaidBroadcast, directMessageTopicId, - suggestedPostParameters, cancellationToken); - } - - /// - /// Abstract base class for branching handlers that process message updates. - /// Provides convenient methods for sending replies and responses to messages in branching scenarios. - /// - public abstract class BranchingMessageHandler() : BranchingUpdateHandler(UpdateType.Message) - { - /// - /// Sends a reply message to the current message. - /// - /// The text of the message to send. - /// The parse mode for the message text. - /// The reply markup for the message. - /// Options for link preview generation. - /// The thread ID for forum topics. - /// The message entities to include. - /// Whether to disable notification for the message. - /// Whether to protect the message content. - /// The message effect ID. - /// The business connection ID. - /// Whether to allow paid broadcast. - /// - /// - /// The cancellation token. - /// The sent message. - protected async Task Reply( - string text, - ParseMode parseMode = ParseMode.None, - ReplyMarkup? replyMarkup = null, - LinkPreviewOptions? linkPreviewOptions = null, - int? messageThreadId = null, - IEnumerable? entities = null, - bool disableNotification = false, - bool protectContent = false, - string? messageEffectId = null, - string? businessConnectionId = null, - bool allowPaidBroadcast = false, - int? directMessageTopicId = null, - SuggestedPostParameters? suggestedPostParameters = null, - CancellationToken cancellationToken = default) - => await Container.Reply( - text, parseMode, - replyMarkup, linkPreviewOptions, - messageThreadId, entities, - disableNotification, protectContent, - messageEffectId, businessConnectionId, - allowPaidBroadcast, directMessageTopicId, - suggestedPostParameters, cancellationToken); - - /// - /// Sends a response message to the current chat. - /// - /// The text of the message to send. - /// The parse mode for the message text. - /// The reply parameters for the message. - /// The reply markup for the message. - /// Options for link preview generation. - /// The thread ID for forum topics. - /// The message entities to include. - /// Whether to disable notification for the message. - /// Whether to protect the message content. - /// The message effect ID. - /// The business connection ID. - /// Whether to allow paid broadcast. - /// - /// - /// The cancellation token. - /// The sent message. - protected async Task Responce( - string text, - ParseMode parseMode = ParseMode.None, - ReplyParameters? replyParameters = null, - ReplyMarkup? replyMarkup = null, - LinkPreviewOptions? linkPreviewOptions = null, - int? messageThreadId = null, - IEnumerable? entities = null, - bool disableNotification = false, - bool protectContent = false, - string? messageEffectId = null, - string? businessConnectionId = null, - bool allowPaidBroadcast = false, - int? directMessageTopicId = null, - SuggestedPostParameters? suggestedPostParameters = null, - CancellationToken cancellationToken = default) - => await Container.Responce( - text, parseMode, replyParameters, - replyMarkup, linkPreviewOptions, - messageThreadId, entities, - disableNotification, protectContent, - messageEffectId, businessConnectionId, - allowPaidBroadcast, directMessageTopicId, - suggestedPostParameters, cancellationToken); - } + /// The filter execution context containing the update. + /// True if the update contains a message; otherwise, false. + public override bool CanPass(FilterExecutionContext context) => context.Input is { Message: { } }; +} + +/// +/// Abstract base class for handlers that process message updates. +/// Provides convenient methods for sending replies and responses to messages. +/// +public abstract class MessageHandler() : AbstractUpdateHandler(UpdateType.Message) +{ + /// + /// Sends a reply message to the current message. + /// + /// The text of the message to send. + /// The parse mode for the message text. + /// The reply markup for the message. + /// Options for link preview generation. + /// The thread ID for forum topics. + /// The message entities to include. + /// Whether to disable notification for the message. + /// Whether to protect the message content. + /// The message effect ID. + /// The business connection ID. + /// Whether to allow paid broadcast. + /// + /// + /// The cancellation token. + /// The sent message. + protected async Task Reply( + string text, + ParseMode parseMode = ParseMode.None, + ReplyMarkup? replyMarkup = null, + LinkPreviewOptions? linkPreviewOptions = null, + int? messageThreadId = null, + IEnumerable? entities = null, + bool disableNotification = false, + bool protectContent = false, + string? messageEffectId = null, + string? businessConnectionId = null, + bool allowPaidBroadcast = false, + int? directMessageTopicId = null, + SuggestedPostParameters? suggestedPostParameters = null, + CancellationToken cancellationToken = default) + => await Container.Reply( + text, parseMode, + replyMarkup, linkPreviewOptions, + messageThreadId, entities, + disableNotification, protectContent, + messageEffectId, businessConnectionId, + allowPaidBroadcast, directMessageTopicId, + suggestedPostParameters, cancellationToken); + + /// + /// Sends a responce message to the current chat. + /// + /// The text of the message to send. + /// The parse mode for the message text. + /// The reply parameters for the message. + /// The reply markup for the message. + /// Options for link preview generation. + /// The thread ID for forum topics. + /// The message entities to include. + /// Whether to disable notification for the message. + /// Whether to protect the message content. + /// The message effect ID. + /// The business connection ID. + /// Whether to allow paid broadcast. + /// + /// + /// The cancellation token. + /// The sent message. + protected async Task Responce( + string text, + ParseMode parseMode = ParseMode.None, + ReplyParameters? replyParameters = null, + ReplyMarkup? replyMarkup = null, + LinkPreviewOptions? linkPreviewOptions = null, + int? messageThreadId = null, + IEnumerable? entities = null, + bool disableNotification = false, + bool protectContent = false, + string? messageEffectId = null, + string? businessConnectionId = null, + bool allowPaidBroadcast = false, + int? directMessageTopicId = null, + SuggestedPostParameters? suggestedPostParameters = null, + CancellationToken cancellationToken = default) + => await Container.Responce( + text, parseMode, replyParameters, + replyMarkup, linkPreviewOptions, + messageThreadId, entities, + disableNotification, protectContent, + messageEffectId, businessConnectionId, + allowPaidBroadcast, directMessageTopicId, + suggestedPostParameters, cancellationToken); +} + +/// +/// Abstract base class for branching handlers that process message updates. +/// Provides convenient methods for sending replies and responses to messages in branching scenarios. +/// +public abstract class BranchingMessageHandler() : BranchingUpdateHandler(UpdateType.Message) +{ + /// + /// Sends a reply message to the current message. + /// + /// The text of the message to send. + /// The parse mode for the message text. + /// The reply markup for the message. + /// Options for link preview generation. + /// The thread ID for forum topics. + /// The message entities to include. + /// Whether to disable notification for the message. + /// Whether to protect the message content. + /// The message effect ID. + /// The business connection ID. + /// Whether to allow paid broadcast. + /// + /// + /// The cancellation token. + /// The sent message. + protected async Task Reply( + string text, + ParseMode parseMode = ParseMode.None, + ReplyMarkup? replyMarkup = null, + LinkPreviewOptions? linkPreviewOptions = null, + int? messageThreadId = null, + IEnumerable? entities = null, + bool disableNotification = false, + bool protectContent = false, + string? messageEffectId = null, + string? businessConnectionId = null, + bool allowPaidBroadcast = false, + int? directMessageTopicId = null, + SuggestedPostParameters? suggestedPostParameters = null, + CancellationToken cancellationToken = default) + => await Container.Reply( + text, parseMode, + replyMarkup, linkPreviewOptions, + messageThreadId, entities, + disableNotification, protectContent, + messageEffectId, businessConnectionId, + allowPaidBroadcast, directMessageTopicId, + suggestedPostParameters, cancellationToken); + + /// + /// Sends a response message to the current chat. + /// + /// The text of the message to send. + /// The parse mode for the message text. + /// The reply parameters for the message. + /// The reply markup for the message. + /// Options for link preview generation. + /// The thread ID for forum topics. + /// The message entities to include. + /// Whether to disable notification for the message. + /// Whether to protect the message content. + /// The message effect ID. + /// The business connection ID. + /// Whether to allow paid broadcast. + /// + /// + /// The cancellation token. + /// The sent message. + protected async Task Responce( + string text, + ParseMode parseMode = ParseMode.None, + ReplyParameters? replyParameters = null, + ReplyMarkup? replyMarkup = null, + LinkPreviewOptions? linkPreviewOptions = null, + int? messageThreadId = null, + IEnumerable? entities = null, + bool disableNotification = false, + bool protectContent = false, + string? messageEffectId = null, + string? businessConnectionId = null, + bool allowPaidBroadcast = false, + int? directMessageTopicId = null, + SuggestedPostParameters? suggestedPostParameters = null, + CancellationToken cancellationToken = default) + => await Container.Responce( + text, parseMode, replyParameters, + replyMarkup, linkPreviewOptions, + messageThreadId, entities, + disableNotification, protectContent, + messageEffectId, businessConnectionId, + allowPaidBroadcast, directMessageTopicId, + suggestedPostParameters, cancellationToken); } diff --git a/src/Telegrator/Handlers/TypesExtensions.cs b/src/Telegrator/Handlers/TypesExtensions.cs index b4d5477..5b9cb57 100644 --- a/src/Telegrator/Handlers/TypesExtensions.cs +++ b/src/Telegrator/Handlers/TypesExtensions.cs @@ -4,281 +4,280 @@ using Telegram.Bot.Types.Enums; using Telegram.Bot.Types.InlineQueryResults; using Telegram.Bot.Types.ReplyMarkups; -namespace Telegrator.Handlers +namespace Telegrator.Handlers; + +/// +/// Provides usefull helper methods for abstract handler containers +/// +public static class AbstractHandlerContainerExtensions { /// - /// Provides usefull helper methods for abstract handler containers + /// Changes bot's reaction to message /// - public static class AbstractHandlerContainerExtensions + /// + /// + /// + /// + /// + public static async Task React( + this IHandlerContainer container, + ReactionType reaction, + bool isBig = false, + CancellationToken cancellationToken = default) + => await container.Client.SetMessageReaction( + container.ActualUpdate.Chat, + container.ActualUpdate.Id, + [reaction], isBig, cancellationToken); + + /// + /// Changes bot's reaction to message + /// + /// + /// + /// + /// + /// + public static async Task React( + this IHandlerContainer container, + IEnumerable reactions, + bool isBig = false, + CancellationToken cancellationToken = default) + => await container.Client.SetMessageReaction( + container.ActualUpdate.Chat, + container.ActualUpdate.Id, + reactions, isBig, cancellationToken); + + /// + /// Sends a reply message to the current message. + /// + /// + /// The text of the message to send. + /// The parse mode for the message text. + /// The reply markup for the message. + /// Options for link preview generation. + /// The thread ID for forum topics. + /// The message entities to include. + /// Whether to disable notification for the message. + /// Whether to protect the message content. + /// The message effect ID. + /// The business connection ID. + /// Whether to allow paid broadcast. + /// + /// + /// The cancellation token. + /// The sent message. + public static async Task Reply( + this IHandlerContainer container, + string text, + ParseMode parseMode = ParseMode.None, + ReplyMarkup? replyMarkup = null, + LinkPreviewOptions? linkPreviewOptions = null, + int? messageThreadId = null, + IEnumerable? entities = null, + bool disableNotification = false, + bool protectContent = false, + string? messageEffectId = null, + string? businessConnectionId = null, + bool allowPaidBroadcast = false, + int? directMessageTopicId = null, + SuggestedPostParameters? suggestedPostParameters = null, + CancellationToken cancellationToken = default) + => await container.Client.SendMessage( + container.ActualUpdate.Chat, text, parseMode, container.ActualUpdate, + replyMarkup, linkPreviewOptions, + messageThreadId, entities, + disableNotification, protectContent, + messageEffectId, businessConnectionId, + allowPaidBroadcast, directMessageTopicId, + suggestedPostParameters, cancellationToken); + + /// + /// Sends a responce message to the current chat. + /// + /// + /// The text of the message to send. + /// The parse mode for the message text. + /// The reply parameters for the message. + /// The reply markup for the message. + /// Options for link preview generation. + /// The thread ID for forum topics. + /// The message entities to include. + /// Whether to disable notification for the message. + /// Whether to protect the message content. + /// The message effect ID. + /// The business connection ID. + /// Whether to allow paid broadcast. + /// + /// + /// The cancellation token. + /// The sent message. + public static async Task Responce( + this IHandlerContainer container, + string text, + ParseMode parseMode = ParseMode.None, + ReplyParameters? replyParameters = null, + ReplyMarkup? replyMarkup = null, + LinkPreviewOptions? linkPreviewOptions = null, + int? messageThreadId = null, + IEnumerable? entities = null, + bool disableNotification = false, + bool protectContent = false, + string? messageEffectId = null, + string? businessConnectionId = null, + bool allowPaidBroadcast = false, + int? directMessageTopicId = null, + SuggestedPostParameters? suggestedPostParameters = null, + CancellationToken cancellationToken = default) + => await container.Client.SendMessage( + container.ActualUpdate.Chat, text, parseMode, replyParameters, + replyMarkup, linkPreviewOptions, + messageThreadId, entities, + disableNotification, protectContent, + messageEffectId, businessConnectionId, + allowPaidBroadcast, directMessageTopicId, + suggestedPostParameters, cancellationToken); + + /// + /// Responces to message that this CallbackQuery was originated from + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + public static async Task Responce( + this IHandlerContainer container, + string text, + ParseMode parseMode = ParseMode.None, + ReplyParameters? replyParameters = null, + ReplyMarkup? replyMarkup = null, + LinkPreviewOptions? linkPreviewOptions = null, + int? messageThreadId = null, + IEnumerable? entities = null, + bool disableNotification = false, + bool protectContent = false, + string? messageEffectId = null, + string? businessConnectionId = null, + bool allowPaidBroadcast = false, + int? directMessageTopicId = null, + SuggestedPostParameters? suggestedPostParameters = null, + CancellationToken cancellationToken = default) { - /// - /// Changes bot's reaction to message - /// - /// - /// - /// - /// - /// - public static async Task React( - this IHandlerContainer container, - ReactionType reaction, - bool isBig = false, - CancellationToken cancellationToken = default) - => await container.Client.SetMessageReaction( - container.ActualUpdate.Chat, - container.ActualUpdate.Id, - [reaction], isBig, cancellationToken); + CallbackQuery query = container.ActualUpdate; + if (query.Message == null) + throw new Exception("Callback origin message not found!"); - /// - /// Changes bot's reaction to message - /// - /// - /// - /// - /// - /// - public static async Task React( - this IHandlerContainer container, - IEnumerable reactions, - bool isBig = false, - CancellationToken cancellationToken = default) - => await container.Client.SetMessageReaction( - container.ActualUpdate.Chat, - container.ActualUpdate.Id, - reactions, isBig, cancellationToken); + return await container.Client.SendMessage( + query.Message.Chat, text, parseMode, replyParameters, + replyMarkup, linkPreviewOptions, + messageThreadId, entities, + disableNotification, protectContent, + messageEffectId, businessConnectionId, + allowPaidBroadcast, directMessageTopicId, + suggestedPostParameters, cancellationToken); + } - /// - /// Sends a reply message to the current message. - /// - /// - /// The text of the message to send. - /// The parse mode for the message text. - /// The reply markup for the message. - /// Options for link preview generation. - /// The thread ID for forum topics. - /// The message entities to include. - /// Whether to disable notification for the message. - /// Whether to protect the message content. - /// The message effect ID. - /// The business connection ID. - /// Whether to allow paid broadcast. - /// - /// - /// The cancellation token. - /// The sent message. - public static async Task Reply( - this IHandlerContainer container, - string text, - ParseMode parseMode = ParseMode.None, - ReplyMarkup? replyMarkup = null, - LinkPreviewOptions? linkPreviewOptions = null, - int? messageThreadId = null, - IEnumerable? entities = null, - bool disableNotification = false, - bool protectContent = false, - string? messageEffectId = null, - string? businessConnectionId = null, - bool allowPaidBroadcast = false, - int? directMessageTopicId = null, - SuggestedPostParameters? suggestedPostParameters = null, - CancellationToken cancellationToken = default) - => await container.Client.SendMessage( - container.ActualUpdate.Chat, text, parseMode, container.ActualUpdate, - replyMarkup, linkPreviewOptions, - messageThreadId, entities, - disableNotification, protectContent, - messageEffectId, businessConnectionId, - allowPaidBroadcast, directMessageTopicId, - suggestedPostParameters, cancellationToken); + /// + /// Edits message text that this CallbackQuery was originated from + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + public static async Task EditMessage( + this IHandlerContainer container, + string text, + ParseMode parseMode = ParseMode.None, + InlineKeyboardMarkup? replyMarkup = null, + IEnumerable? entities = null, + LinkPreviewOptions? linkPreviewOptions = null, + CancellationToken cancellationToken = default) + { + CallbackQuery query = container.ActualUpdate; + if (query.Message == null) + throw new Exception("Callback origin message not found!"); - /// - /// Sends a responce message to the current chat. - /// - /// - /// The text of the message to send. - /// The parse mode for the message text. - /// The reply parameters for the message. - /// The reply markup for the message. - /// Options for link preview generation. - /// The thread ID for forum topics. - /// The message entities to include. - /// Whether to disable notification for the message. - /// Whether to protect the message content. - /// The message effect ID. - /// The business connection ID. - /// Whether to allow paid broadcast. - /// - /// - /// The cancellation token. - /// The sent message. - public static async Task Responce( - this IHandlerContainer container, - string text, - ParseMode parseMode = ParseMode.None, - ReplyParameters? replyParameters = null, - ReplyMarkup? replyMarkup = null, - LinkPreviewOptions? linkPreviewOptions = null, - int? messageThreadId = null, - IEnumerable? entities = null, - bool disableNotification = false, - bool protectContent = false, - string? messageEffectId = null, - string? businessConnectionId = null, - bool allowPaidBroadcast = false, - int? directMessageTopicId = null, - SuggestedPostParameters? suggestedPostParameters = null, - CancellationToken cancellationToken = default) - => await container.Client.SendMessage( - container.ActualUpdate.Chat, text, parseMode, replyParameters, - replyMarkup, linkPreviewOptions, - messageThreadId, entities, - disableNotification, protectContent, - messageEffectId, businessConnectionId, - allowPaidBroadcast, directMessageTopicId, - suggestedPostParameters, cancellationToken); + return await container.Client.EditMessageText( + query.Message.Chat, + query.Message.MessageId, + text: text, + parseMode: parseMode, + replyMarkup: replyMarkup, + entities: entities, + linkPreviewOptions: linkPreviewOptions, + cancellationToken: cancellationToken); + } - /// - /// Responces to message that this CallbackQuery was originated from - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - public static async Task Responce( - this IHandlerContainer container, - string text, - ParseMode parseMode = ParseMode.None, - ReplyParameters? replyParameters = null, - ReplyMarkup? replyMarkup = null, - LinkPreviewOptions? linkPreviewOptions = null, - int? messageThreadId = null, - IEnumerable? entities = null, - bool disableNotification = false, - bool protectContent = false, - string? messageEffectId = null, - string? businessConnectionId = null, - bool allowPaidBroadcast = false, - int? directMessageTopicId = null, - SuggestedPostParameters? suggestedPostParameters = null, - CancellationToken cancellationToken = default) - { - CallbackQuery query = container.ActualUpdate; - if (query.Message == null) - throw new Exception("Callback origin message not found!"); + /// + /// Use this method to send answers to callback queries sent from inline keyboards. + /// The answer will be displayed to the user as a notification at the top of the chat screen or as an alert + /// + /// + /// Alternatively, the user can be redirected to the specified Game URL. + /// For this option to work, you must first create a game for your bot via @BotFather and accept the terms. + /// Otherwise, you may use links like t.me/your_bot?start=XXXX that open your bot with a parameter. + /// + /// + /// + /// + /// + /// + /// + /// + public static async Task AnswerCallbackQuery( + this IHandlerContainer container, + string? text = null, + bool showAlert = false, + string? url = null, + int cacheTime = 0, + CancellationToken cancellationToken = default) + => await container.Client.AnswerCallbackQuery( + callbackQueryId: container.ActualUpdate.Id, + text: text, + showAlert: showAlert, + url: url, + cacheTime: cacheTime, + cancellationToken: cancellationToken); - return await container.Client.SendMessage( - query.Message.Chat, text, parseMode, replyParameters, - replyMarkup, linkPreviewOptions, - messageThreadId, entities, - disableNotification, protectContent, - messageEffectId, businessConnectionId, - allowPaidBroadcast, directMessageTopicId, - suggestedPostParameters, cancellationToken); - } - - /// - /// Edits message text that this CallbackQuery was originated from - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - public static async Task EditMessage( - this IHandlerContainer container, - string text, - ParseMode parseMode = ParseMode.None, - InlineKeyboardMarkup? replyMarkup = null, - IEnumerable? entities = null, - LinkPreviewOptions? linkPreviewOptions = null, - CancellationToken cancellationToken = default) - { - CallbackQuery query = container.ActualUpdate; - if (query.Message == null) - throw new Exception("Callback origin message not found!"); - - return await container.Client.EditMessageText( - query.Message.Chat, - query.Message.MessageId, - text: text, - parseMode: parseMode, - replyMarkup: replyMarkup, - entities: entities, - linkPreviewOptions: linkPreviewOptions, - cancellationToken: cancellationToken); - } - - /// - /// Use this method to send answers to callback queries sent from inline keyboards. - /// The answer will be displayed to the user as a notification at the top of the chat screen or as an alert - /// - /// - /// Alternatively, the user can be redirected to the specified Game URL. - /// For this option to work, you must first create a game for your bot via @BotFather and accept the terms. - /// Otherwise, you may use links like t.me/your_bot?start=XXXX that open your bot with a parameter. - /// - /// - /// - /// - /// - /// - /// - /// - public static async Task AnswerCallbackQuery( - this IHandlerContainer container, - string? text = null, - bool showAlert = false, - string? url = null, - int cacheTime = 0, - CancellationToken cancellationToken = default) - => await container.Client.AnswerCallbackQuery( - callbackQueryId: container.ActualUpdate.Id, - text: text, - showAlert: showAlert, - url: url, - cacheTime: cacheTime, - cancellationToken: cancellationToken); - - /// - /// Answers inline query - /// - /// - /// - /// - /// - /// - /// - /// - /// - public static async Task AnswerInlineQuery( - this IHandlerContainer container, - IEnumerable results, - int? cacheTime = null, - bool isPersonal = false, - string? nextOffset = null, - InlineQueryResultsButton? button = null, - CancellationToken cancellationToken = default) - { - string id = container.ActualUpdate.Id; - await container.Client.AnswerInlineQuery(id, results.Take(50), cacheTime, isPersonal, nextOffset, button, cancellationToken); - } + /// + /// Answers inline query + /// + /// + /// + /// + /// + /// + /// + /// + /// + public static async Task AnswerInlineQuery( + this IHandlerContainer container, + IEnumerable results, + int? cacheTime = null, + bool isPersonal = false, + string? nextOffset = null, + InlineQueryResultsButton? button = null, + CancellationToken cancellationToken = default) + { + string id = container.ActualUpdate.Id; + await container.Client.AnswerInlineQuery(id, results.Take(50), cacheTime, isPersonal, nextOffset, button, cancellationToken); } } diff --git a/src/Telegrator/ITelegratorBot.cs b/src/Telegrator/ITelegratorBot.cs index f6dd4ea..dcd754f 100644 --- a/src/Telegrator/ITelegratorBot.cs +++ b/src/Telegrator/ITelegratorBot.cs @@ -1,16 +1,15 @@ using Telegrator.Core; -namespace Telegrator +namespace Telegrator; + +/// +/// Interface for reactive Telegram bot implementations. +/// Defines the core properties and capabilities of a reactive bot. +/// +public interface ITelegratorBot { /// - /// Interface for reactive Telegram bot implementations. - /// Defines the core properties and capabilities of a reactive bot. + /// Gets the update router for handling incoming updates. /// - public interface ITelegratorBot - { - /// - /// Gets the update router for handling incoming updates. - /// - public IUpdateRouter UpdateRouter { get; } - } + public IUpdateRouter UpdateRouter { get; } } diff --git a/src/Telegrator/Logging/ConsoleLogger.cs b/src/Telegrator/Logging/ConsoleLogger.cs index dbd9aee..b7b39e2 100644 --- a/src/Telegrator/Logging/ConsoleLogger.cs +++ b/src/Telegrator/Logging/ConsoleLogger.cs @@ -1,70 +1,67 @@ -using System; +namespace Telegrator.Logging; -namespace Telegrator.Logging +/// +/// Console logger implementation that writes to System.Console. +/// This logger is optional and can be used for simple console output. +/// +public class ConsoleLogger : ITelegratorLogger { - /// - /// Console logger implementation that writes to System.Console. - /// This logger is optional and can be used for simple console output. - /// - public class ConsoleLogger : ITelegratorLogger - { - private readonly LogLevel _minimumLevel; - private readonly bool _includeTimestamp; + private readonly LogLevel _minimumLevel; + private readonly bool _includeTimestamp; - /// - /// Initializes a new instance of ConsoleLogger. - /// - /// Minimum log level to output. Default is Information. - /// Whether to include timestamp in log messages. Default is true. - public ConsoleLogger(LogLevel minimumLevel = LogLevel.Information, bool includeTimestamp = true) + /// + /// Initializes a new instance of ConsoleLogger. + /// + /// Minimum log level to output. Default is Information. + /// Whether to include timestamp in log messages. Default is true. + public ConsoleLogger(LogLevel minimumLevel = LogLevel.Information, bool includeTimestamp = true) + { + _minimumLevel = minimumLevel; + _includeTimestamp = includeTimestamp; + } + + /// + public void Log(LogLevel level, string message, Exception? exception = null) + { + if (level < _minimumLevel) + return; + + var timestamp = _includeTimestamp ? $"[{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff}] " : ""; + var levelStr = $"[{level.ToString().ToUpper()}] "; + var logMessage = $"{timestamp}{levelStr}{message}"; + + // Add exception if present + if (exception != null) { - _minimumLevel = minimumLevel; - _includeTimestamp = includeTimestamp; + logMessage += $" | Exception: {exception.Message}"; } - /// - public void Log(LogLevel level, string message, Exception? exception = null) + // Write to console with appropriate color + var originalColor = Console.ForegroundColor; + try { - if (level < _minimumLevel) - return; + Console.ForegroundColor = level switch + { + LogLevel.Trace => ConsoleColor.Gray, + LogLevel.Debug => ConsoleColor.Cyan, + LogLevel.Information => ConsoleColor.White, + LogLevel.Warning => ConsoleColor.Yellow, + LogLevel.Error => ConsoleColor.Red, + _ => ConsoleColor.White + }; - var timestamp = _includeTimestamp ? $"[{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff}] " : ""; - var levelStr = $"[{level.ToString().ToUpper()}] "; - var logMessage = $"{timestamp}{levelStr}{message}"; + Console.WriteLine(logMessage); - // Add exception if present + // Write exception details if present if (exception != null) { - logMessage += $" | Exception: {exception.Message}"; - } - - // Write to console with appropriate color - var originalColor = Console.ForegroundColor; - try - { - Console.ForegroundColor = level switch - { - LogLevel.Trace => ConsoleColor.Gray, - LogLevel.Debug => ConsoleColor.Cyan, - LogLevel.Information => ConsoleColor.White, - LogLevel.Warning => ConsoleColor.Yellow, - LogLevel.Error => ConsoleColor.Red, - _ => ConsoleColor.White - }; - - Console.WriteLine(logMessage); - - // Write exception details if present - if (exception != null) - { - Console.ForegroundColor = ConsoleColor.Red; - Console.WriteLine($"Exception Details: {exception}"); - } - } - finally - { - Console.ForegroundColor = originalColor; + Console.ForegroundColor = ConsoleColor.Red; + Console.WriteLine($"Exception Details: {exception}"); } } + finally + { + Console.ForegroundColor = originalColor; + } } -} \ No newline at end of file +} diff --git a/src/Telegrator/Logging/ITelegratorLogger.cs b/src/Telegrator/Logging/ITelegratorLogger.cs index f1b9b6d..8d8da1c 100644 --- a/src/Telegrator/Logging/ITelegratorLogger.cs +++ b/src/Telegrator/Logging/ITelegratorLogger.cs @@ -1,50 +1,47 @@ -using System; +namespace Telegrator.Logging; -namespace Telegrator.Logging +/// +/// Interface for Telegrator logging system. +/// Provides abstraction for logging without external dependencies. +/// +public interface ITelegratorLogger { /// - /// Interface for Telegrator logging system. - /// Provides abstraction for logging without external dependencies. + /// Logs a message with specified level. /// - public interface ITelegratorLogger - { - /// - /// Logs a message with specified level. - /// - /// The log level. - /// The message to log. - /// Optional exception. - void Log(LogLevel level, string message, Exception? exception = null); - } + /// The log level. + /// The message to log. + /// Optional exception. + void Log(LogLevel level, string message, Exception? exception = null); +} + +/// +/// Log levels for Telegrator logging system. +/// +public enum LogLevel +{ + /// + /// Trace level - most detailed logging. + /// + Trace = 0, /// - /// Log levels for Telegrator logging system. + /// Debug level - detailed debugging information. /// - public enum LogLevel - { - /// - /// Trace level - most detailed logging. - /// - Trace = 0, + Debug = 1, - /// - /// Debug level - detailed debugging information. - /// - Debug = 1, + /// + /// Information level - general information. + /// + Information = 2, - /// - /// Information level - general information. - /// - Information = 2, + /// + /// Warning level - warning messages. + /// + Warning = 3, - /// - /// Warning level - warning messages. - /// - Warning = 3, - - /// - /// Error level - error messages. - /// - Error = 4 - } -} \ No newline at end of file + /// + /// Error level - error messages. + /// + Error = 4 +} diff --git a/src/Telegrator/Logging/NullLogger.cs b/src/Telegrator/Logging/NullLogger.cs index 28c67e4..ac8294e 100644 --- a/src/Telegrator/Logging/NullLogger.cs +++ b/src/Telegrator/Logging/NullLogger.cs @@ -1,21 +1,18 @@ -using System; +namespace Telegrator.Logging; -namespace Telegrator.Logging +/// +/// Null logger implementation that does nothing. +/// Used when logging is not required or disabled. +/// +public class NullLogger : ITelegratorLogger { /// - /// Null logger implementation that does nothing. - /// Used when logging is not required or disabled. + /// Singleton instance of NullLogger. /// - public class NullLogger : ITelegratorLogger - { - /// - /// Singleton instance of NullLogger. - /// - public static readonly NullLogger Instance = new(); + public static readonly NullLogger Instance = new(); - private NullLogger() { } + private NullLogger() { } - /// - public void Log(LogLevel level, string message, Exception? exception = null) { } - } -} \ No newline at end of file + /// + public void Log(LogLevel level, string message, Exception? exception = null) { } +} diff --git a/src/Telegrator/Logging/TelegratorLogging.cs b/src/Telegrator/Logging/TelegratorLogging.cs index 844b4c9..77af8d9 100644 --- a/src/Telegrator/Logging/TelegratorLogging.cs +++ b/src/Telegrator/Logging/TelegratorLogging.cs @@ -1,218 +1,215 @@ -using System.Security.Cryptography.X509Certificates; +namespace Telegrator.Logging; -namespace Telegrator.Logging +/// +/// Centralized logging system for Telegrator. +/// Provides static access to logging functionality with adapter support. +/// +public static class TelegratorLogging { + private static readonly List _adapters = new(); + private static readonly object _lock = new(); + /// - /// Centralized logging system for Telegrator. - /// Provides static access to logging functionality with adapter support. + /// Gets the current adapters count. /// - public static class TelegratorLogging + public static int AdaptersCount => _adapters.Count; + + /// + /// Minimal level of logging messages. + /// Any messages below thi value will not be writen! + /// + public static LogLevel MinimalLevel { get; set; } = LogLevel.Information; + + /// + /// Adds a logger adapter to the centralized logging system. + /// + /// The logger adapter to add. + public static void AddAdapter(ITelegratorLogger adapter) { - private static readonly List _adapters = new(); - private static readonly object _lock = new(); + if (adapter == null) + throw new ArgumentNullException(nameof(adapter)); - /// - /// Gets the current adapters count. - /// - public static int AdaptersCount => _adapters.Count; - - /// - /// Minimal level of logging messages. - /// Any messages below thi value will not be writen! - /// - public static LogLevel MinimalLevel { get; set; } = LogLevel.Information; - - /// - /// Adds a logger adapter to the centralized logging system. - /// - /// The logger adapter to add. - public static void AddAdapter(ITelegratorLogger adapter) + lock (_lock) { - if (adapter == null) - throw new ArgumentNullException(nameof(adapter)); - - lock (_lock) + if (!_adapters.Contains(adapter)) { - if (!_adapters.Contains(adapter)) - { - _adapters.Add(adapter); - } + _adapters.Add(adapter); } } - - /// - /// Removes a logger adapter from the centralized logging system. - /// - /// The logger adapter to remove. - public static void RemoveAdapter(ITelegratorLogger adapter) - { - if (adapter == null) - return; - - lock (_lock) - { - _adapters.Remove(adapter); - } - } - - /// - /// Clears all logger adapters. - /// - public static void ClearAdapters() - { - lock (_lock) - { - _adapters.Clear(); - } - } - - /// - /// Logs a message to all registered adapters. - /// - /// The log level. - /// The message to log. - /// Optional exception. - /// - public static void Log(LogLevel level, string message, Exception? exception = null, params object[] args) - { - // Fast path: if no adapters, do nothing - if (_adapters.Count == 0) - return; - - if (level != LogLevel.Trace & level < MinimalLevel) - return; - - // Lock only during enumeration to prevent collection modification during iteration - foreach (var adapter in _adapters) - { - try - { - if (args != null) - message = string.Format(message, args); - - adapter.Log(level, message, exception); - } - catch - { - _ = 0xBAD + 0xC0DE; // Ignore adapter errors to prevent logging failures - } - } - } - - /// - /// Logs a trace message to all registered adapters. - /// - /// The message to log. - public static void LogTrace(string message) - { - Log(LogLevel.Trace, message); - } - - /// - /// Logs a trace message to all registered adapters. - /// - /// The message to log. - /// - public static void LogTrace(string message, params object[] args) - { - Log(LogLevel.Trace, message, args: args); - } - - /// - /// Logs a debug message to all registered adapters. - /// - /// The message to log. - public static void LogDebug(string message) - { - Log(LogLevel.Debug, message); - } - - /// - /// Logs a debug message to all registered adapters. - /// - /// The message to log. - /// - public static void LogDebug(string message, params object[] args) - { - Log(LogLevel.Debug, message, args: args); - } - - /// - /// Logs an information message to all registered adapters. - /// - /// The message to log. - public static void LogInformation(string message) - { - Log(LogLevel.Information, message); - } - - /// - /// Logs an information message to all registered adapters. - /// - /// The message to log. - /// - public static void LogInformation(string message, params object[] args) - { - Log(LogLevel.Information, message, args: args); - } - - /// - /// Logs a warning message to all registered adapters. - /// - /// The message to log. - public static void LogWarning(string message) - { - Log(LogLevel.Warning, message); - } - - /// - /// Logs a warning message to all registered adapters. - /// - /// The message to log. - /// - public static void LogWarning(string message, params object[] args) - { - Log(LogLevel.Warning, message, args: args); - } - - /// - /// Logs an error message to all registered adapters. - /// - /// The message to log. - /// Optional exception. - public static void LogError(string message, Exception? exception = null) - { - Log(LogLevel.Error, message, exception); - } - - /// - /// Logs an error message to all registered adapters. - /// - /// The message to log. - /// - public static void LogError(string message, params object[] args) - { - Log(LogLevel.Error, message, args: args); - } - - /// - /// Logs an error message with exception only to all registered adapters. - /// - /// The exception to log. - public static void LogError(Exception exception) - { - Log(LogLevel.Error, exception.Message, exception); - } - - /// - /// Logs an error message to all registered adapters. - /// - /// The message to log. - /// Optional exception. - /// - public static void LogError(string message, Exception? exception = null, params object[] args) - { - Log(LogLevel.Error, message, exception, args); - } } -} \ No newline at end of file + + /// + /// Removes a logger adapter from the centralized logging system. + /// + /// The logger adapter to remove. + public static void RemoveAdapter(ITelegratorLogger adapter) + { + if (adapter == null) + return; + + lock (_lock) + { + _adapters.Remove(adapter); + } + } + + /// + /// Clears all logger adapters. + /// + public static void ClearAdapters() + { + lock (_lock) + { + _adapters.Clear(); + } + } + + /// + /// Logs a message to all registered adapters. + /// + /// The log level. + /// The message to log. + /// Optional exception. + /// + public static void Log(LogLevel level, string message, Exception? exception = null, params object[] args) + { + // Fast path: if no adapters, do nothing + if (_adapters.Count == 0) + return; + + if (level != LogLevel.Trace & level < MinimalLevel) + return; + + // Lock only during enumeration to prevent collection modification during iteration + foreach (var adapter in _adapters) + { + try + { + if (args != null) + message = string.Format(message, args); + + adapter.Log(level, message, exception); + } + catch + { + _ = 0xBAD + 0xC0DE; // Ignore adapter errors to prevent logging failures + } + } + } + + /// + /// Logs a trace message to all registered adapters. + /// + /// The message to log. + public static void LogTrace(string message) + { + Log(LogLevel.Trace, message); + } + + /// + /// Logs a trace message to all registered adapters. + /// + /// The message to log. + /// + public static void LogTrace(string message, params object[] args) + { + Log(LogLevel.Trace, message, args: args); + } + + /// + /// Logs a debug message to all registered adapters. + /// + /// The message to log. + public static void LogDebug(string message) + { + Log(LogLevel.Debug, message); + } + + /// + /// Logs a debug message to all registered adapters. + /// + /// The message to log. + /// + public static void LogDebug(string message, params object[] args) + { + Log(LogLevel.Debug, message, args: args); + } + + /// + /// Logs an information message to all registered adapters. + /// + /// The message to log. + public static void LogInformation(string message) + { + Log(LogLevel.Information, message); + } + + /// + /// Logs an information message to all registered adapters. + /// + /// The message to log. + /// + public static void LogInformation(string message, params object[] args) + { + Log(LogLevel.Information, message, args: args); + } + + /// + /// Logs a warning message to all registered adapters. + /// + /// The message to log. + public static void LogWarning(string message) + { + Log(LogLevel.Warning, message); + } + + /// + /// Logs a warning message to all registered adapters. + /// + /// The message to log. + /// + public static void LogWarning(string message, params object[] args) + { + Log(LogLevel.Warning, message, args: args); + } + + /// + /// Logs an error message to all registered adapters. + /// + /// The message to log. + /// Optional exception. + public static void LogError(string message, Exception? exception = null) + { + Log(LogLevel.Error, message, exception); + } + + /// + /// Logs an error message to all registered adapters. + /// + /// The message to log. + /// + public static void LogError(string message, params object[] args) + { + Log(LogLevel.Error, message, args: args); + } + + /// + /// Logs an error message with exception only to all registered adapters. + /// + /// The exception to log. + public static void LogError(Exception exception) + { + Log(LogLevel.Error, exception.Message, exception); + } + + /// + /// Logs an error message to all registered adapters. + /// + /// The message to log. + /// Optional exception. + /// + public static void LogError(string message, Exception? exception = null, params object[] args) + { + Log(LogLevel.Error, message, exception, args); + } +} diff --git a/src/Telegrator/Markups/KeyboardMapper.cs b/src/Telegrator/Markups/KeyboardMapper.cs index 33a6442..60f4e3d 100644 --- a/src/Telegrator/Markups/KeyboardMapper.cs +++ b/src/Telegrator/Markups/KeyboardMapper.cs @@ -1,20 +1,19 @@ using System.Reflection; using Telegram.Bot.Types.ReplyMarkups; -namespace Telegrator.Markups +namespace Telegrator.Markups; + +/* +internal static class KeyboardMapper { - /* - internal static class KeyboardMapper + public static InlineKeyboardMarkup MapInline(MemberInfo member) + { + + } + + public static ReplyKeyboardMarkup MapReply() { - public static InlineKeyboardMarkup MapInline(MemberInfo member) - { - } - - public static ReplyKeyboardMarkup MapReply() - { - - } } - */ } +*/ diff --git a/src/Telegrator/Markups/KeyboardMarkupButtonAttributes.cs b/src/Telegrator/Markups/KeyboardMarkupButtonAttributes.cs index d097b22..aa03d2e 100644 --- a/src/Telegrator/Markups/KeyboardMarkupButtonAttributes.cs +++ b/src/Telegrator/Markups/KeyboardMarkupButtonAttributes.cs @@ -2,205 +2,204 @@ using Telegram.Bot.Types; using Telegram.Bot.Types.ReplyMarkups; -namespace Telegrator.Markups +namespace Telegrator.Markups; + +/// +[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] +public sealed class CallbackButtonAttribute(string name, string data) : Attribute { - /// - [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] - public sealed class CallbackButtonAttribute(string name, string data) : Attribute - { - /// - /// Name of button - /// - public string Name { get; } = name; + /// + /// Name of button + /// + public string Name { get; } = name; - /// - /// Data to be sent in a callback query to the bot when the button is pressed, 1-64 bytes - /// - public string Data { get; } = data; - } - - /// - [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] - public sealed class GameButtonAttribute(string name) : Attribute - { - /// - /// Name of button - /// - public string Name { get; } = name; - } - - /// - [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] - public sealed class CopyTextButtonAttribute(string name, CopyTextButton copyText) : Attribute - { - /// - /// Name of button - /// - public string Name { get; } = name; - - /// - /// Description of the button that copies the specified text to the clipboard. - /// - public CopyTextButton CopyText { get; } = copyText; - } - - /// - [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] - public sealed class PayRequestButtonAttribute(string name) : Attribute - { - /// - /// Name of button - /// - public string Name { get; } = name; - } - - /// - [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] - public sealed class LoginButtonAttribute(string name, LoginUrl url) : Attribute - { - /// - /// Name of button - /// - public string Name { get; } = name; - - /// - /// An HTTPS URL used to automatically authorize the user. Can be used as a replacement for the Telegram Login Widget. - /// - public LoginUrl Url { get; } = url; - } - - /// - [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] - public sealed class UrlRedirectButtonAttribute(string name, string url) : Attribute - { - /// - /// Name of button - /// - public string Name { get; } = name; - - /// - /// HTTP or tg:// URL to be opened when the button is pressed. Links tg://user?id=<UserId> can be used to mention a user by their identifier without using a username, if this is allowed by their privacy settings. - /// - public string Url { get; } = url; - } - - /// - [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] - public sealed class WebAppButtonAttribute(string name, WebAppInfo webApp) : Attribute - { - /// - /// Name of button - /// - public string Name { get; } = name; - - /// - /// Description of the Web App that will be launched when the user presses the button. The Web App will be able to send an arbitrary message on behalf of the user using the method AnswerWebAppQuery. Available only in private chats between a user and the bot. Not supported for messages sent on behalf of a Telegram Business account. - /// - public WebAppInfo AppInfo { get; } = webApp; - } - - /// - [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] - public sealed class SwitchQueryButtonAttribute(string name, string switchInlineQuery = "") : Attribute - { - /// - /// Name of button - /// - public string Name { get; } = name; - - /// - /// If set, pressing the button will prompt the user to select one of their chats, open that chat and insert the bot's username and the specified inline query in the input field. May be empty, in which case just the bot's username will be inserted. Not supported for messages sent in channel direct messages chats and on behalf of a Telegram Business account. - /// - public string Query { get; } = switchInlineQuery; - } - - /// - [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] - public sealed class QueryCurrentButtonAttribute(string name, string switchInlineQueryCurrentChat = "") : Attribute - { - /// - /// Name of button - /// - public string Name { get; } = name; - - /// - /// If set, pressing the button will insert the bot's username and the specified inline query in the current chat's input field. May be empty, in which case only the bot's username will be inserted.

This offers a quick way for the user to open your bot in inline mode in the same chat - good for selecting something from multiple options. Not supported in channels and for messages sent in channel direct messages chats and on behalf of a Telegram Business account. - ///
- public string Query { get; } = switchInlineQueryCurrentChat; - } - - /// - [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] - public class RequestChatButtonAttribute(string name, bool chatIsChannel = true) : Attribute - { - /// - /// Name of button - /// - public string Name { get; } = name; - - /// - /// Signed 32-bit identifier of the request that will be received back in the object. Must be unique within the message - /// - public int RequestId { get; } = new Random().Next(); - - /// - /// Pass to request a channel chat, pass to request a group or a supergroup chat. - /// - public bool ChatIsChannel { get; } = chatIsChannel; - } - - /// - [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] - public class RequestContactButtonAttribute(string name) : Attribute - { - /// - /// Name of button - /// - public string Name { get; } = name; - } - - /// - [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] - public class RequestLocationButtonAttribute(string name) : Attribute - { - /// - /// Name of button - /// - public string Name { get; } = name; - } - - /// - [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] - public class RequestPoolButtonAttribute(string name, KeyboardButtonPollType requestPoll) : Attribute - { - /// - /// Name of button - /// - public string Name { get; } = name; - - /// - /// If specified, the user will be asked to create a poll and send it to the bot when the button is pressed. Available in private chats only. - /// - public KeyboardButtonPollType PollType { get; } = requestPoll; - } - - /// - [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] - public class RequestUsersButtonAttribute(string name, int? maxQuantity = null) : Attribute - { - /// - /// Name of button - /// - public string Name { get; } = name; - - /// - /// Signed 32-bit identifier of the request that will be received back in the object. Must be unique within the message - /// - public int RequestId { get; } = new Random().Next(); - - /// - /// Optional. The maximum number of users to be selected; 1-10. Defaults to 1. - /// - public int? MaxQuantity { get; } = maxQuantity; - } + /// + /// Data to be sent in a callback query to the bot when the button is pressed, 1-64 bytes + /// + public string Data { get; } = data; +} + +/// +[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] +public sealed class GameButtonAttribute(string name) : Attribute +{ + /// + /// Name of button + /// + public string Name { get; } = name; +} + +/// +[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] +public sealed class CopyTextButtonAttribute(string name, CopyTextButton copyText) : Attribute +{ + /// + /// Name of button + /// + public string Name { get; } = name; + + /// + /// Description of the button that copies the specified text to the clipboard. + /// + public CopyTextButton CopyText { get; } = copyText; +} + +/// +[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] +public sealed class PayRequestButtonAttribute(string name) : Attribute +{ + /// + /// Name of button + /// + public string Name { get; } = name; +} + +/// +[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] +public sealed class LoginButtonAttribute(string name, LoginUrl url) : Attribute +{ + /// + /// Name of button + /// + public string Name { get; } = name; + + /// + /// An HTTPS URL used to automatically authorize the user. Can be used as a replacement for the Telegram Login Widget. + /// + public LoginUrl Url { get; } = url; +} + +/// +[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] +public sealed class UrlRedirectButtonAttribute(string name, string url) : Attribute +{ + /// + /// Name of button + /// + public string Name { get; } = name; + + /// + /// HTTP or tg:// URL to be opened when the button is pressed. Links tg://user?id=<UserId> can be used to mention a user by their identifier without using a username, if this is allowed by their privacy settings. + /// + public string Url { get; } = url; +} + +/// +[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] +public sealed class WebAppButtonAttribute(string name, WebAppInfo webApp) : Attribute +{ + /// + /// Name of button + /// + public string Name { get; } = name; + + /// + /// Description of the Web App that will be launched when the user presses the button. The Web App will be able to send an arbitrary message on behalf of the user using the method AnswerWebAppQuery. Available only in private chats between a user and the bot. Not supported for messages sent on behalf of a Telegram Business account. + /// + public WebAppInfo AppInfo { get; } = webApp; +} + +/// +[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] +public sealed class SwitchQueryButtonAttribute(string name, string switchInlineQuery = "") : Attribute +{ + /// + /// Name of button + /// + public string Name { get; } = name; + + /// + /// If set, pressing the button will prompt the user to select one of their chats, open that chat and insert the bot's username and the specified inline query in the input field. May be empty, in which case just the bot's username will be inserted. Not supported for messages sent in channel direct messages chats and on behalf of a Telegram Business account. + /// + public string Query { get; } = switchInlineQuery; +} + +/// +[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] +public sealed class QueryCurrentButtonAttribute(string name, string switchInlineQueryCurrentChat = "") : Attribute +{ + /// + /// Name of button + /// + public string Name { get; } = name; + + /// + /// If set, pressing the button will insert the bot's username and the specified inline query in the current chat's input field. May be empty, in which case only the bot's username will be inserted.

This offers a quick way for the user to open your bot in inline mode in the same chat - good for selecting something from multiple options. Not supported in channels and for messages sent in channel direct messages chats and on behalf of a Telegram Business account. + ///
+ public string Query { get; } = switchInlineQueryCurrentChat; +} + +/// +[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] +public class RequestChatButtonAttribute(string name, bool chatIsChannel = true) : Attribute +{ + /// + /// Name of button + /// + public string Name { get; } = name; + + /// + /// Signed 32-bit identifier of the request that will be received back in the object. Must be unique within the message + /// + public int RequestId { get; } = new Random().Next(); + + /// + /// Pass to request a channel chat, pass to request a group or a supergroup chat. + /// + public bool ChatIsChannel { get; } = chatIsChannel; +} + +/// +[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] +public class RequestContactButtonAttribute(string name) : Attribute +{ + /// + /// Name of button + /// + public string Name { get; } = name; +} + +/// +[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] +public class RequestLocationButtonAttribute(string name) : Attribute +{ + /// + /// Name of button + /// + public string Name { get; } = name; +} + +/// +[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] +public class RequestPoolButtonAttribute(string name, KeyboardButtonPollType requestPoll) : Attribute +{ + /// + /// Name of button + /// + public string Name { get; } = name; + + /// + /// If specified, the user will be asked to create a poll and send it to the bot when the button is pressed. Available in private chats only. + /// + public KeyboardButtonPollType PollType { get; } = requestPoll; +} + +/// +[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] +public class RequestUsersButtonAttribute(string name, int? maxQuantity = null) : Attribute +{ + /// + /// Name of button + /// + public string Name { get; } = name; + + /// + /// Signed 32-bit identifier of the request that will be received back in the object. Must be unique within the message + /// + public int RequestId { get; } = new Random().Next(); + + /// + /// Optional. The maximum number of users to be selected; 1-10. Defaults to 1. + /// + public int? MaxQuantity { get; } = maxQuantity; } diff --git a/src/Telegrator/Mediation/DefaultRouterExceptionHandler.cs b/src/Telegrator/Mediation/DefaultRouterExceptionHandler.cs index 4d1d488..94b49c4 100644 --- a/src/Telegrator/Mediation/DefaultRouterExceptionHandler.cs +++ b/src/Telegrator/Mediation/DefaultRouterExceptionHandler.cs @@ -2,35 +2,35 @@ using Telegram.Bot.Polling; using Telegrator.Core; -namespace Telegrator.Mediation +namespace Telegrator.Mediation; + +/// +/// Delegate used to handle exception +/// +/// +/// +/// +/// +public delegate void RouterExceptionHandler(ITelegramBotClient botClient, Exception exception, HandleErrorSource source, CancellationToken cancellationToken); + +/// +/// Realizes using function delegate +/// +/// +public sealed class DefaultRouterExceptionHandler(RouterExceptionHandler handler) : IRouterExceptionHandler { - /// - /// Delegate used to handle exception - /// - /// - /// - /// - /// - public delegate void RouterExceptionHandler(ITelegramBotClient botClient, Exception exception, HandleErrorSource source, CancellationToken cancellationToken); + private readonly RouterExceptionHandler _handler = handler; - /// - /// Realizes using function delegate - /// - /// - public sealed class DefaultRouterExceptionHandler(RouterExceptionHandler handler) : IRouterExceptionHandler + /// + public void HandleException(ITelegramBotClient botClient, Exception exception, HandleErrorSource source, CancellationToken cancellationToken) { - private readonly RouterExceptionHandler _handler = handler; - - /// - public void HandleException(ITelegramBotClient botClient, Exception exception, HandleErrorSource source, CancellationToken cancellationToken) + try { - try - { - _handler.Invoke(botClient, exception, source, cancellationToken); - } - catch - { - _ = 0xBAD + 0xC0DE; - } - }} + _handler.Invoke(botClient, exception, source, cancellationToken); + } + catch + { + _ = 0xBAD + 0xC0DE; + } + } } diff --git a/src/Telegrator/Mediation/DefaultUpdateReceiver.cs b/src/Telegrator/Mediation/DefaultUpdateReceiver.cs index f216d95..625c63c 100644 --- a/src/Telegrator/Mediation/DefaultUpdateReceiver.cs +++ b/src/Telegrator/Mediation/DefaultUpdateReceiver.cs @@ -4,82 +4,81 @@ using Telegram.Bot.Requests; using Telegram.Bot.Types; using Telegrator.Core; -namespace Telegrator.Mediation +namespace Telegrator.Mediation; + +/// +/// Reactive implementation of for polling updates from Telegram. +/// Provides custom update receiving logic with error handling and configuration options. +/// +/// The Telegram bot client for making API requests. +/// Optional receiver options for configuring update polling behavior. +public class DefaultUpdateReceiver(ITelegramBotClient client, ReceiverOptions? options) : IUpdateReceiver { /// - /// Reactive implementation of for polling updates from Telegram. - /// Provides custom update receiving logic with error handling and configuration options. + /// Gets the receiver options for configuring update polling behavior. /// - /// The Telegram bot client for making API requests. - /// Optional receiver options for configuring update polling behavior. - public class DefaultUpdateReceiver(ITelegramBotClient client, ReceiverOptions? options) : IUpdateReceiver + public readonly ReceiverOptions? Options = options; + + /// + /// Gets the Telegram bot client for making API requests. + /// + public readonly ITelegramBotClient Client = client; + + /// + /// Receives updates from Telegram using long polling. + /// Handles update processing, error handling, and cancellation. + /// + /// The update handler to process received updates. + /// The cancellation token to stop receiving updates. + /// A task representing the asynchronous update receiving operation. + public async Task ReceiveAsync(IUpdateHandler updateHandler, CancellationToken cancellationToken) { - /// - /// Gets the receiver options for configuring update polling behavior. - /// - public readonly ReceiverOptions? Options = options; - - /// - /// Gets the Telegram bot client for making API requests. - /// - public readonly ITelegramBotClient Client = client; - - /// - /// Receives updates from Telegram using long polling. - /// Handles update processing, error handling, and cancellation. - /// - /// The update handler to process received updates. - /// The cancellation token to stop receiving updates. - /// A task representing the asynchronous update receiving operation. - public async Task ReceiveAsync(IUpdateHandler updateHandler, CancellationToken cancellationToken) + cancellationToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken).Token; + GetUpdatesRequest request = new GetUpdatesRequest() { - cancellationToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken).Token; - GetUpdatesRequest request = new GetUpdatesRequest() - { - AllowedUpdates = Options?.AllowedUpdates ?? [], - Limit = Options?.Limit.GetValueOrDefault(100), - Offset = Options?.Offset - }; + AllowedUpdates = Options?.AllowedUpdates ?? [], + Limit = Options?.Limit.GetValueOrDefault(100), + Offset = Options?.Offset + }; - if (Options?.DropPendingUpdates ?? false) + if (Options?.DropPendingUpdates ?? false) + { + try { - try - { - Update[] array = await Client.GetUpdates(-1, 1, 0, [], cancellationToken).ConfigureAwait(false); - request.Offset = array.Length != 0 ? array[^1].Id + 1 : 0; - } - catch (OperationCanceledException) - { - return; - } + Update[] array = await Client.GetUpdates(-1, 1, 0, [], cancellationToken).ConfigureAwait(false); + request.Offset = array.Length != 0 ? array[^1].Id + 1 : 0; } - - while (!cancellationToken.IsCancellationRequested) + catch (OperationCanceledException) { - try + return; + } + } + + while (!cancellationToken.IsCancellationRequested) + { + try + { + request.Timeout = (int)Client.Timeout.TotalSeconds; + foreach (Update update in await Client.SendRequest(request, cancellationToken).ConfigureAwait(false)) { - request.Timeout = (int)Client.Timeout.TotalSeconds; - foreach (Update update in await Client.SendRequest(request, cancellationToken).ConfigureAwait(false)) + try { - try - { - request.Offset = update.Id + 1; - await updateHandler.HandleUpdateAsync(Client, update, cancellationToken).ConfigureAwait(continueOnCapturedContext: false); - } - catch (Exception exception2) - { - await updateHandler.HandleErrorAsync(Client, exception2, HandleErrorSource.HandleUpdateError, cancellationToken).ConfigureAwait(false); - } + request.Offset = update.Id + 1; + await updateHandler.HandleUpdateAsync(Client, update, cancellationToken).ConfigureAwait(continueOnCapturedContext: false); + } + catch (Exception exception2) + { + await updateHandler.HandleErrorAsync(Client, exception2, HandleErrorSource.HandleUpdateError, cancellationToken).ConfigureAwait(false); } } - catch (OperationCanceledException) - { - return; - } - catch (Exception exception) - { - await updateHandler.HandleErrorAsync(Client, exception, HandleErrorSource.PollingError, cancellationToken).ConfigureAwait(false); - } + } + catch (OperationCanceledException) + { + return; + } + catch (Exception exception) + { + await updateHandler.HandleErrorAsync(Client, exception, HandleErrorSource.PollingError, cancellationToken).ConfigureAwait(false); } } } diff --git a/src/Telegrator/Mediation/UpdateHandlersPool.cs b/src/Telegrator/Mediation/UpdateHandlersPool.cs index dfab263..8c50a14 100644 --- a/src/Telegrator/Mediation/UpdateHandlersPool.cs +++ b/src/Telegrator/Mediation/UpdateHandlersPool.cs @@ -1,176 +1,174 @@ -using System.Security.Authentication.ExtendedProtection; -using System.Threading.Channels; +using System.Threading.Channels; using Telegrator.Core; using Telegrator.Core.Descriptors; using Telegrator.Core.Handlers; using Telegrator.Logging; -namespace Telegrator.Mediation +namespace Telegrator.Mediation; + +/// +/// Implementation of that manages the execution of handlers. +/// Provides thread-safe queuing and execution of handlers with configurable concurrency limits. +/// +public class UpdateHandlersPool : IUpdateHandlersPool { /// - /// Implementation of that manages the execution of handlers. - /// Provides thread-safe queuing and execution of handlers with configurable concurrency limits. + /// Synchronization object for thread-safe operations. /// - public class UpdateHandlersPool : IUpdateHandlersPool + protected readonly object SyncObj = new object(); + + /// + /// The task responsible for reading and processing handlers from the channel. + /// + protected readonly Task ChannelReaderTask; + + /// + /// The channel used to queue handlers for execution. + /// + protected readonly Channel ExecutionChannel; + + /// + /// Semaphore for controlling the number of concurrently executing handlers. + /// + protected readonly SemaphoreSlim? ExecutionLimiter; + + /// + /// The update router associated with this pool. + /// + protected readonly IUpdateRouter UpdateRouter; + + /// + /// The bot configuration options. + /// + protected readonly TelegratorOptions Options; + + /// + /// The global cancellation token for stopping all operations. + /// + protected readonly CancellationToken GlobalCancellationToken; + + /// + /// Flag indicating whether the pool has been disposed. + /// + protected bool disposed = false; + + /// + public event HandlerEnqueued? HandlerEnqueued; + + /// + public event HandlerExecuting? HandlerExecuting; + + /// + /// Initializes a new instance of the class. + /// + /// The update handler that claims updates + /// The bot configuration options. + /// The global cancellation token. + public UpdateHandlersPool(IUpdateRouter router, TelegratorOptions options, CancellationToken globalCancellationToken) { - /// - /// Synchronization object for thread-safe operations. - /// - protected readonly object SyncObj = new object(); - - /// - /// The task responsible for reading and processing handlers from the channel. - /// - protected readonly Task ChannelReaderTask; - - /// - /// The channel used to queue handlers for execution. - /// - protected readonly Channel ExecutionChannel; - - /// - /// Semaphore for controlling the number of concurrently executing handlers. - /// - protected readonly SemaphoreSlim? ExecutionLimiter; - - /// - /// The update router associated with this pool. - /// - protected readonly IUpdateRouter UpdateRouter; - - /// - /// The bot configuration options. - /// - protected readonly TelegratorOptions Options; - - /// - /// The global cancellation token for stopping all operations. - /// - protected readonly CancellationToken GlobalCancellationToken; - - /// - /// Flag indicating whether the pool has been disposed. - /// - protected bool disposed = false; - - /// - public event HandlerEnqueued? HandlerEnqueued; - - /// - public event HandlerExecuting? HandlerExecuting; - - /// - /// Initializes a new instance of the class. - /// - /// The update handler that claims updates - /// The bot configuration options. - /// The global cancellation token. - public UpdateHandlersPool(IUpdateRouter router, TelegratorOptions options, CancellationToken globalCancellationToken) + UpdateRouter = router; + Options = options; + GlobalCancellationToken = globalCancellationToken; + + ExecutionChannel = Channel.CreateUnbounded(new UnboundedChannelOptions() { - UpdateRouter = router; - Options = options; - GlobalCancellationToken = globalCancellationToken; - - ExecutionChannel = Channel.CreateUnbounded(new UnboundedChannelOptions() - { - SingleReader = true, - SingleWriter = true, - AllowSynchronousContinuations = false - }); + SingleReader = true, + SingleWriter = true, + AllowSynchronousContinuations = false + }); - if (options.MaximumParallelWorkingHandlers != null) - ExecutionLimiter = new SemaphoreSlim(options.MaximumParallelWorkingHandlers.Value); + if (options.MaximumParallelWorkingHandlers != null) + ExecutionLimiter = new SemaphoreSlim(options.MaximumParallelWorkingHandlers.Value); - GlobalCancellationToken.Register(() => ExecutionChannel.Writer.Complete()); - ChannelReaderTask = ReadChannel(); - } + GlobalCancellationToken.Register(() => ExecutionChannel.Writer.Complete()); + ChannelReaderTask = ReadChannel(); + } - /// - public async Task Enqueue(params IEnumerable handlers) + /// + public async Task Enqueue(params IEnumerable handlers) + { + try { - try + foreach (DescribedHandlerDescriptor handlerInfo in handlers) { - foreach (DescribedHandlerDescriptor handlerInfo in handlers) - { - if (handlerInfo.UpdateRouter != UpdateRouter) - throw new InvalidOperationException("Tried to enqueue update handler info from other router."); + if (handlerInfo.UpdateRouter != UpdateRouter) + throw new InvalidOperationException("Tried to enqueue update handler info from other router."); - await ExecutionChannel.Writer.WriteAsync(handlerInfo, GlobalCancellationToken); - } - } - catch (OperationCanceledException) - { - _ = 0xDEADBEEF; + await ExecutionChannel.Writer.WriteAsync(handlerInfo, GlobalCancellationToken); } } - - private async Task ReadChannel() + catch (OperationCanceledException) { - try - { - await foreach (DescribedHandlerDescriptor handlerInfo in ExecutionChannel.Reader.ReadAllAsync(GlobalCancellationToken)) - { - if (ExecutionLimiter != null) - await ExecutionLimiter.WaitAsync(GlobalCancellationToken); - - // Как только слот получен, "отстреливаем" задачу в ThreadPool - // и идем на следующий круг цикла за новым обработчиком из канала. - _ = ProcessHandler(handlerInfo); - } - } - catch (ChannelClosedException) - { - // TODO: add logging - } - } - - private async Task ProcessHandler(DescribedHandlerDescriptor handlerInfo) - { - try - { - TelegratorLogging.LogDebug("Described handler '{0}' (Update {1})", handlerInfo.DisplayString, handlerInfo.HandlingUpdate.Id); - HandlerExecuting?.Invoke(handlerInfo); - - using UpdateHandlerBase instance = handlerInfo.HandlerInstance; - Task task = instance.Execute(handlerInfo); - HandlerEnqueued?.Invoke(handlerInfo); - - await task.ConfigureAwait(false); - Result lastResult = task.Result; - - handlerInfo.ReportResult(lastResult); - ExecutionLimiter?.Release(1); - } - catch (NotImplementedException) - { - _ = 0xBAD + 0xC0DE; - handlerInfo.ReportResult(null); - } - catch (OperationCanceledException) - { - _ = 0xDEADBEEF; - handlerInfo.ReportResult(null); - } - catch (Exception ex) - { - TelegratorLogging.LogError("Failed to process handler '{0}' (Update {1})", exception: ex, handlerInfo.DisplayString, handlerInfo.HandlingUpdate.Id); - handlerInfo.ReportResult(null); - } - } - - /// - /// Disposes of the handlers pool and releases all resources. - /// - public virtual void Dispose() - { - if (disposed) - return; - - // do not dispose UpdateRouter - ExecutionLimiter?.Dispose(); - - GC.SuppressFinalize(this); - disposed = true; + _ = 0xDEADBEEF; } } + + private async Task ReadChannel() + { + try + { + await foreach (DescribedHandlerDescriptor handlerInfo in ExecutionChannel.Reader.ReadAllAsync(GlobalCancellationToken)) + { + if (ExecutionLimiter != null) + await ExecutionLimiter.WaitAsync(GlobalCancellationToken); + + // Как только слот получен, "отстреливаем" задачу в ThreadPool + // и идем на следующий круг цикла за новым обработчиком из канала. + _ = ProcessHandler(handlerInfo); + } + } + catch (ChannelClosedException) + { + // TODO: add logging + } + } + + private async Task ProcessHandler(DescribedHandlerDescriptor handlerInfo) + { + try + { + TelegratorLogging.LogDebug("Described handler '{0}' (Update {1})", handlerInfo.DisplayString, handlerInfo.HandlingUpdate.Id); + HandlerExecuting?.Invoke(handlerInfo); + + using UpdateHandlerBase instance = handlerInfo.HandlerInstance; + Task task = instance.Execute(handlerInfo); + HandlerEnqueued?.Invoke(handlerInfo); + + await task.ConfigureAwait(false); + Result lastResult = task.Result; + + handlerInfo.ReportResult(lastResult); + ExecutionLimiter?.Release(1); + } + catch (NotImplementedException) + { + _ = 0xBAD + 0xC0DE; + handlerInfo.ReportResult(null); + } + catch (OperationCanceledException) + { + _ = 0xDEADBEEF; + handlerInfo.ReportResult(null); + } + catch (Exception ex) + { + TelegratorLogging.LogError("Failed to process handler '{0}' (Update {1})", exception: ex, handlerInfo.DisplayString, handlerInfo.HandlingUpdate.Id); + handlerInfo.ReportResult(null); + } + } + + /// + /// Disposes of the handlers pool and releases all resources. + /// + public virtual void Dispose() + { + if (disposed) + return; + + // do not dispose UpdateRouter + ExecutionLimiter?.Dispose(); + + GC.SuppressFinalize(this); + disposed = true; + } } diff --git a/src/Telegrator/Mediation/UpdateRouter.cs b/src/Telegrator/Mediation/UpdateRouter.cs index 981694f..9bbd876 100644 --- a/src/Telegrator/Mediation/UpdateRouter.cs +++ b/src/Telegrator/Mediation/UpdateRouter.cs @@ -11,303 +11,302 @@ using Telegrator.Core.States; using Telegrator.Handlers.Diagnostics; using Telegrator.Logging; -namespace Telegrator.Mediation +namespace Telegrator.Mediation; + +/// +/// Implementation of that routes updates to appropriate handlers. +/// Manages the distribution of updates between regular handlers and awaiting handlers. +/// +public class UpdateRouter : IUpdateRouter { + private readonly TelegratorOptions _options; + private readonly IHandlersProvider _handlersProvider; + private readonly IAwaitingProvider _awaitingProvider; + private readonly IStateStorage _stateStorage; + private readonly IUpdateHandlersPool _HandlersPool; + private readonly ITelegramBotInfo _botInfo; + + /// + public IHandlersProvider HandlersProvider => _handlersProvider; + + /// + public IAwaitingProvider AwaitingProvider => _awaitingProvider; + + /// + public IStateStorage StateStorage => _stateStorage; + + /// + public TelegratorOptions Options => _options; + + /// + public IUpdateHandlersPool HandlersPool => _HandlersPool; + + /// + public IRouterExceptionHandler? ExceptionHandler { get; set; } + + /// + public IHandlerContainerFactory? DefaultContainerFactory { get; set; } + /// - /// Implementation of that routes updates to appropriate handlers. - /// Manages the distribution of updates between regular handlers and awaiting handlers. + /// Initializes a new instance of the class. /// - public class UpdateRouter : IUpdateRouter + /// The provider for regular handlers. + /// The provider for awaiting handlers. + /// The state storage. + /// The bot configuration options. + /// + public UpdateRouter(IHandlersProvider handlersProvider, IAwaitingProvider awaitingProvider, IStateStorage stateStorage, TelegratorOptions options, ITelegramBotInfo botInfo) { - private readonly TelegratorOptions _options; - private readonly IHandlersProvider _handlersProvider; - private readonly IAwaitingProvider _awaitingProvider; - private readonly IStateStorage _stateStorage; - private readonly IUpdateHandlersPool _HandlersPool; - private readonly ITelegramBotInfo _botInfo; - - /// - public IHandlersProvider HandlersProvider => _handlersProvider; - - /// - public IAwaitingProvider AwaitingProvider => _awaitingProvider; - - /// - public IStateStorage StateStorage => _stateStorage; - - /// - public TelegratorOptions Options => _options; - - /// - public IUpdateHandlersPool HandlersPool => _HandlersPool; - - /// - public IRouterExceptionHandler? ExceptionHandler { get; set; } - - /// - public IHandlerContainerFactory? DefaultContainerFactory { get; set; } - - /// - /// Initializes a new instance of the class. - /// - /// The provider for regular handlers. - /// The provider for awaiting handlers. - /// The state storage. - /// The bot configuration options. - /// - public UpdateRouter(IHandlersProvider handlersProvider, IAwaitingProvider awaitingProvider, IStateStorage stateStorage, TelegratorOptions options, ITelegramBotInfo botInfo) - { - _options = options; - _handlersProvider = handlersProvider; - _awaitingProvider = awaitingProvider; - _stateStorage = stateStorage; - _HandlersPool = new UpdateHandlersPool(this, _options, _options.GlobalCancellationToken); - _botInfo = botInfo; - } - - /// - /// Handles errors that occur during update processing. - /// - /// The Telegram bot client. - /// The exception that occurred. - /// The source of the error. - /// The cancellation token. - /// A task representing the asynchronous error handling operation. - public virtual Task HandleErrorAsync(ITelegramBotClient botClient, Exception exception, HandleErrorSource source, CancellationToken cancellationToken) - { - TelegratorLogging.LogDebug("Handling exception {0}", exception.GetType().Name); - ExceptionHandler?.HandleException(botClient, exception, source, cancellationToken); - return Task.CompletedTask; - } - - /// - /// Handles incoming updates by routing them to appropriate handlers. - /// - /// The Telegram bot client. - /// The update to handle. - /// The cancellation token. - /// A task representing the asynchronous update handling operation. - public virtual async Task HandleUpdateAsync(ITelegramBotClient botClient, Update update, CancellationToken cancellationToken) - { - _ = HandleUpdateAsyncInternal(botClient, update, cancellationToken); - } - - private async Task HandleUpdateAsyncInternal(ITelegramBotClient botClient, Update update, CancellationToken cancellationToken) - { - // Logging - LogUpdate(update); - - try - { - Result? lastResult = null; - foreach (DescribedHandlerDescriptor handlerInfo in GetHandlers(AwaitingProvider, botClient, update, cancellationToken)) - { - if (lastResult?.NextType != null) - { - if (lastResult.NextType != handlerInfo.From.HandlerType) - continue; - } - - // Enqueuing found awiting handlers - await HandlersPool.Enqueue(handlerInfo); - await handlerInfo.AwaitResult(cancellationToken).ConfigureAwait(false); - - lastResult = handlerInfo.Result; - if (lastResult == null) - break; // Smth went horribly wrong, better to stop routing - - if (lastResult != null && !lastResult.RouteNext) - break; - - TelegratorLogging.LogTrace("Handler '{0}' requested route continuation (Update {1})", handlerInfo.DisplayString, handlerInfo.HandlingUpdate.Id); - } - - // Checking if awaiting handlers has exclusive routing - if (Options.ExclusiveAwaitingHandlerRouting) - { - TelegratorLogging.LogTrace("Receiving Update ({0}) completed with only awaiting handlers", update.Id); - return; - } - - // Queuing reagular handlers for execution - foreach (DescribedHandlerDescriptor handlerInfo in GetHandlers(HandlersProvider, botClient, update, cancellationToken)) - { - if (lastResult?.NextType != null) - { - if (lastResult.NextType != handlerInfo.From.HandlerType) - continue; - } - - // Enqueuing found handlers - await HandlersPool.Enqueue(handlerInfo); - await handlerInfo.AwaitResult(cancellationToken).ConfigureAwait(false); - - lastResult = handlerInfo.Result; - if (lastResult == null) - break; // Smth went horribly wrong, better to stop routing - - if (lastResult != null && !lastResult.RouteNext) - break; - - TelegratorLogging.LogTrace("Handler '{0}' requested route continuation (Update {1})", handlerInfo.DisplayString, handlerInfo.HandlingUpdate.Id); - } - - TelegratorLogging.LogTrace("Receiving Update ({0}) finished", update.Id); - } - catch (OperationCanceledException) - { - TelegratorLogging.LogTrace("Receiving Update ({0}) cancelled", update.Id); - } - catch (Exception ex) - { - TelegratorLogging.LogDebug("Receiving Update ({0}) finished with exception {1}", update.Id, ex.Message); - ExceptionHandler?.HandleException(botClient, ex, HandleErrorSource.PollingError, cancellationToken); - } - } - - /// - /// Gets the handlers that match the specified update, using the provided router and client. - /// Searches for handlers by update type, falling back to Unknown type if no specific handlers are found. - /// - /// The privode used to get handlers instance - /// The Telegram bot client instance - /// The incoming Telegram update to process - /// - /// A collection of described handler information for the update - protected virtual IEnumerable GetHandlers(IHandlersProvider provider, ITelegramBotClient client, Update update, CancellationToken cancellationToken = default) - { - TelegratorLogging.LogTrace("Requested handlers for UpdateType.{0}", update.Type); - if (!provider.TryGetDescriptorList(update.Type, out HandlerDescriptorList? descriptors)) - { - TelegratorLogging.LogTrace("No registered, providing Any"); - provider.TryGetDescriptorList(UpdateType.Unknown, out descriptors); - } - - if (descriptors == null || descriptors.Count == 0) - { - TelegratorLogging.LogTrace("No handlers provided"); - return []; - } - - return DescribeDescriptors(provider, descriptors, client, update, cancellationToken); - } - - /// - /// Describes all handler descriptors for a given update context. - /// Processes descriptors in reverse order and respects the ExecuteOnlyFirstFoundHanlder option. - /// - /// The privode used to get handlers instance - /// The list of handler descriptors to process - /// The Telegram bot client instance - /// The incoming Telegram update to process - /// - /// A collection of described handler information - protected virtual IEnumerable DescribeDescriptors(IHandlersProvider provider, HandlerDescriptorList descriptors, ITelegramBotClient client, Update update, CancellationToken cancellationToken = default) - { - TelegratorLogging.LogTrace("Describing descriptors of descriptorsList.HandlingType.{0} for Update ({1})", descriptors.HandlingType, update.Id); - foreach (HandlerDescriptor descriptor in descriptors.Reverse()) - { - cancellationToken.ThrowIfCancellationRequested(); - DescribedHandlerDescriptor? describedHandler = DescribeHandler(provider, descriptor, client, update, out bool breakRouting, cancellationToken); - if (breakRouting) - yield break; - - if (describedHandler == null) - continue; - - yield return describedHandler; - } - - TelegratorLogging.LogTrace("Describing for Update ({0}) finished", update.Id); - } - - /// - /// Describes a single handler descriptor for a given update context. - /// Validates the handler's filters against the update and creates a handler instance if validation passes. - /// - /// The privode used to get handlers instance - /// The handler descriptor to process - /// The Telegram bot client instance - /// The incoming Telegram update to process - /// - /// - /// The described handler info if validation passes; otherwise, null - public virtual DescribedHandlerDescriptor? DescribeHandler(IHandlersProvider provider, HandlerDescriptor descriptor, ITelegramBotClient client, Update update, out bool breakRouting, CancellationToken cancellationToken = default) - { - breakRouting = false; - cancellationToken.ThrowIfCancellationRequested(); - Dictionary data = new Dictionary() - { - { "handler_name", descriptor.ToString() } - }; - - UpdateHandlerBase handlerInstance = provider.GetHandlerInstance(descriptor, cancellationToken); - FilterExecutionContext filterContext = new FilterExecutionContext(this, _botInfo, update, update, data, []); - - if (descriptor.Filters != null) - { - FiltersFallbackReport report = new FiltersFallbackReport(descriptor, filterContext); - Result filtersResult = descriptor.Filters.Validate(filterContext, descriptor.FormReport, ref report); - - if (filtersResult.RouteNext) - { - Result fallbackResult = handlerInstance.FiltersFallback(report, client, cancellationToken).Result; - breakRouting = !fallbackResult.RouteNext; - return null; - } - else if (!filtersResult.Positive) - { - return null; - } - } - - return new DescribedHandlerDescriptor(descriptor, this, AwaitingProvider, StateStorage, client, handlerInstance, filterContext, descriptor.DisplayString); - } - - /// - /// Methos used to log received object - /// - /// - /// - protected static void LogUpdate(Update update) - { - if (TelegratorLogging.MinimalLevel > LogLevel.Trace) - return; - - StringBuilder sb = new StringBuilder(); - sb.AppendFormat("Received Update ({0}) of type \"{1}\"", update.Id, update.Type); - - switch (update) - { - case { Message: { } message }: - { - if (message is { From: { } from }) - sb.AppendFormat(" from '{0}' ({1})", from.Username, from.Id); - - if (message is { Text: { } text }) - sb.AppendFormat(" with text '{0}'", text); - - if (message is { Sticker: { } sticker }) - sb.AppendFormat(" with sticker '{0}'", sticker.Emoji); - - break; - } - - case { CallbackQuery: { } callback }: - { - if (callback is { From: { } from }) - sb.AppendFormat(" from '{0}' ({1})", from.Username, from.Id); - - if (callback is { Data: { } data }) - sb.AppendFormat(" with data '{0}'", data); - - break; - } - } - - TelegratorLogging.LogTrace(sb.ToString()); - } - - private class BreakDescribingException : Exception { } + _options = options; + _handlersProvider = handlersProvider; + _awaitingProvider = awaitingProvider; + _stateStorage = stateStorage; + _HandlersPool = new UpdateHandlersPool(this, _options, _options.GlobalCancellationToken); + _botInfo = botInfo; } + + /// + /// Handles errors that occur during update processing. + /// + /// The Telegram bot client. + /// The exception that occurred. + /// The source of the error. + /// The cancellation token. + /// A task representing the asynchronous error handling operation. + public virtual Task HandleErrorAsync(ITelegramBotClient botClient, Exception exception, HandleErrorSource source, CancellationToken cancellationToken) + { + TelegratorLogging.LogDebug("Handling exception {0}", exception.GetType().Name); + ExceptionHandler?.HandleException(botClient, exception, source, cancellationToken); + return Task.CompletedTask; + } + + /// + /// Handles incoming updates by routing them to appropriate handlers. + /// + /// The Telegram bot client. + /// The update to handle. + /// The cancellation token. + /// A task representing the asynchronous update handling operation. + public virtual async Task HandleUpdateAsync(ITelegramBotClient botClient, Update update, CancellationToken cancellationToken) + { + _ = HandleUpdateAsyncInternal(botClient, update, cancellationToken); + } + + private async Task HandleUpdateAsyncInternal(ITelegramBotClient botClient, Update update, CancellationToken cancellationToken) + { + // Logging + LogUpdate(update); + + try + { + Result? lastResult = null; + foreach (DescribedHandlerDescriptor handlerInfo in GetHandlers(AwaitingProvider, botClient, update, cancellationToken)) + { + if (lastResult?.NextType != null) + { + if (lastResult.NextType != handlerInfo.From.HandlerType) + continue; + } + + // Enqueuing found awiting handlers + await HandlersPool.Enqueue(handlerInfo); + await handlerInfo.AwaitResult(cancellationToken).ConfigureAwait(false); + + lastResult = handlerInfo.Result; + if (lastResult == null) + break; // Smth went horribly wrong, better to stop routing + + if (lastResult != null && !lastResult.RouteNext) + break; + + TelegratorLogging.LogTrace("Handler '{0}' requested route continuation (Update {1})", handlerInfo.DisplayString, handlerInfo.HandlingUpdate.Id); + } + + // Checking if awaiting handlers has exclusive routing + if (Options.ExclusiveAwaitingHandlerRouting) + { + TelegratorLogging.LogTrace("Receiving Update ({0}) completed with only awaiting handlers", update.Id); + return; + } + + // Queuing reagular handlers for execution + foreach (DescribedHandlerDescriptor handlerInfo in GetHandlers(HandlersProvider, botClient, update, cancellationToken)) + { + if (lastResult?.NextType != null) + { + if (lastResult.NextType != handlerInfo.From.HandlerType) + continue; + } + + // Enqueuing found handlers + await HandlersPool.Enqueue(handlerInfo); + await handlerInfo.AwaitResult(cancellationToken).ConfigureAwait(false); + + lastResult = handlerInfo.Result; + if (lastResult == null) + break; // Smth went horribly wrong, better to stop routing + + if (lastResult != null && !lastResult.RouteNext) + break; + + TelegratorLogging.LogTrace("Handler '{0}' requested route continuation (Update {1})", handlerInfo.DisplayString, handlerInfo.HandlingUpdate.Id); + } + + TelegratorLogging.LogTrace("Receiving Update ({0}) finished", update.Id); + } + catch (OperationCanceledException) + { + TelegratorLogging.LogTrace("Receiving Update ({0}) cancelled", update.Id); + } + catch (Exception ex) + { + TelegratorLogging.LogDebug("Receiving Update ({0}) finished with exception {1}", update.Id, ex.Message); + ExceptionHandler?.HandleException(botClient, ex, HandleErrorSource.PollingError, cancellationToken); + } + } + + /// + /// Gets the handlers that match the specified update, using the provided router and client. + /// Searches for handlers by update type, falling back to Unknown type if no specific handlers are found. + /// + /// The privode used to get handlers instance + /// The Telegram bot client instance + /// The incoming Telegram update to process + /// + /// A collection of described handler information for the update + protected virtual IEnumerable GetHandlers(IHandlersProvider provider, ITelegramBotClient client, Update update, CancellationToken cancellationToken = default) + { + TelegratorLogging.LogTrace("Requested handlers for UpdateType.{0}", update.Type); + if (!provider.TryGetDescriptorList(update.Type, out HandlerDescriptorList? descriptors)) + { + TelegratorLogging.LogTrace("No registered, providing Any"); + provider.TryGetDescriptorList(UpdateType.Unknown, out descriptors); + } + + if (descriptors == null || descriptors.Count == 0) + { + TelegratorLogging.LogTrace("No handlers provided"); + return []; + } + + return DescribeDescriptors(provider, descriptors, client, update, cancellationToken); + } + + /// + /// Describes all handler descriptors for a given update context. + /// Processes descriptors in reverse order and respects the ExecuteOnlyFirstFoundHanlder option. + /// + /// The privode used to get handlers instance + /// The list of handler descriptors to process + /// The Telegram bot client instance + /// The incoming Telegram update to process + /// + /// A collection of described handler information + protected virtual IEnumerable DescribeDescriptors(IHandlersProvider provider, HandlerDescriptorList descriptors, ITelegramBotClient client, Update update, CancellationToken cancellationToken = default) + { + TelegratorLogging.LogTrace("Describing descriptors of descriptorsList.HandlingType.{0} for Update ({1})", descriptors.HandlingType, update.Id); + foreach (HandlerDescriptor descriptor in descriptors.Reverse()) + { + cancellationToken.ThrowIfCancellationRequested(); + DescribedHandlerDescriptor? describedHandler = DescribeHandler(provider, descriptor, client, update, out bool breakRouting, cancellationToken); + if (breakRouting) + yield break; + + if (describedHandler == null) + continue; + + yield return describedHandler; + } + + TelegratorLogging.LogTrace("Describing for Update ({0}) finished", update.Id); + } + + /// + /// Describes a single handler descriptor for a given update context. + /// Validates the handler's filters against the update and creates a handler instance if validation passes. + /// + /// The privode used to get handlers instance + /// The handler descriptor to process + /// The Telegram bot client instance + /// The incoming Telegram update to process + /// + /// + /// The described handler info if validation passes; otherwise, null + public virtual DescribedHandlerDescriptor? DescribeHandler(IHandlersProvider provider, HandlerDescriptor descriptor, ITelegramBotClient client, Update update, out bool breakRouting, CancellationToken cancellationToken = default) + { + breakRouting = false; + cancellationToken.ThrowIfCancellationRequested(); + Dictionary data = new Dictionary() + { + { "handler_name", descriptor.ToString() } + }; + + UpdateHandlerBase handlerInstance = provider.GetHandlerInstance(descriptor, cancellationToken); + FilterExecutionContext filterContext = new FilterExecutionContext(this, _botInfo, update, update, data, []); + + if (descriptor.Filters != null) + { + FiltersFallbackReport report = new FiltersFallbackReport(descriptor, filterContext); + Result filtersResult = descriptor.Filters.Validate(filterContext, descriptor.FormReport, ref report); + + if (filtersResult.RouteNext) + { + Result fallbackResult = handlerInstance.FiltersFallback(report, client, cancellationToken).Result; + breakRouting = !fallbackResult.RouteNext; + return null; + } + else if (!filtersResult.Positive) + { + return null; + } + } + + return new DescribedHandlerDescriptor(descriptor, this, AwaitingProvider, StateStorage, client, handlerInstance, filterContext, descriptor.DisplayString); + } + + /// + /// Methos used to log received object + /// + /// + /// + protected static void LogUpdate(Update update) + { + if (TelegratorLogging.MinimalLevel > LogLevel.Trace) + return; + + StringBuilder sb = new StringBuilder(); + sb.AppendFormat("Received Update ({0}) of type \"{1}\"", update.Id, update.Type); + + switch (update) + { + case { Message: { } message }: + { + if (message is { From: { } from }) + sb.AppendFormat(" from '{0}' ({1})", from.Username, from.Id); + + if (message is { Text: { } text }) + sb.AppendFormat(" with text '{0}'", text); + + if (message is { Sticker: { } sticker }) + sb.AppendFormat(" with sticker '{0}'", sticker.Emoji); + + break; + } + + case { CallbackQuery: { } callback }: + { + if (callback is { From: { } from }) + sb.AppendFormat(" from '{0}' ({1})", from.Username, from.Id); + + if (callback is { Data: { } data }) + sb.AppendFormat(" with data '{0}'", data); + + break; + } + } + + TelegratorLogging.LogTrace(sb.ToString()); + } + + private class BreakDescribingException : Exception { } } diff --git a/src/Telegrator/Providers/AwaitingProvider.cs b/src/Telegrator/Providers/AwaitingProvider.cs index f98cf8e..f4af7c4 100644 --- a/src/Telegrator/Providers/AwaitingProvider.cs +++ b/src/Telegrator/Providers/AwaitingProvider.cs @@ -2,62 +2,61 @@ using Telegrator.Core; using Telegrator.Core.Descriptors; -namespace Telegrator.Providers +namespace Telegrator.Providers; + +/// +/// Provider for managing awaiting handlers that can wait for specific update types. +/// Extends HandlersProvider to provide functionality for creating and managing awaiter handlers. +/// +/// The bot configuration options. +public class AwaitingProvider(TelegratorOptions options) : HandlersProvider([], options), IAwaitingProvider { /// - /// Provider for managing awaiting handlers that can wait for specific update types. - /// Extends HandlersProvider to provide functionality for creating and managing awaiter handlers. + /// List of handler descriptors for awaiting handlers. /// - /// The bot configuration options. - public class AwaitingProvider(TelegratorOptions options) : HandlersProvider([], options), IAwaitingProvider + protected readonly HandlerDescriptorList HandlersList = []; + + /// + public override bool TryGetDescriptorList(UpdateType updateType, out HandlerDescriptorList? list) + { + list = HandlersList; + return true; + } + + /// + public IDisposable UseHandler(HandlerDescriptor handlerDescriptor) + { + HandlerToken handlerToken = new HandlerToken(HandlersList, handlerDescriptor); + handlerToken.Register(); + return handlerToken; + } + + /// + /// Token for managing the lifetime of a handler in the awaiting provider. + /// Implements IDisposable to automatically remove the handler when disposed. + /// + /// The list of handler descriptors. + /// The handler descriptor to manage. + private readonly struct HandlerToken(HandlerDescriptorList handlersList, HandlerDescriptor handlerDescriptor) : IDisposable { /// - /// List of handler descriptors for awaiting handlers. + /// Registers the handler descriptor in the handlers list. /// - protected readonly HandlerDescriptorList HandlersList = []; - - /// - public override bool TryGetDescriptorList(UpdateType updateType, out HandlerDescriptorList? list) + /// Thrown when the handler descriptor has no singleton instance. + public readonly void Register() { - list = HandlersList; - return true; - } + if (handlerDescriptor.SingletonInstance == null) + throw new Exception(); - /// - public IDisposable UseHandler(HandlerDescriptor handlerDescriptor) - { - HandlerToken handlerToken = new HandlerToken(HandlersList, handlerDescriptor); - handlerToken.Register(); - return handlerToken; + handlersList.Add(handlerDescriptor); } /// - /// Token for managing the lifetime of a handler in the awaiting provider. - /// Implements IDisposable to automatically remove the handler when disposed. + /// Disposes of the handler token by removing the handler descriptor from the list. /// - /// The list of handler descriptors. - /// The handler descriptor to manage. - private readonly struct HandlerToken(HandlerDescriptorList handlersList, HandlerDescriptor handlerDescriptor) : IDisposable + public readonly void Dispose() { - /// - /// Registers the handler descriptor in the handlers list. - /// - /// Thrown when the handler descriptor has no singleton instance. - public readonly void Register() - { - if (handlerDescriptor.SingletonInstance == null) - throw new Exception(); - - handlersList.Add(handlerDescriptor); - } - - /// - /// Disposes of the handler token by removing the handler descriptor from the list. - /// - public readonly void Dispose() - { - handlersList.Remove(handlerDescriptor.Indexer); - } + handlersList.Remove(handlerDescriptor.Indexer); } } } diff --git a/src/Telegrator/Providers/HandlersCollection.cs b/src/Telegrator/Providers/HandlersCollection.cs index 8d26df4..65e2274 100644 --- a/src/Telegrator/Providers/HandlersCollection.cs +++ b/src/Telegrator/Providers/HandlersCollection.cs @@ -4,128 +4,127 @@ using Telegrator.Annotations; using Telegrator.Core; using Telegrator.Core.Descriptors; -namespace Telegrator.Providers +namespace Telegrator.Providers; + +/// +/// Collection class for managing handler descriptors organized by update type. +/// Provides functionality for collecting, adding, and organizing handlers. +/// +/// Optional configuration options for handler collecting. +public class HandlersCollection(TelegratorOptions? options) : IHandlersCollection { /// - /// Collection class for managing handler descriptors organized by update type. - /// Provides functionality for collecting, adding, and organizing handlers. + /// Gets the collection of 's allowed by registered handlers /// - /// Optional configuration options for handler collecting. - public class HandlersCollection(TelegratorOptions? options) : IHandlersCollection + protected readonly List _allowedTypes = []; + + /// + /// Dictionary that organizes handler descriptors by update type. + /// + protected readonly Dictionary InnerDictionary = []; + + /// + /// Configuration options for handler collecting. + /// + protected readonly TelegratorOptions? Options = options; + + /// + /// Gets whether handlers must have a parameterless constructor. + /// + protected virtual bool MustHaveParameterlessCtor => true; + + /// + /// List of command aliases that have been registered. + /// + public readonly List CommandAliasses = []; + + /// + public IEnumerable AllowedTypes => _allowedTypes; + + /// + public IEnumerable Keys { - /// - /// Gets the collection of 's allowed by registered handlers - /// - protected readonly List _allowedTypes = []; + get => InnerDictionary.Keys; + } - /// - /// Dictionary that organizes handler descriptors by update type. - /// - protected readonly Dictionary InnerDictionary = []; - - /// - /// Configuration options for handler collecting. - /// - protected readonly TelegratorOptions? Options = options; - - /// - /// Gets whether handlers must have a parameterless constructor. - /// - protected virtual bool MustHaveParameterlessCtor => true; - - /// - /// List of command aliases that have been registered. - /// - public readonly List CommandAliasses = []; + /// + public IEnumerable Values + { + get => InnerDictionary.Values; + } - /// - public IEnumerable AllowedTypes => _allowedTypes; + /// + public HandlerDescriptorList this[UpdateType updateType] + { + get => InnerDictionary[updateType]; + } - /// - public IEnumerable Keys + /// + /// Adds a handler descriptor to the collection. + /// + /// The handler descriptor to add. + /// This collection instance for method chaining. + /// Thrown when the handler type doesn't have a parameterless constructor and MustHaveParameterlessCtor is true. + public virtual IHandlersCollection AddDescriptor(HandlerDescriptor descriptor) + { + if (MustHaveParameterlessCtor && !descriptor.HandlerType.HasParameterlessCtor()) + throw new Exception("This handler (" + descriptor.HandlerType.FullName + "), must contain constructor without parameters."); + + _allowedTypes.UnionAdd(descriptor.UpdateType); + MightAwaitAttribute? mightAwait = descriptor.HandlerType.GetCustomAttribute(); + if (mightAwait != null) + _allowedTypes.UnionAdd(mightAwait.UpdateTypes); + + IntersectCommands(descriptor); + HandlerDescriptorList list = GetDescriptorList(descriptor); + + if (descriptor.UpdateType == UpdateType.InlineQuery || descriptor.UpdateType == UpdateType.ChosenInlineResult) { - get => InnerDictionary.Keys; + if (list.Count > 0) + throw new Exception("Bot cannot have more than one InlineQuery handler"); } - /// - public IEnumerable Values + list.Add(descriptor); + return this; + } + + /// + /// Gets the for the specified . + /// + /// The handler descriptor. + /// The handler descriptor list containing the descriptor. + protected virtual HandlerDescriptorList GetDescriptorList(HandlerDescriptor descriptor) + { + UpdateType updateType = UpdateTypeExtensions.SuppressTypes.TryGetValue(descriptor.UpdateType, out UpdateType suppressType) + ? suppressType + : descriptor.UpdateType; + + if (!InnerDictionary.TryGetValue(updateType, out HandlerDescriptorList? list)) { - get => InnerDictionary.Values; + list = new HandlerDescriptorList(updateType, Options); + InnerDictionary.Add(updateType, list); } - /// - public HandlerDescriptorList this[UpdateType updateType] - { - get => InnerDictionary[updateType]; - } + return list; + } - /// - /// Adds a handler descriptor to the collection. - /// - /// The handler descriptor to add. - /// This collection instance for method chaining. - /// Thrown when the handler type doesn't have a parameterless constructor and MustHaveParameterlessCtor is true. - public virtual IHandlersCollection AddDescriptor(HandlerDescriptor descriptor) - { - if (MustHaveParameterlessCtor && !descriptor.HandlerType.HasParameterlessCtor()) - throw new Exception("This handler (" + descriptor.HandlerType.FullName + "), must contain constructor without parameters."); + /// + /// Checks for intersecting command aliases and handles them according to configuration. + /// + /// The handler descriptor to check for command aliases. + /// Thrown when intersecting command aliases are found and ExceptIntersectingCommandAliases is enabled. + protected void IntersectCommands(HandlerDescriptor descriptor) + { + if (Options == null) + return; - _allowedTypes.UnionAdd(descriptor.UpdateType); - MightAwaitAttribute? mightAwait = descriptor.HandlerType.GetCustomAttribute(); - if (mightAwait != null) - _allowedTypes.UnionAdd(mightAwait.UpdateTypes); + CommandAlliasAttribute? alliasAttribute = descriptor.HandlerType.GetCustomAttribute(); + if (alliasAttribute == null) + return; - IntersectCommands(descriptor); - HandlerDescriptorList list = GetDescriptorList(descriptor); + if (Options.ExceptIntersectingCommandAliases && CommandAliasses.Intersect(alliasAttribute.Alliases, StringComparer.InvariantCultureIgnoreCase).Any()) + throw new Exception(descriptor.HandlerType.FullName); - if (descriptor.UpdateType == UpdateType.InlineQuery || descriptor.UpdateType == UpdateType.ChosenInlineResult) - { - if (list.Count > 0) - throw new Exception("Bot cannot have more than one InlineQuery handler"); - } - - list.Add(descriptor); - return this; - } - - /// - /// Gets the for the specified . - /// - /// The handler descriptor. - /// The handler descriptor list containing the descriptor. - protected virtual HandlerDescriptorList GetDescriptorList(HandlerDescriptor descriptor) - { - UpdateType updateType = UpdateTypeExtensions.SuppressTypes.TryGetValue(descriptor.UpdateType, out UpdateType suppressType) - ? suppressType - : descriptor.UpdateType; - - if (!InnerDictionary.TryGetValue(updateType, out HandlerDescriptorList? list)) - { - list = new HandlerDescriptorList(updateType, Options); - InnerDictionary.Add(updateType, list); - } - - return list; - } - - /// - /// Checks for intersecting command aliases and handles them according to configuration. - /// - /// The handler descriptor to check for command aliases. - /// Thrown when intersecting command aliases are found and ExceptIntersectingCommandAliases is enabled. - protected void IntersectCommands(HandlerDescriptor descriptor) - { - if (Options == null) - return; - - CommandAlliasAttribute? alliasAttribute = descriptor.HandlerType.GetCustomAttribute(); - if (alliasAttribute == null) - return; - - if (Options.ExceptIntersectingCommandAliases && CommandAliasses.Intersect(alliasAttribute.Alliases, StringComparer.InvariantCultureIgnoreCase).Any()) - throw new Exception(descriptor.HandlerType.FullName); - - CommandAliasses.AddRange(alliasAttribute.Alliases); - } + CommandAliasses.AddRange(alliasAttribute.Alliases); } } diff --git a/src/Telegrator/Providers/HandlersManagerBase.cs b/src/Telegrator/Providers/HandlersManagerBase.cs index b39f3f0..f73e700 100644 --- a/src/Telegrator/Providers/HandlersManagerBase.cs +++ b/src/Telegrator/Providers/HandlersManagerBase.cs @@ -5,122 +5,121 @@ using Telegrator.Core; using Telegrator.Core.Descriptors; using Telegrator.Core.Handlers; -namespace Telegrator.Providers +namespace Telegrator.Providers; + +/// +/// Provides functionality of incrementally collecting, organizing and resolving handlers instances. +/// Minimum implementation of . Abstract class, still requires handler instance resolving. +/// +/// +public abstract class HandlersManagerBase(TelegratorOptions options) : IHandlersManager { /// - /// Provides functionality of incrementally collecting, organizing and resolving handlers instances. - /// Minimum implementation of . Abstract class, still requires handler instance resolving. + /// Dictionary that organizes handler descriptors by update type. /// - /// - public abstract class HandlersManagerBase(TelegratorOptions options) : IHandlersManager + protected readonly Dictionary _handlersDictionary = []; + + /// + /// Gets the collection of Telegram.Bot.Types.Enums.UpdateType's allowed by registered handlers + /// + protected readonly List _allowedTypes = []; + + /// + /// Configuration options for handler collecting. + /// + protected readonly TelegratorOptions? Options = options; + + /// + /// Gets whether handlers must have a parameterless constructor. + /// + protected virtual bool MustHaveParameterlessCtor => true; + + /// + /// List of command aliases that have been registered. + /// + public readonly List CommandAliasses = []; + + /// + public HandlerDescriptorList this[UpdateType updateType] => _handlersDictionary[updateType]; + + /// + public IEnumerable AllowedTypes => _allowedTypes; + + /// + public IEnumerable Keys => _handlersDictionary.Keys; + + /// + public IEnumerable Values => _handlersDictionary.Values; + + /// + public virtual IHandlersCollection AddDescriptor(HandlerDescriptor descriptor) { - /// - /// Dictionary that organizes handler descriptors by update type. - /// - protected readonly Dictionary _handlersDictionary = []; + if (MustHaveParameterlessCtor && !descriptor.HandlerType.HasParameterlessCtor()) + throw new Exception("This handler (" + descriptor.HandlerType.FullName + "), must contain constructor without parameters."); - /// - /// Gets the collection of Telegram.Bot.Types.Enums.UpdateType's allowed by registered handlers - /// - protected readonly List _allowedTypes = []; + _allowedTypes.UnionAdd([descriptor.UpdateType]); + MightAwaitAttribute? mightAwait = descriptor.HandlerType.GetCustomAttribute(); + if (mightAwait != null) + _allowedTypes.UnionAdd(mightAwait.UpdateTypes); - /// - /// Configuration options for handler collecting. - /// - protected readonly TelegratorOptions? Options = options; + IntersectCommands(descriptor); + HandlerDescriptorList list = GetDescriptorList(descriptor); - /// - /// Gets whether handlers must have a parameterless constructor. - /// - protected virtual bool MustHaveParameterlessCtor => true; - - /// - /// List of command aliases that have been registered. - /// - public readonly List CommandAliasses = []; - - /// - public HandlerDescriptorList this[UpdateType updateType] => _handlersDictionary[updateType]; - - /// - public IEnumerable AllowedTypes => _allowedTypes; - - /// - public IEnumerable Keys => _handlersDictionary.Keys; - - /// - public IEnumerable Values => _handlersDictionary.Values; - - /// - public virtual IHandlersCollection AddDescriptor(HandlerDescriptor descriptor) - { - if (MustHaveParameterlessCtor && !descriptor.HandlerType.HasParameterlessCtor()) - throw new Exception("This handler (" + descriptor.HandlerType.FullName + "), must contain constructor without parameters."); - - _allowedTypes.UnionAdd([descriptor.UpdateType]); - MightAwaitAttribute? mightAwait = descriptor.HandlerType.GetCustomAttribute(); - if (mightAwait != null) - _allowedTypes.UnionAdd(mightAwait.UpdateTypes); - - IntersectCommands(descriptor); - HandlerDescriptorList list = GetDescriptorList(descriptor); - - list.Add(descriptor); - return this; - } - - /// - /// Gets the for the specified . - /// - /// The handler descriptor. - /// The handler descriptor list containing the descriptor. - protected virtual HandlerDescriptorList GetDescriptorList(HandlerDescriptor descriptor) - { - UpdateType updateType = UpdateTypeExtensions.SuppressTypes.TryGetValue(descriptor.UpdateType, out UpdateType suppressType) - ? suppressType : descriptor.UpdateType; - - if (!_handlersDictionary.TryGetValue(updateType, out HandlerDescriptorList? list)) - { - list = new HandlerDescriptorList(updateType, Options); - _handlersDictionary.Add(updateType, list); - } - - return list; - } - - /// - /// Checks for intersecting command aliases and handles them according to configuration. - /// - /// The handler descriptor to check for command aliases. - /// Thrown when intersecting command aliases are found and ExceptIntersectingCommandAliases is enabled. - protected virtual void IntersectCommands(HandlerDescriptor descriptor) - { - if (Options == null || !Options.ExceptIntersectingCommandAliases) - return; - - CommandAlliasAttribute? alliasAttribute = descriptor.HandlerType.GetCustomAttribute(); - if (alliasAttribute == null) - return; - - if (CommandAliasses.Intersect(alliasAttribute.Alliases, StringComparer.InvariantCultureIgnoreCase).Any()) - throw new Exception(descriptor.HandlerType.FullName); - - CommandAliasses.AddRange(alliasAttribute.Alliases); - } - - /// - public bool TryGetDescriptorList(UpdateType updateType, out HandlerDescriptorList? list) - { - return _handlersDictionary.TryGetValue(updateType, out list); - } - - /// - public bool IsEmpty() - { - return _handlersDictionary.Any(pair => pair.Value.Count != 0); - } - - /// - public abstract UpdateHandlerBase GetHandlerInstance(HandlerDescriptor descriptor, CancellationToken cancellationToken = default); + list.Add(descriptor); + return this; } + + /// + /// Gets the for the specified . + /// + /// The handler descriptor. + /// The handler descriptor list containing the descriptor. + protected virtual HandlerDescriptorList GetDescriptorList(HandlerDescriptor descriptor) + { + UpdateType updateType = UpdateTypeExtensions.SuppressTypes.TryGetValue(descriptor.UpdateType, out UpdateType suppressType) + ? suppressType : descriptor.UpdateType; + + if (!_handlersDictionary.TryGetValue(updateType, out HandlerDescriptorList? list)) + { + list = new HandlerDescriptorList(updateType, Options); + _handlersDictionary.Add(updateType, list); + } + + return list; + } + + /// + /// Checks for intersecting command aliases and handles them according to configuration. + /// + /// The handler descriptor to check for command aliases. + /// Thrown when intersecting command aliases are found and ExceptIntersectingCommandAliases is enabled. + protected virtual void IntersectCommands(HandlerDescriptor descriptor) + { + if (Options == null || !Options.ExceptIntersectingCommandAliases) + return; + + CommandAlliasAttribute? alliasAttribute = descriptor.HandlerType.GetCustomAttribute(); + if (alliasAttribute == null) + return; + + if (CommandAliasses.Intersect(alliasAttribute.Alliases, StringComparer.InvariantCultureIgnoreCase).Any()) + throw new Exception(descriptor.HandlerType.FullName); + + CommandAliasses.AddRange(alliasAttribute.Alliases); + } + + /// + public bool TryGetDescriptorList(UpdateType updateType, out HandlerDescriptorList? list) + { + return _handlersDictionary.TryGetValue(updateType, out list); + } + + /// + public bool IsEmpty() + { + return _handlersDictionary.Any(pair => pair.Value.Count != 0); + } + + /// + public abstract UpdateHandlerBase GetHandlerInstance(HandlerDescriptor descriptor, CancellationToken cancellationToken = default); } diff --git a/src/Telegrator/Providers/HandlersProvider.cs b/src/Telegrator/Providers/HandlersProvider.cs index 8f18d52..1feb552 100644 --- a/src/Telegrator/Providers/HandlersProvider.cs +++ b/src/Telegrator/Providers/HandlersProvider.cs @@ -6,115 +6,114 @@ using Telegrator.Core.Descriptors; using Telegrator.Core.Handlers; using Telegrator.Logging; -namespace Telegrator.Providers +namespace Telegrator.Providers; + +/// +/// Provides handler resolution and instantiation logic for Telegram bot updates. +/// Responsible for mapping update types to handler descriptors, filtering handlers based on update context, +/// and creating handler instances with appropriate lifecycle management. +/// +public class HandlersProvider : IHandlersProvider { + /// + public IEnumerable AllowedTypes { get; } + /// - /// Provides handler resolution and instantiation logic for Telegram bot updates. - /// Responsible for mapping update types to handler descriptors, filtering handlers based on update context, - /// and creating handler instances with appropriate lifecycle management. + /// Read-only dictionary mapping to lists of handler descriptors. + /// Each descriptor list is frozen to prevent modification after initialization. /// - public class HandlersProvider : IHandlersProvider + protected readonly ReadOnlyDictionary HandlersDictionary; + + /// + /// Configuration options for the bot and handler execution behavior. + /// + protected readonly TelegratorOptions Options; + + /// + /// Initializes a new instance of with the specified handler collections and configuration. + /// + /// Collection of handler descriptor lists organized by update type + /// Configuration options for the bot and handler execution + /// Thrown when options or botInfo is null + public HandlersProvider(IHandlersCollection handlers, TelegratorOptions options) { - /// - public IEnumerable AllowedTypes { get; } + AllowedTypes = handlers.AllowedTypes; + HandlersDictionary = handlers.Values.ForEach(list => list.Freeze()).ToReadOnlyDictionary(list => list.HandlingType); + Options = options ?? throw new ArgumentNullException(nameof(options)); + TelegratorLogging.LogTrace("{0} created!", GetType().Name); + } - /// - /// Read-only dictionary mapping to lists of handler descriptors. - /// Each descriptor list is frozen to prevent modification after initialization. - /// - protected readonly ReadOnlyDictionary HandlersDictionary; + /// + /// Initializes a new instance of with the specified handler collections and configuration. + /// + /// Collection of handler descriptor lists organized by update type + /// Configuration options for the bot and handler execution + /// Thrown when options or botInfo is null + public HandlersProvider(IEnumerable handlers, TelegratorOptions options) + { + AllowedTypes = Update.AllTypes; + HandlersDictionary = handlers.ForEach(list => list.Freeze()).ToReadOnlyDictionary(list => list.HandlingType); + Options = options ?? throw new ArgumentNullException(nameof(options)); + TelegratorLogging.LogTrace("{0} created!", GetType().Name); + } - /// - /// Configuration options for the bot and handler execution behavior. - /// - protected readonly TelegratorOptions Options; - - /// - /// Initializes a new instance of with the specified handler collections and configuration. - /// - /// Collection of handler descriptor lists organized by update type - /// Configuration options for the bot and handler execution - /// Thrown when options or botInfo is null - public HandlersProvider(IHandlersCollection handlers, TelegratorOptions options) + /// + /// Thrown when the descriptor type is not recognized + public virtual UpdateHandlerBase GetHandlerInstance(HandlerDescriptor descriptor, CancellationToken cancellationToken = default) + { + try { - AllowedTypes = handlers.AllowedTypes; - HandlersDictionary = handlers.Values.ForEach(list => list.Freeze()).ToReadOnlyDictionary(list => list.HandlingType); - Options = options ?? throw new ArgumentNullException(nameof(options)); - TelegratorLogging.LogTrace("{0} created!", GetType().Name); + // Checking handler instance status + cancellationToken.ThrowIfCancellationRequested(); + bool useSingleton = UseSingleton(descriptor); + + // Returning singleton instance + if (useSingleton && descriptor.SingletonInstance != null) + return descriptor.SingletonInstance; + + // Creating instance + UpdateHandlerBase instance = GetHandlerInstanceInternal(descriptor); + if (useSingleton) + descriptor.TrySetInstance(instance); + + // Lazy initialization execution + descriptor.LazyInitialization?.Invoke(instance); + return instance; } - - /// - /// Initializes a new instance of with the specified handler collections and configuration. - /// - /// Collection of handler descriptor lists organized by update type - /// Configuration options for the bot and handler execution - /// Thrown when options or botInfo is null - public HandlersProvider(IEnumerable handlers, TelegratorOptions options) + catch (Exception ex) { - AllowedTypes = Update.AllTypes; - HandlersDictionary = handlers.ForEach(list => list.Freeze()).ToReadOnlyDictionary(list => list.HandlingType); - Options = options ?? throw new ArgumentNullException(nameof(options)); - TelegratorLogging.LogTrace("{0} created!", GetType().Name); - } - - /// - /// Thrown when the descriptor type is not recognized - public virtual UpdateHandlerBase GetHandlerInstance(HandlerDescriptor descriptor, CancellationToken cancellationToken = default) - { - try - { - // Checking handler instance status - cancellationToken.ThrowIfCancellationRequested(); - bool useSingleton = UseSingleton(descriptor); - - // Returning singleton instance - if (useSingleton && descriptor.SingletonInstance != null) - return descriptor.SingletonInstance; - - // Creating instance - UpdateHandlerBase instance = GetHandlerInstanceInternal(descriptor); - if (useSingleton) - descriptor.TrySetInstance(instance); - - // Lazy initialization execution - descriptor.LazyInitialization?.Invoke(instance); - return instance; - } - catch (Exception ex) - { - TelegratorLogging.LogError("Failed to create instance of '{0}'", exception: ex, descriptor.ToString()); - throw; - } - } - - private static UpdateHandlerBase GetHandlerInstanceInternal(HandlerDescriptor descriptor) - { - if (descriptor.InstanceFactory != null) - return descriptor.InstanceFactory.Invoke(); - - return (UpdateHandlerBase)Activator.CreateInstance(descriptor.HandlerType); - } - - private static bool UseSingleton(HandlerDescriptor descriptor) => descriptor.Type switch - { - DescriptorType.General or DescriptorType.Keyed => false, - DescriptorType.Implicit or DescriptorType.Singleton => true, - _ => throw new Exception("Unknown decriptor type") - }; - - /// - public virtual bool TryGetDescriptorList(UpdateType updateType, out HandlerDescriptorList? list) - { - if (UpdateTypeExtensions.SuppressTypes.TryGetValue(updateType, out UpdateType suppressType)) - updateType = suppressType; - - return HandlersDictionary.TryGetValue(updateType, out list); - } - - /// - public virtual bool IsEmpty() - { - return HandlersDictionary.Count == 0; + TelegratorLogging.LogError("Failed to create instance of '{0}'", exception: ex, descriptor.ToString()); + throw; } } + + private static UpdateHandlerBase GetHandlerInstanceInternal(HandlerDescriptor descriptor) + { + if (descriptor.InstanceFactory != null) + return descriptor.InstanceFactory.Invoke(); + + return (UpdateHandlerBase)Activator.CreateInstance(descriptor.HandlerType); + } + + private static bool UseSingleton(HandlerDescriptor descriptor) => descriptor.Type switch + { + DescriptorType.General or DescriptorType.Keyed => false, + DescriptorType.Implicit or DescriptorType.Singleton => true, + _ => throw new Exception("Unknown decriptor type") + }; + + /// + public virtual bool TryGetDescriptorList(UpdateType updateType, out HandlerDescriptorList? list) + { + if (UpdateTypeExtensions.SuppressTypes.TryGetValue(updateType, out UpdateType suppressType)) + updateType = suppressType; + + return HandlersDictionary.TryGetValue(updateType, out list); + } + + /// + public virtual bool IsEmpty() + { + return HandlersDictionary.Count == 0; + } } diff --git a/src/Telegrator/Result.cs b/src/Telegrator/Result.cs index 060b28b..ddadd12 100644 --- a/src/Telegrator/Result.cs +++ b/src/Telegrator/Result.cs @@ -3,82 +3,81 @@ using Telegrator.Core; using Telegrator.Core.Handlers; using Telegrator.Handlers.Diagnostics; -namespace Telegrator +namespace Telegrator; + +/// +/// Represents handler results, allowing to communicate with router and control aspect execution +/// +public sealed class Result { + private static readonly Result ok = new Result(true, false, null); + private static readonly Result fault = new Result(false, false, null); + private static readonly Result next = new Result(true, true, null); + /// - /// Represents handler results, allowing to communicate with router and control aspect execution + /// Is result positive /// - public sealed class Result + public bool Positive { get; } + + /// + /// Should router search for next matching handler + /// + public bool RouteNext { get; } + + /// + /// Exact type that router should search + /// + public Type? NextType { get; } + + internal Result(bool positive, bool routeNext, Type? nextType) { - private static readonly Result ok = new Result(true, false, null); - private static readonly Result fault = new Result(false, false, null); - private static readonly Result next = new Result(true, true, null); - - /// - /// Is result positive - /// - public bool Positive { get; } - - /// - /// Should router search for next matching handler - /// - public bool RouteNext { get; } - - /// - /// Exact type that router should search - /// - public Type? NextType { get; } - - internal Result(bool positive, bool routeNext, Type? nextType) - { - Positive = positive; - RouteNext = routeNext; - NextType = nextType; - } - - /// - /// Represents 'success' - /// - /// Inside - let handler's main block be executed - /// Inside - tells that he can stop describing, as needed handler was found - /// Inside - let continue describing - /// - /// - /// - public static Result Ok() - => ok; - - /// - /// Represents 'fault' or 'error'. Use cases: - /// - /// Inside - interupts execution of handler, main block and wont be executed - /// Inside - interupts 's describing sequence - /// - /// - /// - public static Result Fault() - => fault; - - /// - /// Represents 'continue'. Use cases: - /// - /// Inside - let continue describing - /// Inside - Tells to continue describing handlers - /// - /// - /// - public static Result Next() - => next; - - /// - /// Represents 'chain'. Use cases: - /// - /// Inside - Tells to continue describing handlers and execute only handlers of exact type - /// - /// - /// - /// - public static Result Next() - => new Result(true, true, typeof(T)); + Positive = positive; + RouteNext = routeNext; + NextType = nextType; } + + /// + /// Represents 'success' + /// + /// Inside - let handler's main block be executed + /// Inside - tells that he can stop describing, as needed handler was found + /// Inside - let continue describing + /// + /// + /// + public static Result Ok() + => ok; + + /// + /// Represents 'fault' or 'error'. Use cases: + /// + /// Inside - interupts execution of handler, main block and wont be executed + /// Inside - interupts 's describing sequence + /// + /// + /// + public static Result Fault() + => fault; + + /// + /// Represents 'continue'. Use cases: + /// + /// Inside - let continue describing + /// Inside - Tells to continue describing handlers + /// + /// + /// + public static Result Next() + => next; + + /// + /// Represents 'chain'. Use cases: + /// + /// Inside - Tells to continue describing handlers and execute only handlers of exact type + /// + /// + /// + /// + public static Result Next() + => new Result(true, true, typeof(T)); } diff --git a/src/Telegrator/SimpleTypesExtensions.cs b/src/Telegrator/SimpleTypesExtensions.cs index b3e2963..4e45485 100644 --- a/src/Telegrator/SimpleTypesExtensions.cs +++ b/src/Telegrator/SimpleTypesExtensions.cs @@ -4,334 +4,333 @@ using Telegrator.Core; using Telegrator.Core.Filters; using Telegrator.Core.Handlers; -namespace Telegrator +namespace Telegrator; + +/// +/// Provides extension methods for working with collections. +/// +public static partial class ColletionsExtensions { /// - /// Provides extension methods for working with collections. + /// Creates a from an + /// according to a specified key selector function. /// - public static partial class ColletionsExtensions + /// + /// + /// + /// + /// + public static ReadOnlyDictionary ToReadOnlyDictionary(this IEnumerable source, Func keySelector) where TKey : notnull { - /// - /// Creates a from an - /// according to a specified key selector function. - /// - /// - /// - /// - /// - /// - public static ReadOnlyDictionary ToReadOnlyDictionary(this IEnumerable source, Func keySelector) where TKey : notnull + Dictionary dictionary = source.ToDictionary(keySelector); + return new ReadOnlyDictionary(dictionary); + } + + /// + /// Enumerates objects in a and executes an on each one + /// + /// + /// + /// + /// + public static IEnumerable ForEach(this IEnumerable source, Action action) + { + foreach (TValue value in source) + action.Invoke(value); + + return source; + } + + /// + /// Sets the value of a key in a dictionary, or if the key does not exist, adds it + /// + /// + /// + /// + /// + /// + public static void Set(this IDictionary source, TKey key, TValue value) + { + if (source.ContainsKey(key)) + source[key] = value; + else + source.Add(key, value); + } + + /// + /// Sets the value of a key in a dictionary, or if the key does not exist, adds its default value. + /// + /// + /// + /// + /// + /// + /// + public static void Set(this IDictionary source, TKey key, TValue value, TValue defaultValue) + { + if (source.ContainsKey(key)) + source[key] = value; + else + source.Add(key, defaultValue); + } + + /// + /// Return the random object from + /// + /// + /// + /// + public static TSource Random(this IEnumerable source) + => source.Random(new Random()); + + /// + /// Return the random object from + /// + /// + /// + /// + /// + public static TSource Random(this IEnumerable source, Random random) + => source.ElementAt(random.Next(0, source.Count() - 1)); + + /// + /// Adds a range of elements to collection if they dont already exist using default equality comparer + /// + /// + /// + /// + public static void UnionAdd(this IList list, params IEnumerable elements) + { + foreach (TSource item in elements) { - Dictionary dictionary = source.ToDictionary(keySelector); - return new ReadOnlyDictionary(dictionary); - } - - /// - /// Enumerates objects in a and executes an on each one - /// - /// - /// - /// - /// - public static IEnumerable ForEach(this IEnumerable source, Action action) - { - foreach (TValue value in source) - action.Invoke(value); - - return source; - } - - /// - /// Sets the value of a key in a dictionary, or if the key does not exist, adds it - /// - /// - /// - /// - /// - /// - public static void Set(this IDictionary source, TKey key, TValue value) - { - if (source.ContainsKey(key)) - source[key] = value; - else - source.Add(key, value); - } - - /// - /// Sets the value of a key in a dictionary, or if the key does not exist, adds its default value. - /// - /// - /// - /// - /// - /// - /// - public static void Set(this IDictionary source, TKey key, TValue value, TValue defaultValue) - { - if (source.ContainsKey(key)) - source[key] = value; - else - source.Add(key, defaultValue); - } - - /// - /// Return the random object from - /// - /// - /// - /// - public static TSource Random(this IEnumerable source) - => source.Random(new Random()); - - /// - /// Return the random object from - /// - /// - /// - /// - /// - public static TSource Random(this IEnumerable source, Random random) - => source.ElementAt(random.Next(0, source.Count() - 1)); - - /// - /// Adds a range of elements to collection if they dont already exist using default equality comparer - /// - /// - /// - /// - public static void UnionAdd(this IList list, params IEnumerable elements) - { - foreach (TSource item in elements) - { - if (!list.Contains(item, EqualityComparer.Default)) - list.Add(item); - } - } - - /// - /// Return index of first element that satisfies the condition - /// - /// - /// - /// - /// - public static int IndexOf(this IEnumerable source, Func predicate) - { - int index = 0; - foreach (T item in source) - { - if (predicate.Invoke(item)) - return index; - - index++; - } - - return -1; - } - - /// - /// Returns the only element of a sequence, or a default value if the sequence is empty. - /// This method returns default if there is more than one element in the sequence. - /// - /// - /// - /// - public static T? SingleSafe(this IEnumerable source) - => source.Count() == 1 ? source.ElementAt(0) : default; - - /// - /// Returns the only element of a sequence that satisfies a specified condition or a default value if no such element exists. - /// This method return default if more than one element satisfies the condition. - /// - /// - /// - /// - /// - public static T? SingleSafe(this IEnumerable source, Func predicate) - { - source = source.Where(predicate); - return source.Count() == 1 ? source.ElementAt(0) : default; + if (!list.Contains(item, EqualityComparer.Default)) + list.Add(item); } } /// - /// Provides extension methods for reflection and type inspection. + /// Return index of first element that satisfies the condition /// - public static partial class ReflectionExtensions + /// + /// + /// + /// + public static int IndexOf(this IEnumerable source, Func predicate) { - /// - /// Checks if a type implements the interface. - /// - /// The type to check. - /// True if the type implements ICustomDescriptorsProvider; otherwise, false. - public static bool IsCustomDescriptorsProvider(this Type type) - => type.GetInterface(nameof(ICustomDescriptorsProvider)) != null; - - /// - /// Checks if is a - /// - /// - /// - public static bool IsFilterType(this Type type) - => type.IsAssignableToGenericType(typeof(IFilter<>)); - - /// - /// Checks if is a descendant of class - /// - /// - /// - public static bool IsHandlerAbstract(this Type type) - => type.IsAbstract && typeof(UpdateHandlerBase).IsAssignableFrom(type); - - /// - /// Checks if is an implementation of class or its descendants - /// - /// - /// - public static bool IsHandlerRealization(this Type type) - => !type.IsAbstract && type != typeof(UpdateHandlerBase) && typeof(UpdateHandlerBase).IsAssignableFrom(type); - - /// - /// Checks if has a parameterless constructor - /// - /// - /// - public static bool HasParameterlessCtor(this Type type) - => type.GetConstructors().Any(ctor => ctor.GetParameters().Length == 0); - - /// - /// Checks is has public properties - /// - /// - /// - public static bool HasPublicProperties(this Type type) - => type.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public).Where(prop => prop.Name != "IsCollectible").Any(); - - /// - /// Determines whether an instance of a specified type can be assigned to an instance of the current type - /// - /// - /// - /// - public static bool IsAssignableToGenericType(this Type givenType, Type genericType) + int index = 0; + foreach (T item in source) { - if (givenType.GetInterfaces().Any(inter => inter.IsGenericType && inter.GetGenericTypeDefinition() == genericType)) - return true; + if (predicate.Invoke(item)) + return index; - if (givenType.IsGenericType && givenType.GetGenericTypeDefinition() == genericType) - return true; - - if (givenType.BaseType == null) - return false; - - return givenType.BaseType.IsAssignableToGenericType(genericType); + index++; } + + return -1; } /// - /// Provides extension methods for string manipulation. + /// Returns the only element of a sequence, or a default value if the sequence is empty. + /// This method returns default if there is more than one element in the sequence. /// - public static partial class StringExtensions - { - /// - /// Slices a string into a array of substrings of fixed - /// - /// - /// - /// - public static IEnumerable SliceBy(this string source, int length) - { - for (int start = 0; start < source.Length; start += length + 1) - { - int tillEnd = source.Length - start; - int toSlice = tillEnd < length + 1 ? tillEnd : length + 1; - - ReadOnlySpan chunk = source.AsSpan().Slice(start, toSlice); - yield return chunk.ToString(); - } - } - - /// - /// Return new string with first found letter set to upper case - /// - /// - /// - public static string FirstLetterToUpper(this string target) - { - char[] chars = target.ToCharArray(); - int index = chars.IndexOf(char.IsLetter); - chars[index] = char.ToUpper(chars[index]); - return new string(chars); - } - - /// - /// Return new string with first found letter set to lower case - /// - /// - /// - public static string FirstLetterToLower(this string target) - { - char[] chars = target.ToCharArray(); - int index = chars.IndexOf(char.IsLetter); - chars[index] = char.ToLower(chars[index]); - return new string(chars); - } - - /// - /// Checks if string contains a 'word'. - /// 'Word' must be a separate member of the text, and not have any alphabetic characters next to it. - /// - /// - /// - /// - /// - /// - public static bool ContainsWord(this string source, string word, StringComparison comparison = StringComparison.InvariantCulture, int startIndex = 0) - { - int index = source.IndexOf(word, startIndex, comparison); - if (index == -1) - return false; - - if (index > 0) - { - char prev = source[index - 1]; - if (char.IsLetter(prev)) - return false; - } - - if (index + word.Length < source.Length) - { - char post = source[index + word.Length]; - if (char.IsLetter(post)) - return false; - } - - return true; - } - } + /// + /// + /// + public static T? SingleSafe(this IEnumerable source) + => source.Count() == 1 ? source.ElementAt(0) : default; /// - /// Contains extension method for number types + /// Returns the only element of a sequence that satisfies a specified condition or a default value if no such element exists. + /// This method return default if more than one element satisfies the condition. /// - public static class NumbersExtensions + /// + /// + /// + /// + public static T? SingleSafe(this IEnumerable source, Func predicate) { - /// - /// Check if int value has int flag using bit compare - /// - /// - /// - /// - public static bool HasFlag(this int value, int flag) - => (value & flag) == flag; - - /// - /// Check if int value has enum flag using bit compare - /// - /// - /// - /// - /// - public static bool HasFlag(this int value, T flag) where T : Enum - => value.HasFlag(Convert.ToInt32(flag)); + source = source.Where(predicate); + return source.Count() == 1 ? source.ElementAt(0) : default; } } + +/// +/// Provides extension methods for reflection and type inspection. +/// +public static partial class ReflectionExtensions +{ + /// + /// Checks if a type implements the interface. + /// + /// The type to check. + /// True if the type implements ICustomDescriptorsProvider; otherwise, false. + public static bool IsCustomDescriptorsProvider(this Type type) + => type.GetInterface(nameof(ICustomDescriptorsProvider)) != null; + + /// + /// Checks if is a + /// + /// + /// + public static bool IsFilterType(this Type type) + => type.IsAssignableToGenericType(typeof(IFilter<>)); + + /// + /// Checks if is a descendant of class + /// + /// + /// + public static bool IsHandlerAbstract(this Type type) + => type.IsAbstract && typeof(UpdateHandlerBase).IsAssignableFrom(type); + + /// + /// Checks if is an implementation of class or its descendants + /// + /// + /// + public static bool IsHandlerRealization(this Type type) + => !type.IsAbstract && type != typeof(UpdateHandlerBase) && typeof(UpdateHandlerBase).IsAssignableFrom(type); + + /// + /// Checks if has a parameterless constructor + /// + /// + /// + public static bool HasParameterlessCtor(this Type type) + => type.GetConstructors().Any(ctor => ctor.GetParameters().Length == 0); + + /// + /// Checks is has public properties + /// + /// + /// + public static bool HasPublicProperties(this Type type) + => type.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public).Where(prop => prop.Name != "IsCollectible").Any(); + + /// + /// Determines whether an instance of a specified type can be assigned to an instance of the current type + /// + /// + /// + /// + public static bool IsAssignableToGenericType(this Type givenType, Type genericType) + { + if (givenType.GetInterfaces().Any(inter => inter.IsGenericType && inter.GetGenericTypeDefinition() == genericType)) + return true; + + if (givenType.IsGenericType && givenType.GetGenericTypeDefinition() == genericType) + return true; + + if (givenType.BaseType == null) + return false; + + return givenType.BaseType.IsAssignableToGenericType(genericType); + } +} + +/// +/// Provides extension methods for string manipulation. +/// +public static partial class StringExtensions +{ + /// + /// Slices a string into a array of substrings of fixed + /// + /// + /// + /// + public static IEnumerable SliceBy(this string source, int length) + { + for (int start = 0; start < source.Length; start += length + 1) + { + int tillEnd = source.Length - start; + int toSlice = tillEnd < length + 1 ? tillEnd : length + 1; + + ReadOnlySpan chunk = source.AsSpan().Slice(start, toSlice); + yield return chunk.ToString(); + } + } + + /// + /// Return new string with first found letter set to upper case + /// + /// + /// + public static string FirstLetterToUpper(this string target) + { + char[] chars = target.ToCharArray(); + int index = chars.IndexOf(char.IsLetter); + chars[index] = char.ToUpper(chars[index]); + return new string(chars); + } + + /// + /// Return new string with first found letter set to lower case + /// + /// + /// + public static string FirstLetterToLower(this string target) + { + char[] chars = target.ToCharArray(); + int index = chars.IndexOf(char.IsLetter); + chars[index] = char.ToLower(chars[index]); + return new string(chars); + } + + /// + /// Checks if string contains a 'word'. + /// 'Word' must be a separate member of the text, and not have any alphabetic characters next to it. + /// + /// + /// + /// + /// + /// + public static bool ContainsWord(this string source, string word, StringComparison comparison = StringComparison.InvariantCulture, int startIndex = 0) + { + int index = source.IndexOf(word, startIndex, comparison); + if (index == -1) + return false; + + if (index > 0) + { + char prev = source[index - 1]; + if (char.IsLetter(prev)) + return false; + } + + if (index + word.Length < source.Length) + { + char post = source[index + word.Length]; + if (char.IsLetter(post)) + return false; + } + + return true; + } +} + +/// +/// Contains extension method for number types +/// +public static class NumbersExtensions +{ + /// + /// Check if int value has int flag using bit compare + /// + /// + /// + /// + public static bool HasFlag(this int value, int flag) + => (value & flag) == flag; + + /// + /// Check if int value has enum flag using bit compare + /// + /// + /// + /// + /// + public static bool HasFlag(this int value, T flag) where T : Enum + => value.HasFlag(Convert.ToInt32(flag)); +} diff --git a/src/Telegrator/States/ChatIdResolver.cs b/src/Telegrator/States/ChatIdResolver.cs index 2159510..0def86e 100644 --- a/src/Telegrator/States/ChatIdResolver.cs +++ b/src/Telegrator/States/ChatIdResolver.cs @@ -1,21 +1,20 @@ using Telegram.Bot.Types; using Telegrator.Core.States; -namespace Telegrator.States +namespace Telegrator.States; + +/// +/// Resolves chat ID from Telegram updates for state management purposes. +/// Extracts the chat identifier from various types of updates to provide a consistent key for state operations. +/// +public class ChatIdResolver : IStateKeyResolver { /// - /// Resolves chat ID from Telegram updates for state management purposes. - /// Extracts the chat identifier from various types of updates to provide a consistent key for state operations. + /// Resolves the chat ID from a Telegram update. /// - public class ChatIdResolver : IStateKeyResolver - { - /// - /// Resolves the chat ID from a Telegram update. - /// - /// The Telegram update to extract the chat ID from. - /// The chat ID as a long value. - /// Thrown when the update does not contain a valid chat ID. - public string? ResolveKey(Update keySource) - => keySource.GetChatId()?.ToString() ?? throw new ArgumentException("Cannot resolve ChatID for this Update"); - } + /// The Telegram update to extract the chat ID from. + /// The chat ID as a long value. + /// Thrown when the update does not contain a valid chat ID. + public string? ResolveKey(Update keySource) + => keySource.GetChatId()?.ToString() ?? throw new ArgumentException("Cannot resolve ChatID for this Update"); } diff --git a/src/Telegrator/TelegramBotInfo.cs b/src/Telegrator/TelegramBotInfo.cs index 7fb2dd3..326e9f4 100644 --- a/src/Telegrator/TelegramBotInfo.cs +++ b/src/Telegrator/TelegramBotInfo.cs @@ -1,17 +1,16 @@ using Telegram.Bot.Types; using Telegrator.Core; -namespace Telegrator +namespace Telegrator; + +/// +/// Implementation of that provides bot information. +/// Contains metadata about the Telegram bot including user details. +/// +public class TelegramBotInfo(User user) : ITelegramBotInfo { /// - /// Implementation of that provides bot information. - /// Contains metadata about the Telegram bot including user details. + /// Gets the user information for the bot. /// - public class TelegramBotInfo(User user) : ITelegramBotInfo - { - /// - /// Gets the user information for the bot. - /// - public User User { get; } = user; - } + public User User { get; } = user; } diff --git a/src/Telegrator/Telegrator.csproj b/src/Telegrator/Telegrator.csproj index 00a2c30..229907f 100644 --- a/src/Telegrator/Telegrator.csproj +++ b/src/Telegrator/Telegrator.csproj @@ -44,8 +44,4 @@ - - - - diff --git a/src/Telegrator/TelegratorClient.cs b/src/Telegrator/TelegratorClient.cs index b64c0da..05d6f1f 100644 --- a/src/Telegrator/TelegratorClient.cs +++ b/src/Telegrator/TelegratorClient.cs @@ -6,116 +6,115 @@ using Telegrator.Mediation; using Telegrator.Providers; using Telegrator.States; -namespace Telegrator +namespace Telegrator; + +/// +/// Main client class for the Telegrator library. +/// Extends TelegramBotClient with reactive capabilities for handling updates. +/// +public class TelegratorClient : TelegramBotClient, ITelegratorBot, ICollectingProvider { /// - /// Main client class for the Telegrator library. - /// Extends TelegramBotClient with reactive capabilities for handling updates. + /// The update router for handling incoming updates. /// - public class TelegratorClient : TelegramBotClient, ITelegratorBot, ICollectingProvider + private IUpdateRouter? updateRouter = null; + + /// + public TelegratorOptions Options { get; private set; } + + /// + public IHandlersCollection Handlers { get; private set; } + + /// + public ITelegramBotInfo BotInfo { get; private set; } + + /// + public IUpdateRouter UpdateRouter { get => updateRouter ?? throw new Exception(); } + + /// + /// Initializes a new instance of the class with a bot token. + /// + /// The bot token from BotFather. + /// Optional HTTP client for making requests. + /// The cancellation token. + public TelegratorClient(string token, HttpClient? httpClient = null, CancellationToken cancellationToken = default) + : this(new TelegramBotClientOptions(token), null, httpClient, cancellationToken) { } + + /// + /// Initializes a new instance of the class with bot options. + /// + /// The Telegram bot client options. + /// Optional HTTP client for making requests. + /// The cancellation token. + public TelegratorClient(TelegramBotClientOptions options, HttpClient? httpClient = null, CancellationToken cancellationToken = default) + : this(options, null, httpClient, cancellationToken) { } + + /// + /// Initializes a new instance of the class with bot options and Telegrator options. + /// + /// The Telegram bot client options. + /// The Telegrator options. + /// Optional HTTP client for making requests. + /// The cancellation token. + public TelegratorClient(TelegramBotClientOptions options, TelegratorOptions? telegratorOptions, HttpClient? httpClient = null, CancellationToken cancellationToken = default) : base(options, httpClient, cancellationToken) { - /// - /// The update router for handling incoming updates. - /// - private IUpdateRouter? updateRouter = null; + Options = telegratorOptions ?? new TelegratorOptions(); + Handlers = new HandlersCollection(default); + BotInfo = new TelegramBotInfo(this.GetMe(cancellationToken).Result); + } - /// - public TelegratorOptions Options { get; private set; } + /// + /// Starts receiving updates from Telegram. + /// Initializes the update router and begins polling for updates. + /// + /// Optional receiver options for configuring update polling. + /// The cancellation token to stop receiving updates. + public void StartReceiving(ReceiverOptions? receiverOptions = null, CancellationToken cancellationToken = default) + { + if (Options.GlobalCancellationToken == CancellationToken.None) + Options.GlobalCancellationToken = cancellationToken; - /// - public IHandlersCollection Handlers { get; private set; } + HandlersProvider handlerProvider = new HandlersProvider(Handlers, Options); + AwaitingProvider awaitingProvider = new AwaitingProvider(Options); + DefaultStateStorage stateStorage = new DefaultStateStorage(); - /// - public ITelegramBotInfo BotInfo { get; private set; } + updateRouter = new UpdateRouter(handlerProvider, awaitingProvider, stateStorage, Options, BotInfo); + + // Log startup + TelegratorLogging.LogInformation($"Telegrator bot starting up - BotId: {BotInfo.User.Id}, Username: {BotInfo.User.Username}, MaxParallelHandlers: {Options.MaximumParallelWorkingHandlers ?? -1}"); - /// - public IUpdateRouter UpdateRouter { get => updateRouter ?? throw new Exception(); } + StartReceivingInternal(receiverOptions, cancellationToken); + } - /// - /// Initializes a new instance of the class with a bot token. - /// - /// The bot token from BotFather. - /// Optional HTTP client for making requests. - /// The cancellation token. - public TelegratorClient(string token, HttpClient? httpClient = null, CancellationToken cancellationToken = default) - : this(new TelegramBotClientOptions(token), null, httpClient, cancellationToken) { } - - /// - /// Initializes a new instance of the class with bot options. - /// - /// The Telegram bot client options. - /// Optional HTTP client for making requests. - /// The cancellation token. - public TelegratorClient(TelegramBotClientOptions options, HttpClient? httpClient = null, CancellationToken cancellationToken = default) - : this(options, null, httpClient, cancellationToken) { } - - /// - /// Initializes a new instance of the class with bot options and Telegrator options. - /// - /// The Telegram bot client options. - /// The Telegrator options. - /// Optional HTTP client for making requests. - /// The cancellation token. - public TelegratorClient(TelegramBotClientOptions options, TelegratorOptions? telegratorOptions, HttpClient? httpClient = null, CancellationToken cancellationToken = default) : base(options, httpClient, cancellationToken) - { - Options = telegratorOptions ?? new TelegratorOptions(); - Handlers = new HandlersCollection(default); - BotInfo = new TelegramBotInfo(this.GetMe(cancellationToken).Result); - } - - /// - /// Starts receiving updates from Telegram. - /// Initializes the update router and begins polling for updates. - /// - /// Optional receiver options for configuring update polling. - /// The cancellation token to stop receiving updates. - public void StartReceiving(ReceiverOptions? receiverOptions = null, CancellationToken cancellationToken = default) - { - if (Options.GlobalCancellationToken == CancellationToken.None) - Options.GlobalCancellationToken = cancellationToken; - - HandlersProvider handlerProvider = new HandlersProvider(Handlers, Options); - AwaitingProvider awaitingProvider = new AwaitingProvider(Options); - DefaultStateStorage stateStorage = new DefaultStateStorage(); - - updateRouter = new UpdateRouter(handlerProvider, awaitingProvider, stateStorage, Options, BotInfo); - - // Log startup - TelegratorLogging.LogInformation($"Telegrator bot starting up - BotId: {BotInfo.User.Id}, Username: {BotInfo.User.Username}, MaxParallelHandlers: {Options.MaximumParallelWorkingHandlers ?? -1}"); - - StartReceivingInternal(receiverOptions, cancellationToken); - } - - /// - /// Internal method that starts the update receiving process. - /// Handles the reactive update receiver and error handling. - /// - /// Optional receiver options for configuring update polling. - /// The cancellation token to stop receiving updates. - private async void StartReceivingInternal(ReceiverOptions? receiverOptions, CancellationToken cancellationToken) + /// + /// Internal method that starts the update receiving process. + /// Handles the reactive update receiver and error handling. + /// + /// Optional receiver options for configuring update polling. + /// The cancellation token to stop receiving updates. + private async void StartReceivingInternal(ReceiverOptions? receiverOptions, CancellationToken cancellationToken) + { + try { try { - try - { - await new DefaultUpdateReceiver(this, receiverOptions) - .ReceiveAsync(UpdateRouter, cancellationToken) - .ConfigureAwait(false); - } - catch (Exception exception) - { - await UpdateRouter - .HandleErrorAsync(this, exception, HandleErrorSource.FatalError, cancellationToken) - .ConfigureAwait(false); - } + await new DefaultUpdateReceiver(this, receiverOptions) + .ReceiveAsync(UpdateRouter, cancellationToken) + .ConfigureAwait(false); } - catch (OperationCanceledException) + catch (Exception exception) { - // Cancelled - TelegratorLogging.LogInformation("Telegrator bot stopped (cancelled)"); + await UpdateRouter + .HandleErrorAsync(this, exception, HandleErrorSource.FatalError, cancellationToken) + .ConfigureAwait(false); } } - - + catch (OperationCanceledException) + { + // Cancelled + TelegratorLogging.LogInformation("Telegrator bot stopped (cancelled)"); + } } + + } diff --git a/src/Telegrator/TelegratorOptions.cs b/src/Telegrator/TelegratorOptions.cs index c18173c..b0328e7 100644 --- a/src/Telegrator/TelegratorOptions.cs +++ b/src/Telegrator/TelegratorOptions.cs @@ -1,54 +1,53 @@ -namespace Telegrator +namespace Telegrator; + +/// +/// Configuration options for Telegram bot behavior and execution settings. +/// Controls various aspects of bot operation including concurrency, routing, and execution policies. +/// +public class TelegratorOptions { /// - /// Configuration options for Telegram bot behavior and execution settings. - /// Controls various aspects of bot operation including concurrency, routing, and execution policies. + /// Gets or sets the bot token. /// - public class TelegratorOptions - { - /// - /// Gets or sets the bot token. - /// - public string Token { get; set; } = string.Empty; + public string Token { get; set; } = string.Empty; - /// - /// Gets or sets the base URL for the bot API. - /// - public string? BaseUrl { get; set; } = null; + /// + /// Gets or sets the base URL for the bot API. + /// + public string? BaseUrl { get; set; } = null; - /// - /// Gets or sets whether to use the test environment. - /// - public bool UseTestEnvironment { get; set; } = false; + /// + /// Gets or sets whether to use the test environment. + /// + public bool UseTestEnvironment { get; set; } = false; - /// - /// Gets or sets the retry threshold in seconds. - /// - public int RetryThreshold { get; set; } = 60; + /// + /// Gets or sets the retry threshold in seconds. + /// + public int RetryThreshold { get; set; } = 60; - /// - /// Gets or sets the number of retry attempts. - /// - public int RetryCount { get; set; } = 3; + /// + /// Gets or sets the number of retry attempts. + /// + public int RetryCount { get; set; } = 3; - /// - /// Gets or sets the maximum number of parallel working handlers. Null means no limit. - /// - public int? MaximumParallelWorkingHandlers { get; set; } = null; + /// + /// Gets or sets the maximum number of parallel working handlers. Null means no limit. + /// + public int? MaximumParallelWorkingHandlers { get; set; } = null; - /// - /// Gets or sets a value indicating whether awaiting handlers should be routed separately from regular handlers. - /// - public bool ExclusiveAwaitingHandlerRouting { get; set; } = false; + /// + /// Gets or sets a value indicating whether awaiting handlers should be routed separately from regular handlers. + /// + public bool ExclusiveAwaitingHandlerRouting { get; set; } = false; - /// - /// Gets or sets a value indicating whether to exclude intersecting command aliases. - /// - public bool ExceptIntersectingCommandAliases { get; set; } = true; + /// + /// Gets or sets a value indicating whether to exclude intersecting command aliases. + /// + public bool ExceptIntersectingCommandAliases { get; set; } = true; - /// - /// Gets or sets the global cancellation token for all bot operations. - /// - public CancellationToken GlobalCancellationToken { get; set; } = default; - } + /// + /// Gets or sets the global cancellation token for all bot operations. + /// + public CancellationToken GlobalCancellationToken { get; set; } = default; } diff --git a/src/Telegrator/TypesExtensions.cs b/src/Telegrator/TypesExtensions.cs index 3db5e4c..915b964 100644 --- a/src/Telegrator/TypesExtensions.cs +++ b/src/Telegrator/TypesExtensions.cs @@ -11,725 +11,724 @@ using Telegrator.Core.States; using Telegrator.Handlers.Building; using Telegrator.States; -namespace Telegrator +namespace Telegrator; + +/// +/// Provides usefull helper methods for messages +/// +public static class MessageExtensions { /// - /// Provides usefull helper methods for messages + /// Substrings entity content from text /// - public static class MessageExtensions + /// + /// + /// + /// + public static string SubstringEntity(this Message message, MessageEntity entity) { - /// - /// Substrings entity content from text - /// - /// - /// - /// - /// - public static string SubstringEntity(this Message message, MessageEntity entity) - { - if (message.Text == null || string.IsNullOrEmpty(message.Text)) // DO NOT CHANGE! Compiler SOMEHOW warnings "probably null" here - throw new ArgumentNullException(nameof(message), "Cannot substring entity from message with text that null or empty"); + if (message.Text == null || string.IsNullOrEmpty(message.Text)) // DO NOT CHANGE! Compiler SOMEHOW warnings "probably null" here + throw new ArgumentNullException(nameof(message), "Cannot substring entity from message with text that null or empty"); - return message.Text.Substring(entity.Offset, entity.Length); - } - - /// - /// Checkes if sent contains command. Automatically cuts bot name from it - /// - /// - /// - /// - public static bool IsCommand(this Message message, out string? command) - { - command = null; - - if (message is not { Entities.Length: > 0, Text.Length: > 0 }) - return false; - - MessageEntity commandEntity = message.Entities[0]; - if (commandEntity.Type != MessageEntityType.BotCommand) - return false; - - if (commandEntity.Offset != 0) - return false; - - command = message.Text.Substring(1, commandEntity.Length - 1); - if (command.Contains('@')) - { - string[] split = command.Split('@'); - command = split[0]; - } - - return true; - } - - /// - /// Checkes if sent contains command. Automatically cuts bot name from it - /// - /// - /// - /// - /// - public static bool IsCommand(this Message message, out string? command, out string? args) - { - command = null; - args = null; - - if (message is not { Entities.Length: > 0, Text.Length: > 0 }) - return false; - - MessageEntity commandEntity = message.Entities[0]; - if (commandEntity.Type != MessageEntityType.BotCommand) - return false; - - if (commandEntity.Offset != 0) - return false; - - command = message.Text.Substring(1, commandEntity.Length - 1); - if (message.Text.Length > command.Length) - { - args = message.Text.Substring(command.Length); - } - - if (command.Contains('@')) - { - string[] split = command.Split('@'); - command = split[0]; - } - - return true; - } - - /// - /// Split message text into arguments, ignoring command instance. Splits by space character - /// - /// - /// - /// - /// - /// - public static string[] SplitArgs(this Message message) - { - if (message.Text is not { Length: > 0 } text) - throw new ArgumentNullException(nameof(message), "Command text cannot be null or empty"); - - if (!text.Contains(' ')) - throw new MissingMemberException("Command doesn't contains arguments"); - - if (!message.IsCommand(out _, out string? argsStr)) - throw new InvalidDataException("Message does not contain a command"); - - return argsStr?.Split([' '], StringSplitOptions.RemoveEmptyEntries) ?? []; - } - - /// - /// Tries to split message text into arguments, ignoring command instance. Splits by space character. Exception-free version of - /// - /// - /// - /// - public static bool TrySplitArgs(this Message message, out string[]? args) - { - args = null; - - if (message is not { Text.Length: > 0 }) - return false; - - if (!message.Text.Contains(' ')) - return false; - - args = null; - if (!message.IsCommand(out _, out string? argsStr)) - return false; - - args = argsStr?.Split([' '], StringSplitOptions.RemoveEmptyEntries); - return true; - } + return message.Text.Substring(entity.Offset, entity.Length); } /// - /// Extension methods for handler containers. - /// Provides convenient methods for creating awaiter builders and state keeping. + /// Checkes if sent contains command. Automatically cuts bot name from it /// - public static class HandlerContainerExtensions + /// + /// + /// + public static bool IsCommand(this Message message, out string? command) { - /// - /// Creates an awaiter builder for a specific update type. - /// - /// The type of update to await. - /// The handler container. - /// The type of update to await. - /// An awaiter builder for the specified update type. - public static IAwaiterHandlerBuilder AwaitUpdate(this IHandlerContainer container, UpdateType updateType) where TUpdate : class - => container.AwaitingProvider.CreateAbstract(updateType, container.HandlingUpdate); + command = null; - /// - /// Creates an awaiter builder for any update type. - /// - /// The handler container. - /// An awaiter builder for any update type. - public static IAwaiterHandlerBuilder AwaitAny(this IHandlerContainer container) - => container.AwaitUpdate(UpdateType.Unknown); + if (message is not { Entities.Length: > 0, Text.Length: > 0 }) + return false; - /// - /// Creates an awaiter builder for message updates. - /// - /// The handler container. - /// An awaiter builder for message updates. - public static IAwaiterHandlerBuilder AwaitMessage(this IHandlerContainer container) - => container.AwaitUpdate(UpdateType.Message); + MessageEntity commandEntity = message.Entities[0]; + if (commandEntity.Type != MessageEntityType.BotCommand) + return false; - /// - /// Creates an awaiter builder for callback query updates. - /// - /// The handler container. - /// An awaiter builder for callback query updates. - public static IAwaiterHandlerBuilder AwaitCallbackQuery(this IHandlerContainer container) - => container.AwaitUpdate(UpdateType.CallbackQuery); + if (commandEntity.Offset != 0) + return false; + + command = message.Text.Substring(1, commandEntity.Length - 1); + if (command.Contains('@')) + { + string[] split = command.Split('@'); + command = split[0]; + } + + return true; } /// - /// Extensions methods for Awaiter Handler Builders + /// Checkes if sent contains command. Automatically cuts bot name from it /// - public static class AwaiterHandlerBuilderExtensions + /// + /// + /// + /// + public static bool IsCommand(this Message message, out string? command, out string? args) { - /// - /// Awaits an update using the chat id key resolver and cancellation token. - /// - /// - /// - /// - /// - public static async Task ByChatId(this IAwaiterHandlerBuilder builder, CancellationToken cancellationToken = default) where TUpdate : class - => await builder.Await(new ChatIdResolver(), cancellationToken); + command = null; + args = null; - /// - /// Awaits an update using the sender id key resolver and cancellation token. - /// - /// - /// - /// - /// - public static async Task BySenderId(this IAwaiterHandlerBuilder builder, CancellationToken cancellationToken = default) where TUpdate : class - => await builder.Await(new SenderIdResolver(), cancellationToken); + if (message is not { Entities.Length: > 0, Text.Length: > 0 }) + return false; + + MessageEntity commandEntity = message.Entities[0]; + if (commandEntity.Type != MessageEntityType.BotCommand) + return false; + + if (commandEntity.Offset != 0) + return false; + + command = message.Text.Substring(1, commandEntity.Length - 1); + if (message.Text.Length > command.Length) + { + args = message.Text.Substring(command.Length); + } + + if (command.Contains('@')) + { + string[] split = command.Split('@'); + command = split[0]; + } + + return true; } /// - /// Extensions methods for awaiting providers - /// Provides convenient methods for creating awaiter builders. + /// Split message text into arguments, ignoring command instance. Splits by space character /// - public static class AwaitingProviderExtensions + /// + /// + /// + /// + /// + public static string[] SplitArgs(this Message message) { - /// - /// Creates an awaiter handler builder for a specific update type. - /// - /// The type of update to await. - /// - /// The type of update to await. - /// The update that triggered the awaiter creation. - /// An awaiter handler builder for the specified update type. - public static IAwaiterHandlerBuilder CreateAbstract(this IAwaitingProvider awaitingProvider, UpdateType updateType, Update handlingUpdate) where TUpdate : class - => new AwaiterHandlerBuilder(updateType, handlingUpdate, awaitingProvider); + if (message.Text is not { Length: > 0 } text) + throw new ArgumentNullException(nameof(message), "Command text cannot be null or empty"); - /// - /// Creates an awaiter builder for any update type. - /// - /// - /// - /// An awaiter builder for any update type. - public static IAwaiterHandlerBuilder AwaitAny(this IAwaitingProvider awaitingProvider, Update handlingUpdate) - => awaitingProvider.CreateAbstract(UpdateType.Unknown, handlingUpdate); + if (!text.Contains(' ')) + throw new MissingMemberException("Command doesn't contains arguments"); - /// - /// Creates an awaiter builder for message updates. - /// - /// - /// - /// An awaiter builder for message updates. - public static IAwaiterHandlerBuilder AwaitMessage(this IAwaitingProvider awaitingProvider, Update handlingUpdate) - => awaitingProvider.CreateAbstract(UpdateType.Message, handlingUpdate); + if (!message.IsCommand(out _, out string? argsStr)) + throw new InvalidDataException("Message does not contain a command"); - /// - /// Creates an awaiter builder for callback query updates. - /// - /// - /// - /// An awaiter builder for callback query updates. - public static IAwaiterHandlerBuilder AwaitCallbackQuery(this IAwaitingProvider awaitingProvider, Update handlingUpdate) - => awaitingProvider.CreateAbstract(UpdateType.CallbackQuery, handlingUpdate); + return argsStr?.Split([' '], StringSplitOptions.RemoveEmptyEntries) ?? []; } /// - /// Extesions method for handlers providers + /// Tries to split message text into arguments, ignoring command instance. Splits by space character. Exception-free version of /// - public static class HandlersProviderExtensions + /// + /// + /// + public static bool TrySplitArgs(this Message message, out string[]? args) { - /// - /// Gets the list of bot commands supported by the provider. - /// - /// An enumerable of bot commands. - public static IEnumerable GetBotCommands(this IHandlersProvider provider, CancellationToken cancellationToken = default) - { - if (!provider.TryGetDescriptorList(UpdateType.Message, out HandlerDescriptorList? list)) - yield break; + args = null; - foreach (BotCommand botCommand in list - .Select(descriptor => descriptor.HandlerType) - .SelectMany(handlerType => handlerType.GetCustomAttributes() - .SelectMany(attribute => attribute.Alliases.Select(alias => new BotCommand(alias, attribute.Description))))) - { - cancellationToken.ThrowIfCancellationRequested(); - yield return botCommand; - } - } + if (message is not { Text.Length: > 0 }) + return false; + + if (!message.Text.Contains(' ')) + return false; + + args = null; + if (!message.IsCommand(out _, out string? argsStr)) + return false; + + args = argsStr?.Split([' '], StringSplitOptions.RemoveEmptyEntries); + return true; } +} + +/// +/// Extension methods for handler containers. +/// Provides convenient methods for creating awaiter builders and state keeping. +/// +public static class HandlerContainerExtensions +{ + /// + /// Creates an awaiter builder for a specific update type. + /// + /// The type of update to await. + /// The handler container. + /// The type of update to await. + /// An awaiter builder for the specified update type. + public static IAwaiterHandlerBuilder AwaitUpdate(this IHandlerContainer container, UpdateType updateType) where TUpdate : class + => container.AwaitingProvider.CreateAbstract(updateType, container.HandlingUpdate); /// - /// Provides extension methods for to easily initialize state machines. + /// Creates an awaiter builder for any update type. /// - public static class StateStorageExtensions - { - /// - /// Initializes a state machine using the default for the specified update. - /// - /// The enum type representing the state. - /// The storage mechanism used to persist the state. - /// The update context to resolve the state key from. - /// A new instance of . - public static StateMachine, TState> GetStateMachine(this IStateStorage stateStorage, Update handlingUpdate) - where TState : struct, Enum, IEquatable - => new StateMachine, TState>(stateStorage, handlingUpdate); - - /// - /// Initializes a specific custom state machine for the specified update. - /// - /// The type of the state machine logic implementation. - /// The type of the state. - /// The storage mechanism used to persist the state. - /// The update context to resolve the state key from. - /// A new instance of . - public static StateMachine GetStateMachine(this IStateStorage stateStorage, Update handlingUpdate) - where TMachine : IStateMachine, new() - where TState : IEquatable - => new StateMachine(stateStorage, handlingUpdate); - - /// - /// Initializes a state machine and explicitly configures it to resolve keys by the chat ID. - /// - /// The type of the state machine logic implementation. - /// The type of the state. - /// The storage mechanism used to persist the state. - /// The update context to resolve the state key from. - /// A configured instance of . - public static StateMachine ByChatId(this IStateStorage stateStorage, Update handlingUpdate) - where TMachine : IStateMachine, new() - where TState : IEquatable - => new StateMachine(stateStorage, handlingUpdate).ByChatId(); - - /// - /// Initializes a state machine and explicitly configures it to resolve keys by the sender (user) ID. - /// - /// The type of the state machine logic implementation. - /// The type of the state. - /// The storage mechanism used to persist the state. - /// The update context to resolve the state key from. - /// A configured instance of . - public static StateMachine BySenderId(this IStateStorage stateStorage, Update handlingUpdate) - where TMachine : IStateMachine, new() - where TState : IEquatable - => new StateMachine(stateStorage, handlingUpdate).BySenderId(); - } + /// The handler container. + /// An awaiter builder for any update type. + public static IAwaiterHandlerBuilder AwaitAny(this IHandlerContainer container) + => container.AwaitUpdate(UpdateType.Unknown); /// - /// Provides fluent extension methods for configuring instances. + /// Creates an awaiter builder for message updates. /// - public static class StateMachineExtensions - { - /// - /// Configures the state machine to use a for state key resolution. - /// - /// The type of the state machine logic implementation. - /// The type of the state. - /// The state machine instance to configure. - /// The same state machine instance for method chaining. - public static StateMachine ByChatId(this StateMachine stateMachine) - where TMachine : IStateMachine, new() - where TState : IEquatable - { - stateMachine.KeyResolver = new ChatIdResolver(); - return stateMachine; - } - - /// - /// Configures the state machine to use a for state key resolution. - /// - /// The type of the state machine logic implementation. - /// The type of the state. - /// The state machine instance to configure. - /// The same state machine instance for method chaining. - public static StateMachine BySenderId(this StateMachine stateMachine) - where TMachine : IStateMachine, new() - where TState : IEquatable - { - stateMachine.KeyResolver = new SenderIdResolver(); - return stateMachine; - } - } + /// The handler container. + /// An awaiter builder for message updates. + public static IAwaiterHandlerBuilder AwaitMessage(this IHandlerContainer container) + => container.AwaitUpdate(UpdateType.Message); /// - /// Extension methods for handlers collections. - /// Provides convenient methods for creating implicit handlers. + /// Creates an awaiter builder for callback query updates. /// - public static partial class HandlersCollectionExtensions - { - // TODO: rewrite collecting system, add certain hardcoded Type searching, generated automatically - private static readonly string[] skippingAssemblies = [ - "System", "Microsoft", "Windows", "Newtonsoft", "Serilog", "NLog", - "AutoMapper", "MediatR", "Dapper", "RestSharp", "Polly", "FluentValidation", - "NUnit", "Xunit", "Moq", "FluentAssertions", "Castle", "Autofac", "Ninject", - "StackExchange", "RabbitMQ", "Quartz", "Hangfire", "Npgsql", "MySql", "Oracle", - "Bogus", "CsvHelper", "Grpc", "Swashbuckle", "MassTransit", "AngleSharp", - "Ocelot", "BouncyCastle", "IdentityModel", "Telegrator" - ]; + /// The handler container. + /// An awaiter builder for callback query updates. + public static IAwaiterHandlerBuilder AwaitCallbackQuery(this IHandlerContainer container) + => container.AwaitUpdate(UpdateType.CallbackQuery); +} - /// - /// Collects all public handlers from the current app domain. - /// Scans for types that implement handlers and adds them to the collection. - /// - /// This collection instance for method chaining. - /// Thrown when the entry assembly cannot be found. - public static IHandlersCollection CollectHandlersDomainWide(this IHandlersCollection handlers) - { - AppDomain.CurrentDomain - .GetAssemblies() - .Where(ass => skippingAssemblies.All(skip => !ass.FullName.StartsWith(skip))) - .ForEach(ass => handlers.CollectHandlersAssemblyWide(ass)); - - return handlers; - } - - /// - /// Collects all public handlers from the calling this function assembly. - /// Scans for types that implement handlers and adds them to the collection. - /// - /// This collection instance for method chaining. - /// Thrown when the entry assembly cannot be found. - public static IHandlersCollection CollectHandlersAssemblyWide(this IHandlersCollection handlers, Assembly? collectingTarget = null) - { - (collectingTarget ?? Assembly.GetCallingAssembly()) - .GetExportedTypes() - .Where(type => type.GetCustomAttribute() == null) - .Where(type => type.IsHandlerRealization()) - .ForEach(type => handlers.AddHandler(type)); - - return handlers; - } - - /// - /// Creates a handler builder for a specific update type. - /// - /// The type of update to handle. - /// The handlers collection. - /// The type of update to handle. - /// A handler builder for the specified update type. - public static HandlerBuilder CreateHandler(this IHandlersCollection handlers, UpdateType updateType) where TUpdate : class - => new HandlerBuilder(updateType, handlers); - - /// - /// Creates a handler builder for any update type. - /// - /// The handlers collection. - /// A handler builder for any update type. - public static HandlerBuilder CreateAny(this IHandlersCollection handlers) - => handlers.CreateHandler(UpdateType.Unknown); - - /// - /// Creates a handler builder for message updates. - /// - /// The handlers collection. - /// A handler builder for message updates. - public static HandlerBuilder CreateMessage(this IHandlersCollection handlers) - => handlers.CreateHandler(UpdateType.Message); - - /// - /// Creates a handler builder for callback query updates. - /// - /// The handlers collection. - /// A handler builder for callback query updates. - public static HandlerBuilder CreateCallbackQuery(this IHandlersCollection handlers) - => handlers.CreateHandler(UpdateType.CallbackQuery); - - /// - /// Adds a handler type to the collection. - /// - /// The handlers collection. - /// The type of handler to add. - /// This collection instance for method chaining. - public static IHandlersCollection AddHandler(this IHandlersCollection handlers) where THandler : UpdateHandlerBase - => handlers.AddHandler(typeof(THandler)); - - /// - /// Adds a handler type to the collection. - /// - /// The handlers collection. - /// The type of handler to add. - /// This collection instance for method chaining. - /// Thrown when the type is not a valid handler implementation. - public static IHandlersCollection AddHandler(this IHandlersCollection handlers, Type handlerType) - { - if (!handlerType.IsHandlerRealization()) - throw new Exception(); - - if (handlerType.IsCustomDescriptorsProvider()) - { - ICustomDescriptorsProvider provider = (ICustomDescriptorsProvider)Activator.CreateInstance(handlerType); - foreach (HandlerDescriptor handlerDescriptor in provider.DescribeHandlers()) - handlers.AddDescriptor(handlerDescriptor); - } - else - { - HandlerDescriptor descriptor = new HandlerDescriptor(DescriptorType.General, handlerType); - handlers.AddDescriptor(descriptor); - } - - return handlers; - } - - /// - /// Creates implicit handler from method - /// - /// - /// - /// - /// - public static IHandlersCollection AddMethod(this IHandlersCollection handlers, AbstractHandlerAction method) where TUpdate : class - { - MethodHandlerDescriptor descriptor = new MethodHandlerDescriptor(method); - handlers.AddDescriptor(descriptor); - return handlers; - } - } +/// +/// Extensions methods for Awaiter Handler Builders +/// +public static class AwaiterHandlerBuilderExtensions +{ + /// + /// Awaits an update using the chat id key resolver and cancellation token. + /// + /// + /// + /// + /// + public static async Task ByChatId(this IAwaiterHandlerBuilder builder, CancellationToken cancellationToken = default) where TUpdate : class + => await builder.Await(new ChatIdResolver(), cancellationToken); /// - /// Provides extension methods for working with Telegram Update objects. + /// Awaits an update using the sender id key resolver and cancellation token. /// - public static partial class UpdateExtensions - { - /// - /// Selects from Update an object from which you can get the sender's ID - /// - /// - /// Sender's ID - public static long? GetSenderId(this Update update) => update switch - { - { Message.From: { } from } => from.Id, - { Message.SenderChat: { } chat } => chat.Id, - { EditedMessage.From: { } from } => from.Id, - { EditedMessage.SenderChat: { } chat } => chat.Id, - { ChannelPost.From: { } from } => from.Id, - { ChannelPost.SenderChat: { } chat } => chat.Id, - { EditedChannelPost.From: { } from } => from.Id, - { EditedChannelPost.SenderChat: { } chat } => chat.Id, - { CallbackQuery.From: { } from } => from.Id, - { InlineQuery.From: { } from } => from.Id, - { PollAnswer.User: { } user } => user.Id, - { PreCheckoutQuery.From: { } from } => from.Id, - { ShippingQuery.From: { } from } => from.Id, - { ChosenInlineResult.From: { } from } => from.Id, - { ChatJoinRequest.From: { } from } => from.Id, - { ChatMember.From: { } from } => from.Id, - { MyChatMember.From: { } from } => from.Id, - _ => null - }; + /// + /// + /// + /// + public static async Task BySenderId(this IAwaiterHandlerBuilder builder, CancellationToken cancellationToken = default) where TUpdate : class + => await builder.Await(new SenderIdResolver(), cancellationToken); +} - /// - /// Selects from Update an object from which you can get the chat's ID - /// - /// - /// Sender's ID - public static long? GetChatId(this Update update) => update switch - { - { Message.Chat: { } chat } => chat.Id, - { Message.SenderChat: { } chat } => chat.Id, - { EditedMessage.Chat: { } chat } => chat.Id, - { EditedMessage.SenderChat: { } chat } => chat.Id, - { ChannelPost.Chat: { } chat } => chat.Id, - { ChannelPost.SenderChat: { } chat } => chat.Id, - { EditedChannelPost.Chat: { } chat } => chat.Id, - { EditedChannelPost.SenderChat: { } chat } => chat.Id, - { CallbackQuery.Message.Chat: { } chat } => chat.Id, - { ChatJoinRequest.Chat: { } chat } => chat.Id, - { ChatMember.Chat: { } chat } => chat.Id, - { MyChatMember.Chat: { } chat } => chat.Id, - _ => null - }; - - /// - /// Selects from an object that contains information about the update - /// - /// - /// - public static object GetActualUpdateObject(this Update update) => update switch - { - { Message: { } message } => message, - { EditedMessage: { } editedMessage } => editedMessage, - { ChannelPost: { } channelPost } => channelPost, - { EditedChannelPost: { } editedChannelPost } => editedChannelPost, - { BusinessConnection: { } businessConnection } => businessConnection, - { BusinessMessage: { } businessMessage } => businessMessage, - { EditedBusinessMessage: { } editedBusinessMessage } => editedBusinessMessage, - { DeletedBusinessMessages: { } deletedBusinessMessages } => deletedBusinessMessages, - { MessageReaction: { } messageReaction } => messageReaction, - { MessageReactionCount: { } messageReactionCount } => messageReactionCount, - { InlineQuery: { } inlineQuery } => inlineQuery, - { ChosenInlineResult: { } chosenInlineResult } => chosenInlineResult, - { CallbackQuery: { } callbackQuery } => callbackQuery, - { ShippingQuery: { } shippingQuery } => shippingQuery, - { PreCheckoutQuery: { } preCheckoutQuery } => preCheckoutQuery, - { PurchasedPaidMedia: { } purchasedPaidMedia } => purchasedPaidMedia, - { Poll: { } poll } => poll, - { PollAnswer: { } pollAnswer } => pollAnswer, - { MyChatMember: { } myChatMember } => myChatMember, - { ChatMember: { } chatMember } => chatMember, - { ChatJoinRequest: { } chatJoinRequest } => chatJoinRequest, - { ChatBoost: { } chatBoost } => chatBoost, - { RemovedChatBoost: { } removedChatBoost } => removedChatBoost, - _ => update - }; - - /// - /// Selecting corresponding s for 's sub-type - /// - /// - public static UpdateType[] GetAllowedUpdateTypes(this Type type) => type.FullName switch - { - "Telegram.Bot.Types.Message" => UpdateTypeExtensions.MessageTypes, - "Telegram.Bot.Types.ChatMemberUpdated" => [UpdateType.MyChatMember, UpdateType.ChatMember], - "Telegram.Bot.Types.InlineQuery" => [UpdateType.InlineQuery], - "Telegram.Bot.Types.ChosenInlineResult" => [UpdateType.ChosenInlineResult], - "Telegram.Bot.Types.CallbackQuery" => [UpdateType.CallbackQuery], - "Telegram.Bot.Types.ShippingQuery" => [UpdateType.ShippingQuery], - "Telegram.Bot.Types.PreCheckoutQuery" => [UpdateType.PreCheckoutQuery], - "Telegram.Bot.Types.Poll" => [UpdateType.Poll], - "Telegram.Bot.Types.PollAnswer" => [UpdateType.PollAnswer], - "Telegram.Bot.Types.ChatJoinRequest" => [UpdateType.ChatJoinRequest], - "Telegram.Bot.Types.MessageReactionUpdated" => [UpdateType.MessageReaction], - "Telegram.Bot.TypesMessageReactionCountUpdated" => [UpdateType.MessageReactionCount], - "Telegram.Bot.Types.ChatBoostUpdated" => [UpdateType.ChatBoost], - "Telegram.Bot.Types.ChatBoostRemoved" => [UpdateType.RemovedChatBoost], - "Telegram.Bot.Types.BusinessConnection" => [UpdateType.BusinessConnection], - "Telegram.Bot.Types.BusinessMessagesDeleted" => [UpdateType.DeletedBusinessMessages], - "Telegram.Bot.Types.PaidMediaPurchased" => [UpdateType.PurchasedPaidMedia], - "Telegram.Bot.Types.Update" => Update.AllTypes, - _ => [] - }; - - /// - /// Selecting corresponding s for 's sub-type - /// - /// - /// - public static UpdateType[] GetAllowedUpdateTypes() where T : class - => GetAllowedUpdateTypes(typeof(T)); - - /// - /// Selects from an that contains information about the update - /// - /// - /// - public static T GetActualUpdateObject(this Update update) - { - if (update is T upd) - return upd; - - object actualUpdate = update.GetActualUpdateObject() ?? throw new Exception(); - if (actualUpdate is not T actualCasted) - throw new Exception(); - - return actualCasted; - } - } +/// +/// Extensions methods for awaiting providers +/// Provides convenient methods for creating awaiter builders. +/// +public static class AwaitingProviderExtensions +{ + /// + /// Creates an awaiter handler builder for a specific update type. + /// + /// The type of update to await. + /// + /// The type of update to await. + /// The update that triggered the awaiter creation. + /// An awaiter handler builder for the specified update type. + public static IAwaiterHandlerBuilder CreateAbstract(this IAwaitingProvider awaitingProvider, UpdateType updateType, Update handlingUpdate) where TUpdate : class + => new AwaiterHandlerBuilder(updateType, handlingUpdate, awaitingProvider); /// - /// Provides extension methods for working with UpdateType enums. + /// Creates an awaiter builder for any update type. /// - public static partial class UpdateTypeExtensions + /// + /// + /// An awaiter builder for any update type. + public static IAwaiterHandlerBuilder AwaitAny(this IAwaitingProvider awaitingProvider, Update handlingUpdate) + => awaitingProvider.CreateAbstract(UpdateType.Unknown, handlingUpdate); + + /// + /// Creates an awaiter builder for message updates. + /// + /// + /// + /// An awaiter builder for message updates. + public static IAwaiterHandlerBuilder AwaitMessage(this IAwaitingProvider awaitingProvider, Update handlingUpdate) + => awaitingProvider.CreateAbstract(UpdateType.Message, handlingUpdate); + + /// + /// Creates an awaiter builder for callback query updates. + /// + /// + /// + /// An awaiter builder for callback query updates. + public static IAwaiterHandlerBuilder AwaitCallbackQuery(this IAwaitingProvider awaitingProvider, Update handlingUpdate) + => awaitingProvider.CreateAbstract(UpdateType.CallbackQuery, handlingUpdate); +} + +/// +/// Extesions method for handlers providers +/// +public static class HandlersProviderExtensions +{ + /// + /// Gets the list of bot commands supported by the provider. + /// + /// An enumerable of bot commands. + public static IEnumerable GetBotCommands(this IHandlersProvider provider, CancellationToken cancellationToken = default) { - /// - /// 's that contain a message - /// - public static readonly UpdateType[] MessageTypes = - [ - UpdateType.Message, - UpdateType.EditedMessage, - UpdateType.BusinessMessage, - UpdateType.EditedBusinessMessage, - UpdateType.ChannelPost, - UpdateType.EditedChannelPost - ]; + if (!provider.TryGetDescriptorList(UpdateType.Message, out HandlerDescriptorList? list)) + yield break; - /// - /// Dictionary of s that suppresses to generic type for handling types that has complex multi-type handlers - /// - public static readonly Dictionary SuppressTypes = new Dictionary() + foreach (BotCommand botCommand in list + .Select(descriptor => descriptor.HandlerType) + .SelectMany(handlerType => handlerType.GetCustomAttributes() + .SelectMany(attribute => attribute.Alliases.Select(alias => new BotCommand(alias, attribute.Description))))) { - { UpdateType.ChosenInlineResult, UpdateType.InlineQuery } - }; - - /// - /// Checks if matches one of the 's give on - /// - /// - /// - /// - public static bool IsUpdateObjectAllowed(this UpdateType[] allowedTypes) where T : class - { - return allowedTypes.Any(t => t.IsValidUpdateObject()); - } - - /// - /// Checks if matches the given - /// - /// - /// - /// - public static bool IsValidUpdateObject(this UpdateType updateType) where TUpdate : class - { - if (typeof(TUpdate) == typeof(Update)) - return true; - - return typeof(TUpdate).Equals(updateType.ReflectUpdateObject()); - } - - /// - /// Returns an update object corresponding to the . - /// - /// - /// - public static Type? ReflectUpdateObject(this UpdateType updateType) - { - return updateType switch - { - UpdateType.Message or UpdateType.EditedMessage or UpdateType.BusinessMessage or UpdateType.EditedBusinessMessage or UpdateType.ChannelPost or UpdateType.EditedChannelPost => typeof(Message), - UpdateType.MyChatMember => typeof(ChatMemberUpdated), - UpdateType.ChatMember => typeof(ChatMemberUpdated), - UpdateType.InlineQuery => typeof(InlineQuery), - UpdateType.ChosenInlineResult => typeof(ChosenInlineResult), - UpdateType.CallbackQuery => typeof(CallbackQuery), - UpdateType.ShippingQuery => typeof(ShippingQuery), - UpdateType.PreCheckoutQuery => typeof(PreCheckoutQuery), - UpdateType.Poll => typeof(Poll), - UpdateType.PollAnswer => typeof(PollAnswer), - UpdateType.ChatJoinRequest => typeof(ChatJoinRequest), - UpdateType.MessageReaction => typeof(MessageReactionUpdated), - UpdateType.MessageReactionCount => typeof(MessageReactionCountUpdated), - UpdateType.ChatBoost => typeof(ChatBoostUpdated), - UpdateType.RemovedChatBoost => typeof(ChatBoostRemoved), - UpdateType.BusinessConnection => typeof(BusinessConnection), - UpdateType.DeletedBusinessMessages => typeof(BusinessMessagesDeleted), - UpdateType.PurchasedPaidMedia => typeof(PaidMediaPurchased), - _ or UpdateType.Unknown => typeof(Update) - }; + cancellationToken.ThrowIfCancellationRequested(); + yield return botCommand; } } } + +/// +/// Provides extension methods for to easily initialize state machines. +/// +public static class StateStorageExtensions +{ + /// + /// Initializes a state machine using the default for the specified update. + /// + /// The enum type representing the state. + /// The storage mechanism used to persist the state. + /// The update context to resolve the state key from. + /// A new instance of . + public static StateMachine, TState> GetStateMachine(this IStateStorage stateStorage, Update handlingUpdate) + where TState : struct, Enum, IEquatable + => new StateMachine, TState>(stateStorage, handlingUpdate); + + /// + /// Initializes a specific custom state machine for the specified update. + /// + /// The type of the state machine logic implementation. + /// The type of the state. + /// The storage mechanism used to persist the state. + /// The update context to resolve the state key from. + /// A new instance of . + public static StateMachine GetStateMachine(this IStateStorage stateStorage, Update handlingUpdate) + where TMachine : IStateMachine, new() + where TState : IEquatable + => new StateMachine(stateStorage, handlingUpdate); + + /// + /// Initializes a state machine and explicitly configures it to resolve keys by the chat ID. + /// + /// The type of the state machine logic implementation. + /// The type of the state. + /// The storage mechanism used to persist the state. + /// The update context to resolve the state key from. + /// A configured instance of . + public static StateMachine ByChatId(this IStateStorage stateStorage, Update handlingUpdate) + where TMachine : IStateMachine, new() + where TState : IEquatable + => new StateMachine(stateStorage, handlingUpdate).ByChatId(); + + /// + /// Initializes a state machine and explicitly configures it to resolve keys by the sender (user) ID. + /// + /// The type of the state machine logic implementation. + /// The type of the state. + /// The storage mechanism used to persist the state. + /// The update context to resolve the state key from. + /// A configured instance of . + public static StateMachine BySenderId(this IStateStorage stateStorage, Update handlingUpdate) + where TMachine : IStateMachine, new() + where TState : IEquatable + => new StateMachine(stateStorage, handlingUpdate).BySenderId(); +} + +/// +/// Provides fluent extension methods for configuring instances. +/// +public static class StateMachineExtensions +{ + /// + /// Configures the state machine to use a for state key resolution. + /// + /// The type of the state machine logic implementation. + /// The type of the state. + /// The state machine instance to configure. + /// The same state machine instance for method chaining. + public static StateMachine ByChatId(this StateMachine stateMachine) + where TMachine : IStateMachine, new() + where TState : IEquatable + { + stateMachine.KeyResolver = new ChatIdResolver(); + return stateMachine; + } + + /// + /// Configures the state machine to use a for state key resolution. + /// + /// The type of the state machine logic implementation. + /// The type of the state. + /// The state machine instance to configure. + /// The same state machine instance for method chaining. + public static StateMachine BySenderId(this StateMachine stateMachine) + where TMachine : IStateMachine, new() + where TState : IEquatable + { + stateMachine.KeyResolver = new SenderIdResolver(); + return stateMachine; + } +} + +/// +/// Extension methods for handlers collections. +/// Provides convenient methods for creating implicit handlers. +/// +public static partial class HandlersCollectionExtensions +{ + // TODO: rewrite collecting system, add certain hardcoded Type searching, generated automatically + private static readonly string[] skippingAssemblies = [ + "System", "Microsoft", "Windows", "Newtonsoft", "Serilog", "NLog", + "AutoMapper", "MediatR", "Dapper", "RestSharp", "Polly", "FluentValidation", + "NUnit", "Xunit", "Moq", "FluentAssertions", "Castle", "Autofac", "Ninject", + "StackExchange", "RabbitMQ", "Quartz", "Hangfire", "Npgsql", "MySql", "Oracle", + "Bogus", "CsvHelper", "Grpc", "Swashbuckle", "MassTransit", "AngleSharp", + "Ocelot", "BouncyCastle", "IdentityModel", "Telegrator" + ]; + + /// + /// Collects all public handlers from the current app domain. + /// Scans for types that implement handlers and adds them to the collection. + /// + /// This collection instance for method chaining. + /// Thrown when the entry assembly cannot be found. + public static IHandlersCollection CollectHandlersDomainWide(this IHandlersCollection handlers) + { + AppDomain.CurrentDomain + .GetAssemblies() + .Where(ass => skippingAssemblies.All(skip => !ass.FullName.StartsWith(skip))) + .ForEach(ass => handlers.CollectHandlersAssemblyWide(ass)); + + return handlers; + } + + /// + /// Collects all public handlers from the calling this function assembly. + /// Scans for types that implement handlers and adds them to the collection. + /// + /// This collection instance for method chaining. + /// Thrown when the entry assembly cannot be found. + public static IHandlersCollection CollectHandlersAssemblyWide(this IHandlersCollection handlers, Assembly? collectingTarget = null) + { + (collectingTarget ?? Assembly.GetCallingAssembly()) + .GetExportedTypes() + .Where(type => type.GetCustomAttribute() == null) + .Where(type => type.IsHandlerRealization()) + .ForEach(type => handlers.AddHandler(type)); + + return handlers; + } + + /// + /// Creates a handler builder for a specific update type. + /// + /// The type of update to handle. + /// The handlers collection. + /// The type of update to handle. + /// A handler builder for the specified update type. + public static HandlerBuilder CreateHandler(this IHandlersCollection handlers, UpdateType updateType) where TUpdate : class + => new HandlerBuilder(updateType, handlers); + + /// + /// Creates a handler builder for any update type. + /// + /// The handlers collection. + /// A handler builder for any update type. + public static HandlerBuilder CreateAny(this IHandlersCollection handlers) + => handlers.CreateHandler(UpdateType.Unknown); + + /// + /// Creates a handler builder for message updates. + /// + /// The handlers collection. + /// A handler builder for message updates. + public static HandlerBuilder CreateMessage(this IHandlersCollection handlers) + => handlers.CreateHandler(UpdateType.Message); + + /// + /// Creates a handler builder for callback query updates. + /// + /// The handlers collection. + /// A handler builder for callback query updates. + public static HandlerBuilder CreateCallbackQuery(this IHandlersCollection handlers) + => handlers.CreateHandler(UpdateType.CallbackQuery); + + /// + /// Adds a handler type to the collection. + /// + /// The handlers collection. + /// The type of handler to add. + /// This collection instance for method chaining. + public static IHandlersCollection AddHandler(this IHandlersCollection handlers) where THandler : UpdateHandlerBase + => handlers.AddHandler(typeof(THandler)); + + /// + /// Adds a handler type to the collection. + /// + /// The handlers collection. + /// The type of handler to add. + /// This collection instance for method chaining. + /// Thrown when the type is not a valid handler implementation. + public static IHandlersCollection AddHandler(this IHandlersCollection handlers, Type handlerType) + { + if (!handlerType.IsHandlerRealization()) + throw new Exception(); + + if (handlerType.IsCustomDescriptorsProvider()) + { + ICustomDescriptorsProvider provider = (ICustomDescriptorsProvider)Activator.CreateInstance(handlerType); + foreach (HandlerDescriptor handlerDescriptor in provider.DescribeHandlers()) + handlers.AddDescriptor(handlerDescriptor); + } + else + { + HandlerDescriptor descriptor = new HandlerDescriptor(DescriptorType.General, handlerType); + handlers.AddDescriptor(descriptor); + } + + return handlers; + } + + /// + /// Creates implicit handler from method + /// + /// + /// + /// + /// + public static IHandlersCollection AddMethod(this IHandlersCollection handlers, AbstractHandlerAction method) where TUpdate : class + { + MethodHandlerDescriptor descriptor = new MethodHandlerDescriptor(method); + handlers.AddDescriptor(descriptor); + return handlers; + } +} + +/// +/// Provides extension methods for working with Telegram Update objects. +/// +public static partial class UpdateExtensions +{ + /// + /// Selects from Update an object from which you can get the sender's ID + /// + /// + /// Sender's ID + public static long? GetSenderId(this Update update) => update switch + { + { Message.From: { } from } => from.Id, + { Message.SenderChat: { } chat } => chat.Id, + { EditedMessage.From: { } from } => from.Id, + { EditedMessage.SenderChat: { } chat } => chat.Id, + { ChannelPost.From: { } from } => from.Id, + { ChannelPost.SenderChat: { } chat } => chat.Id, + { EditedChannelPost.From: { } from } => from.Id, + { EditedChannelPost.SenderChat: { } chat } => chat.Id, + { CallbackQuery.From: { } from } => from.Id, + { InlineQuery.From: { } from } => from.Id, + { PollAnswer.User: { } user } => user.Id, + { PreCheckoutQuery.From: { } from } => from.Id, + { ShippingQuery.From: { } from } => from.Id, + { ChosenInlineResult.From: { } from } => from.Id, + { ChatJoinRequest.From: { } from } => from.Id, + { ChatMember.From: { } from } => from.Id, + { MyChatMember.From: { } from } => from.Id, + _ => null + }; + + /// + /// Selects from Update an object from which you can get the chat's ID + /// + /// + /// Sender's ID + public static long? GetChatId(this Update update) => update switch + { + { Message.Chat: { } chat } => chat.Id, + { Message.SenderChat: { } chat } => chat.Id, + { EditedMessage.Chat: { } chat } => chat.Id, + { EditedMessage.SenderChat: { } chat } => chat.Id, + { ChannelPost.Chat: { } chat } => chat.Id, + { ChannelPost.SenderChat: { } chat } => chat.Id, + { EditedChannelPost.Chat: { } chat } => chat.Id, + { EditedChannelPost.SenderChat: { } chat } => chat.Id, + { CallbackQuery.Message.Chat: { } chat } => chat.Id, + { ChatJoinRequest.Chat: { } chat } => chat.Id, + { ChatMember.Chat: { } chat } => chat.Id, + { MyChatMember.Chat: { } chat } => chat.Id, + _ => null + }; + + /// + /// Selects from an object that contains information about the update + /// + /// + /// + public static object GetActualUpdateObject(this Update update) => update switch + { + { Message: { } message } => message, + { EditedMessage: { } editedMessage } => editedMessage, + { ChannelPost: { } channelPost } => channelPost, + { EditedChannelPost: { } editedChannelPost } => editedChannelPost, + { BusinessConnection: { } businessConnection } => businessConnection, + { BusinessMessage: { } businessMessage } => businessMessage, + { EditedBusinessMessage: { } editedBusinessMessage } => editedBusinessMessage, + { DeletedBusinessMessages: { } deletedBusinessMessages } => deletedBusinessMessages, + { MessageReaction: { } messageReaction } => messageReaction, + { MessageReactionCount: { } messageReactionCount } => messageReactionCount, + { InlineQuery: { } inlineQuery } => inlineQuery, + { ChosenInlineResult: { } chosenInlineResult } => chosenInlineResult, + { CallbackQuery: { } callbackQuery } => callbackQuery, + { ShippingQuery: { } shippingQuery } => shippingQuery, + { PreCheckoutQuery: { } preCheckoutQuery } => preCheckoutQuery, + { PurchasedPaidMedia: { } purchasedPaidMedia } => purchasedPaidMedia, + { Poll: { } poll } => poll, + { PollAnswer: { } pollAnswer } => pollAnswer, + { MyChatMember: { } myChatMember } => myChatMember, + { ChatMember: { } chatMember } => chatMember, + { ChatJoinRequest: { } chatJoinRequest } => chatJoinRequest, + { ChatBoost: { } chatBoost } => chatBoost, + { RemovedChatBoost: { } removedChatBoost } => removedChatBoost, + _ => update + }; + + /// + /// Selecting corresponding s for 's sub-type + /// + /// + public static UpdateType[] GetAllowedUpdateTypes(this Type type) => type.FullName switch + { + "Telegram.Bot.Types.Message" => UpdateTypeExtensions.MessageTypes, + "Telegram.Bot.Types.ChatMemberUpdated" => [UpdateType.MyChatMember, UpdateType.ChatMember], + "Telegram.Bot.Types.InlineQuery" => [UpdateType.InlineQuery], + "Telegram.Bot.Types.ChosenInlineResult" => [UpdateType.ChosenInlineResult], + "Telegram.Bot.Types.CallbackQuery" => [UpdateType.CallbackQuery], + "Telegram.Bot.Types.ShippingQuery" => [UpdateType.ShippingQuery], + "Telegram.Bot.Types.PreCheckoutQuery" => [UpdateType.PreCheckoutQuery], + "Telegram.Bot.Types.Poll" => [UpdateType.Poll], + "Telegram.Bot.Types.PollAnswer" => [UpdateType.PollAnswer], + "Telegram.Bot.Types.ChatJoinRequest" => [UpdateType.ChatJoinRequest], + "Telegram.Bot.Types.MessageReactionUpdated" => [UpdateType.MessageReaction], + "Telegram.Bot.TypesMessageReactionCountUpdated" => [UpdateType.MessageReactionCount], + "Telegram.Bot.Types.ChatBoostUpdated" => [UpdateType.ChatBoost], + "Telegram.Bot.Types.ChatBoostRemoved" => [UpdateType.RemovedChatBoost], + "Telegram.Bot.Types.BusinessConnection" => [UpdateType.BusinessConnection], + "Telegram.Bot.Types.BusinessMessagesDeleted" => [UpdateType.DeletedBusinessMessages], + "Telegram.Bot.Types.PaidMediaPurchased" => [UpdateType.PurchasedPaidMedia], + "Telegram.Bot.Types.Update" => Update.AllTypes, + _ => [] + }; + + /// + /// Selecting corresponding s for 's sub-type + /// + /// + /// + public static UpdateType[] GetAllowedUpdateTypes() where T : class + => GetAllowedUpdateTypes(typeof(T)); + + /// + /// Selects from an that contains information about the update + /// + /// + /// + public static T GetActualUpdateObject(this Update update) + { + if (update is T upd) + return upd; + + object actualUpdate = update.GetActualUpdateObject() ?? throw new Exception(); + if (actualUpdate is not T actualCasted) + throw new Exception(); + + return actualCasted; + } +} + +/// +/// Provides extension methods for working with UpdateType enums. +/// +public static partial class UpdateTypeExtensions +{ + /// + /// 's that contain a message + /// + public static readonly UpdateType[] MessageTypes = + [ + UpdateType.Message, + UpdateType.EditedMessage, + UpdateType.BusinessMessage, + UpdateType.EditedBusinessMessage, + UpdateType.ChannelPost, + UpdateType.EditedChannelPost + ]; + + /// + /// Dictionary of s that suppresses to generic type for handling types that has complex multi-type handlers + /// + public static readonly Dictionary SuppressTypes = new Dictionary() + { + { UpdateType.ChosenInlineResult, UpdateType.InlineQuery } + }; + + /// + /// Checks if matches one of the 's give on + /// + /// + /// + /// + public static bool IsUpdateObjectAllowed(this UpdateType[] allowedTypes) where T : class + { + return allowedTypes.Any(t => t.IsValidUpdateObject()); + } + + /// + /// Checks if matches the given + /// + /// + /// + /// + public static bool IsValidUpdateObject(this UpdateType updateType) where TUpdate : class + { + if (typeof(TUpdate) == typeof(Update)) + return true; + + return typeof(TUpdate).Equals(updateType.ReflectUpdateObject()); + } + + /// + /// Returns an update object corresponding to the . + /// + /// + /// + public static Type? ReflectUpdateObject(this UpdateType updateType) + { + return updateType switch + { + UpdateType.Message or UpdateType.EditedMessage or UpdateType.BusinessMessage or UpdateType.EditedBusinessMessage or UpdateType.ChannelPost or UpdateType.EditedChannelPost => typeof(Message), + UpdateType.MyChatMember => typeof(ChatMemberUpdated), + UpdateType.ChatMember => typeof(ChatMemberUpdated), + UpdateType.InlineQuery => typeof(InlineQuery), + UpdateType.ChosenInlineResult => typeof(ChosenInlineResult), + UpdateType.CallbackQuery => typeof(CallbackQuery), + UpdateType.ShippingQuery => typeof(ShippingQuery), + UpdateType.PreCheckoutQuery => typeof(PreCheckoutQuery), + UpdateType.Poll => typeof(Poll), + UpdateType.PollAnswer => typeof(PollAnswer), + UpdateType.ChatJoinRequest => typeof(ChatJoinRequest), + UpdateType.MessageReaction => typeof(MessageReactionUpdated), + UpdateType.MessageReactionCount => typeof(MessageReactionCountUpdated), + UpdateType.ChatBoost => typeof(ChatBoostUpdated), + UpdateType.RemovedChatBoost => typeof(ChatBoostRemoved), + UpdateType.BusinessConnection => typeof(BusinessConnection), + UpdateType.DeletedBusinessMessages => typeof(BusinessMessagesDeleted), + UpdateType.PurchasedPaidMedia => typeof(PaidMediaPurchased), + _ or UpdateType.Unknown => typeof(Update) + }; + } +}