diff --git a/Telegrator.ConsoleHost.Web/Program.cs b/Telegrator.ConsoleHost.Web/Program.cs new file mode 100644 index 0000000..3b67d23 --- /dev/null +++ b/Telegrator.ConsoleHost.Web/Program.cs @@ -0,0 +1,25 @@ +using Telegrator.Hosting; +using Telegrator.Hosting.Web; + +namespace Telegrator.ConsoleHost.Web +{ + public class Program + { + public static void Main(string[] args) + { + TelegramBotWebHostBuilder builder = TelegramBotWebHost.CreateBuilder(new TelegramBotWebOptions() + { + Args = args, + WebhookUri = "https://telegrator-hooker.cloudpub.ru/bot", + DescendDescriptorIndex = false, + ExceptIntersectingCommandAliases = true, + }); + + builder.Handlers.CollectHandlersAssemblyWide(); + + TelegramBotWebHost telegramBot = builder.Build(); + telegramBot.SetBotCommands(); + telegramBot.Run(); + } + } +} diff --git a/Telegrator.ConsoleHost.Web/Properties/launchSettings.json b/Telegrator.ConsoleHost.Web/Properties/launchSettings.json new file mode 100644 index 0000000..f4d95e9 --- /dev/null +++ b/Telegrator.ConsoleHost.Web/Properties/launchSettings.json @@ -0,0 +1,38 @@ +{ + "profiles": { + "http": { + "commandName": "Project", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "dotnetRunMessages": true, + "applicationUrl": "http://localhost:5284" + }, + "https": { + "commandName": "Project", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "dotnetRunMessages": true, + "applicationUrl": "https://localhost:8080" + }, + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + }, + "$schema": "http://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:12266", + "sslPort": 44308 + } + } +} \ No newline at end of file diff --git a/Telegrator.ConsoleHost.Web/Telegrator.ConsoleHost.Web.csproj b/Telegrator.ConsoleHost.Web/Telegrator.ConsoleHost.Web.csproj new file mode 100644 index 0000000..fd9af14 --- /dev/null +++ b/Telegrator.ConsoleHost.Web/Telegrator.ConsoleHost.Web.csproj @@ -0,0 +1,13 @@ + + + + net8.0 + enable + enable + + + + + + + diff --git a/Telegrator.ConsoleHost.Web/appsettings.Development.json b/Telegrator.ConsoleHost.Web/appsettings.Development.json new file mode 100644 index 0000000..0c208ae --- /dev/null +++ b/Telegrator.ConsoleHost.Web/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/Telegrator.ConsoleHost.Web/appsettings.json b/Telegrator.ConsoleHost.Web/appsettings.json new file mode 100644 index 0000000..e1dc924 --- /dev/null +++ b/Telegrator.ConsoleHost.Web/appsettings.json @@ -0,0 +1,28 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + + "TelegramBotClientOptions": { + "Token": "7625502724:AAE5QPuX5P_lk2HwV4kdE6guoM6nsFQbe-c" + }, + + "TelegramBotOptions": { + "ExecuteOnlyFirstFoundHanlder": true, + "MaximumParallelWorkingHandlers": null + }, + + "HostOptions": { + "ShutdownTimeout": 10, + "BackgroundServiceExceptionBehavior": "StopHost" + }, + + "Shapes": { + "Token": "AX5IHZHSZMGQVEC6TILCOFLALPQRDDUWEBT8UYPH0OW" + }, + + "AllowedHosts": "*" +} \ No newline at end of file diff --git a/Telegrator.Generators/ApiMarkdownGenerator.cs b/Telegrator.Generators/ApiMarkdownGenerator.cs index 9b1f7e5..261a6b3 100644 --- a/Telegrator.Generators/ApiMarkdownGenerator.cs +++ b/Telegrator.Generators/ApiMarkdownGenerator.cs @@ -4,6 +4,7 @@ using System.Collections.Immutable; using System.Text; using System.Xml.Linq; +#pragma warning disable CS0162 namespace Telegrator.Generators { /// @@ -14,6 +15,11 @@ namespace Telegrator.Generators { public void Initialize(IncrementalGeneratorInitializationContext context) { +#if RELEASE + // DEBUG ONLY GENERATOR + return; +#endif + IncrementalValueProvider> typeDeclarations = context.SyntaxProvider .CreateSyntaxProvider( predicate: (node, _) => node is ClassDeclarationSyntax || node is InterfaceDeclarationSyntax || node is StructDeclarationSyntax || node is EnumDeclarationSyntax, diff --git a/Telegrator.Hosting.Web/Components/ITelegramBotWebHost.cs b/Telegrator.Hosting.Web/Components/ITelegramBotWebHost.cs index 5ab2dc0..aa286c6 100644 --- a/Telegrator.Hosting.Web/Components/ITelegramBotWebHost.cs +++ b/Telegrator.Hosting.Web/Components/ITelegramBotWebHost.cs @@ -1,8 +1,14 @@ -using Telegrator.Hosting.Components; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Routing; +using Telegrator.Hosting.Components; namespace Telegrator.Hosting.Web.Components { - public interface ITelegramBotWebHost : ITelegramBotHost//, IEndpointRouteBuilder + /// + /// Interface for Telegram bot hosts with Webhook update receiving. + /// Combines wbe application capabilities with reactive Telegram bot functionality. + /// + public interface ITelegramBotWebHost : ITelegramBotHost, IEndpointRouteBuilder, IApplicationBuilder, IAsyncDisposable { } diff --git a/Telegrator.Hosting.Web/GlobalSuppressions.cs b/Telegrator.Hosting.Web/GlobalSuppressions.cs new file mode 100644 index 0000000..e8cd1b7 --- /dev/null +++ b/Telegrator.Hosting.Web/GlobalSuppressions.cs @@ -0,0 +1,10 @@ +// This file is used by Code Analysis to maintain SuppressMessage +// attributes that are applied to this project. +// Project-level suppressions either have no target or are given +// a specific target and scoped to a namespace, type, member, etc. + +using System.Diagnostics.CodeAnalysis; + +[assembly: SuppressMessage("Style", "IDE0290")] +[assembly: SuppressMessage("Style", "IDE0090")] +[assembly: SuppressMessage("Usage", "CA2254")] diff --git a/Telegrator.Hosting.Web/Polling/HostedUpdateWebhooker.cs b/Telegrator.Hosting.Web/Polling/HostedUpdateWebhooker.cs index 36cd5e7..b2cccf6 100644 --- a/Telegrator.Hosting.Web/Polling/HostedUpdateWebhooker.cs +++ b/Telegrator.Hosting.Web/Polling/HostedUpdateWebhooker.cs @@ -1,49 +1,93 @@ -using Microsoft.Extensions.Hosting; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Options; +using Microsoft.Extensions.Primitives; +using System.Text.Json; using Telegram.Bot; +using Telegram.Bot.Types; using Telegrator.Hosting.Web.Components; using Telegrator.MadiatorCore; namespace Telegrator.Hosting.Web.Polling { + /// + /// 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 ITelegramBotWebHost _botHost; private readonly ITelegramBotClient _botClient; private readonly IUpdateRouter _updateRouter; private readonly TelegramBotWebOptions _options; + /// + /// Initiallizes new instance of + /// + /// + /// + /// + /// + /// public HostedUpdateWebhooker(ITelegramBotWebHost botHost, ITelegramBotClient botClient, IUpdateRouter updateRouter, IOptions options) { if (string.IsNullOrEmpty(options.Value.WebhookUri)) throw new ArgumentNullException(nameof(options), "Option \"WebhookUrl\" must be set to subscribe for update recieving"); - if (string.IsNullOrEmpty(options.Value.WebhookPattern)) - throw new ArgumentNullException(nameof(options), "Option \"WebhookPattern\" must be set to subscribe for update recieving"); - _botHost = botHost; _botClient = botClient; _updateRouter = updateRouter; _options = options.Value; } + /// public Task StartAsync(CancellationToken cancellationToken) { + string pattern = new UriBuilder(_options.WebhookUri).Path; + _botHost.MapPost(pattern, (Delegate)ReceiveUpdate); + _botClient.SetWebhook( url: _options.WebhookUri, maxConnections: _options.MaxConnections, allowedUpdates: _botHost.UpdateRouter.HandlersProvider.AllowedTypes, dropPendingUpdates: _options.DropPendingUpdates, - cancellationToken: cancellationToken); + cancellationToken: cancellationToken) + .Wait(cancellationToken); - //botHost.MapGet(_options.WebhookPattern, async (Update update) => await _updateRouter.HandleUpdateAsync(_botClient, update, cancellationToken)); return Task.CompletedTask; } + /// 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 }) + return Results.BadRequest(); + + await _updateRouter.HandleUpdateAsync(_botClient, update, ctx.RequestAborted); + return Results.Ok(); + } } } diff --git a/Telegrator.Hosting.Web/README.md b/Telegrator.Hosting.Web/README.md new file mode 100644 index 0000000..3635afa --- /dev/null +++ b/Telegrator.Hosting.Web/README.md @@ -0,0 +1,84 @@ +# Telegrator.Hosting.Web + +**Telegrator.Hosting.Web** is an extension for the Telegrator framework that enables seamless integration with ASP.NET Core and webhook-based Telegram bots. It is designed for scalable, production-ready web applications. + +--- + +## Features +- ASP.NET Core integration for webhook-based bots +- Automatic handler discovery and registration +- Strongly-typed configuration via `appsettings.json` and environment variables +- Dependency injection and middleware support +- Graceful startup/shutdown and lifecycle management +- Advanced error handling and logging +- Supports all Telegrator handler/filter/state features + +--- + +## Requirements +- .NET 8.0 or later +- ASP.NET Core + +--- + +## Installation + +```shell +dotnet add package Telegrator.Hosting.Web +``` + +--- + +## Quick Start Example + +**Program.cs (ASP.NET Core):** +```csharp +using Telegrator.Hosting; +using Telegrator.Hosting.Web; + +// Creating builder +TelegramBotWebHostBuilder builder = TelegramBotWebHost.CreateBuilder(new TelegramBotWebOptions() +{ + Args = args, + WebhookUri = "https://you-public-host.ru/bot" + ExceptIntersectingCommandAliases = true +}); + +// Register handlers +builder.Handlers.CollectHandlersAssemblyWide(); +builder.Services.AddHandlersFromAssembly(typeof(Program).Assembly); + +// Register your services +builder.Services.AddSingleton(); + +// Building and running application +TelegramBotWebHost telegramBot = builder.Build(); +telegramBot.SetBotCommands(); +telegramBot.Run(); +``` + +--- + +## Configuration (appsettings.json) + +```json +{ + "TelegramBotClientOptions": { + "Token": "YOUR_BOT_TOKEN" + } +} +``` + +- `TelegramBotClientOptions`: Bot token and client settings + +--- + +## Documentation +- [Telegrator Main Docs](https://github.com/Rikitav/Telegrator) +- [Getting Started Guide](https://github.com/Rikitav/Telegrator/wiki/Getting-started) +- [Annotation Overview](https://github.com/Rikitav/Telegrator/wiki/Annotation-overview) + +--- + +## License +GPLv3 \ No newline at end of file diff --git a/Telegrator.Hosting.Web/TelegramBotWebHost.cs b/Telegrator.Hosting.Web/TelegramBotWebHost.cs index 8e4dc29..5686ea4 100644 --- a/Telegrator.Hosting.Web/TelegramBotWebHost.cs +++ b/Telegrator.Hosting.Web/TelegramBotWebHost.cs @@ -1,10 +1,196 @@ -using Telegrator.Hosting.Components; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Routing; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using System.Text; +using Telegram.Bot.Types.Enums; +using Telegrator.Hosting.Components; +using Telegrator.Hosting.Providers; +using Telegrator.Hosting.Web.Components; +using Telegrator.MadiatorCore; +using Telegrator.MadiatorCore.Descriptors; namespace Telegrator.Hosting.Web { - public class TelegramBotWebHost //: ITelegramBotWebHost + /// + /// Represents a web hosted telegram bot + /// + public class TelegramBotWebHost : ITelegramBotWebHost { + 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; + + /// + /// Allows consumers to be notified of application lifetime events. + /// + 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; + + internal TelegramBotWebHost(WebApplicationBuilder webApplicationBuilder, HostHandlersCollection handlers) + { + RegisterHostServices(webApplicationBuilder, handlers); + _innerApp = webApplicationBuilder.Build(); + + _updateRouter = Services.GetRequiredService(); + _logger = Services.GetRequiredService>(); + + LogHandlers(handlers); + } + + /// + /// Creates new with default services and webhook update receiving scheme + /// + /// + public static TelegramBotWebHostBuilder CreateBuilder(TelegramBotWebOptions settings) + { + ArgumentNullException.ThrowIfNull(settings, nameof(settings)); + WebApplicationBuilder innerApp = WebApplication.CreateBuilder(settings.ToWebApplicationOptions()); + TelegramBotWebHostBuilder builder = new TelegramBotWebHostBuilder(innerApp, settings); + builder.Services.AddTelegramBotHostDefaults(); + builder.Services.AddTelegramWebhook(); + return builder; + } + + /// + /// Creates new SLIM with default services and webhook update receiving scheme + /// + /// + public static TelegramBotWebHostBuilder CreateSlimBuilder(TelegramBotWebOptions settings) + { + ArgumentNullException.ThrowIfNull(settings, nameof(settings)); + WebApplicationBuilder innerApp = WebApplication.CreateSlimBuilder(settings.ToWebApplicationOptions()); + TelegramBotWebHostBuilder builder = new TelegramBotWebHostBuilder(innerApp, settings); + builder.Services.AddTelegramBotHostDefaults(); + builder.Services.AddTelegramWebhook(); + return builder; + } + + /// + /// Creates new EMPTY WITHOUT any services or update receiving schemes + /// + /// + public static TelegramBotWebHostBuilder CreateEmptyBuilder(TelegramBotWebOptions settings) + { + ArgumentNullException.ThrowIfNull(settings, nameof(settings)); + WebApplicationBuilder innerApp = WebApplication.CreateEmptyBuilder(settings.ToWebApplicationOptions()); + return new TelegramBotWebHostBuilder(innerApp, settings); + } + + /// + public async Task StartAsync(CancellationToken cancellationToken = default) + { + await _innerApp.StartAsync(cancellationToken); + } + + /// + public async Task StopAsync(CancellationToken cancellationToken = default) + { + await _innerApp.StopAsync(cancellationToken); + } + + /// + public IApplicationBuilder CreateApplicationBuilder() + => ((IEndpointRouteBuilder)_innerApp).CreateApplicationBuilder(); + + /// + 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; + + // Sorry for this, i really dont know how to handle such cases + ValueTask disposeTask = _innerApp.DisposeAsync(); + while (!disposeTask.IsCompleted) + Thread.Sleep(100); + + GC.SuppressFinalize(this); + _disposed = true; + } + + private void LogHandlers(HostHandlersCollection handlers) + { + StringBuilder logBuilder = new StringBuilder("Registered handlers : "); + if (!handlers.Keys.Any()) + throw new Exception(); + + foreach (UpdateType updateType in handlers.Keys) + { + HandlerDescriptorList descriptors = handlers[updateType]; + logBuilder.Append("\n\tUpdateType." + updateType + " :"); + + foreach (HandlerDescriptor descriptor in descriptors.Reverse()) + { + logBuilder.AppendFormat("\n\t* {0} - {1}", + descriptor.Indexer.ToString(), + descriptor.ToString()); + } + } + + Logger.LogInformation(logBuilder.ToString()); + } + + private void RegisterHostServices(WebApplicationBuilder hostApplicationBuilder, HostHandlersCollection handlers) + { + //hostApplicationBuilder.Services.RemoveAll(); + //hostApplicationBuilder.Services.AddSingleton(this); + + hostApplicationBuilder.Services.AddSingleton(this); + hostApplicationBuilder.Services.AddSingleton(this); + hostApplicationBuilder.Services.AddSingleton(this); + hostApplicationBuilder.Services.AddSingleton(handlers); + } } } diff --git a/Telegrator.Hosting.Web/TelegramBotWebHostBuilder.cs b/Telegrator.Hosting.Web/TelegramBotWebHostBuilder.cs index 96b1202..cab6f3d 100644 --- a/Telegrator.Hosting.Web/TelegramBotWebHostBuilder.cs +++ b/Telegrator.Hosting.Web/TelegramBotWebHostBuilder.cs @@ -1,10 +1,73 @@ -using Telegrator.Hosting.Components; -using Telegrator.Hosting.Web.Components; +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Telegram.Bot; +using Telegram.Bot.Polling; +using Telegrator.Hosting.Components; +using Telegrator.Hosting.Configuration; +using Telegrator.Hosting.Providers; +using Telegrator.MadiatorCore; +#pragma warning disable IDE0001 namespace Telegrator.Hosting.Web { - public class TelegramBotWebHostBuilder //: ITelegramBotHostBuilder + /// + /// Represents a web hosted telegram bots and services builder that helps manage configuration, logging, lifetime, and more. + /// + public class TelegramBotWebHostBuilder : ITelegramBotHostBuilder { + private readonly WebApplicationBuilder _innerBuilder; + private readonly TelegramBotWebOptions _settings; + private readonly HostHandlersCollection _handlers; + /// + public IHandlersCollection Handlers => _handlers; + + /// + public IConfigurationManager Configuration => _innerBuilder.Configuration; + + /// + public ILoggingBuilder Logging => _innerBuilder.Logging; + + /// + public IServiceCollection Services => _innerBuilder.Services; + + /// + public IHostEnvironment Environment => _innerBuilder.Environment; + + internal TelegramBotWebHostBuilder(WebApplicationBuilder webApplicationBuilder, TelegramBotWebOptions settings) + { + _innerBuilder = webApplicationBuilder; + _settings = settings ?? throw new ArgumentNullException(nameof(settings)); + _handlers = new HostHandlersCollection(Services, _settings); + + Services.AddSingleton>(Options.Create(settings)); + Services.Configure(Configuration.GetSection(nameof(TelegratorOptions))); + Services.Configure(Configuration.GetSection(nameof(TelegramBotClientOptions)), new TelegramBotClientOptionsProxy()); + } + + /// + /// Builds the host. + /// + /// + public TelegramBotWebHost Build() + { + foreach (PreBuildingRoutine preBuildRoutine in _handlers.PreBuilderRoutines) + { + try + { + preBuildRoutine.Invoke(this); + } + catch (NotImplementedException) + { + _ = 0xBAD + 0xC0DE; + } + } + + return new TelegramBotWebHost(_innerBuilder, _handlers); + } } } diff --git a/Telegrator.Hosting.Web/TelegramBotWebOptions.cs b/Telegrator.Hosting.Web/TelegramBotWebOptions.cs index 6e2eceb..de18089 100644 --- a/Telegrator.Hosting.Web/TelegramBotWebOptions.cs +++ b/Telegrator.Hosting.Web/TelegramBotWebOptions.cs @@ -1,18 +1,58 @@ -using Telegrator.Configuration; +using Microsoft.AspNetCore.Builder; namespace Telegrator.Hosting.Web { - public class TelegramBotWebOptions : TelegramBotOptions + /// + /// 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 TelegramBotWebOptions : TelegratorOptions { /// - /// Gets or sets uri for webhook update receiving + /// Gets or sets HTTPS URL to send updates to. Use an empty string to remove webhook integration /// public required string WebhookUri { get; set; } - public required string WebhookPattern { get; set; } + /// + /// 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; } - public int MaxConnections { get; set; } + /// + /// 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; } + + /// + public string[]? Args { get; init; } + + /// + public string? EnvironmentName { get; init; } + + /// + public string? ApplicationName { get; init; } + + /// + public string? ContentRootPath { get; init; } + + /// + public string? WebRootPath { get; init; } + + internal WebApplicationOptions ToWebApplicationOptions() => new WebApplicationOptions() + { + ApplicationName = ApplicationName, + Args = Args, + ContentRootPath = ContentRootPath, + EnvironmentName = EnvironmentName, + WebRootPath = WebRootPath + }; } } diff --git a/Telegrator.Hosting.Web/Telegrator.Hosting.Web.csproj b/Telegrator.Hosting.Web/Telegrator.Hosting.Web.csproj index a9bf70f..53790f9 100644 --- a/Telegrator.Hosting.Web/Telegrator.Hosting.Web.csproj +++ b/Telegrator.Hosting.Web/Telegrator.Hosting.Web.csproj @@ -4,6 +4,18 @@ net8.0 enable enable + True + True + + Telegrator : Telegram.Bot mediator framework + telegrator_nuget.png + https://github.com/Rikitav/Telegrator + telegram;bot;mediator;attributes;aspect;hosting;host;framework;easy;simple;handlers + True + True + LICENSE + README.md + 1.0.7 @@ -11,6 +23,25 @@ - + + True + \ + + + True + \ + + + True + \ + + + + + + + + + diff --git a/Telegrator.Hosting.Web/TypesExtensions.cs b/Telegrator.Hosting.Web/TypesExtensions.cs index 7cb7f67..1482802 100644 --- a/Telegrator.Hosting.Web/TypesExtensions.cs +++ b/Telegrator.Hosting.Web/TypesExtensions.cs @@ -1,12 +1,22 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Telegram.Bot; +using Telegrator.Hosting.Web.Components; using Telegrator.Hosting.Web.Polling; namespace Telegrator.Hosting.Web { + /// + /// Contains extensions for + /// Provides method to configure + /// public static class ServicesCollectionExtensions { + /// + /// Registers service with to receive updates using webhook + /// + /// + /// public static IServiceCollection AddTelegramWebhook(this IServiceCollection services) { services.AddHttpClient("tgwebhook").RemoveAllLoggers().AddTypedClient(TypedTelegramBotClientFactory); diff --git a/Telegrator.Hosting/Components/IPreBuildingRoutine.cs b/Telegrator.Hosting/Components/IPreBuildingRoutine.cs index 8e1fd14..c0c7f26 100644 --- a/Telegrator.Hosting/Components/IPreBuildingRoutine.cs +++ b/Telegrator.Hosting/Components/IPreBuildingRoutine.cs @@ -10,6 +10,6 @@ /// Executes the pre-building routine on the specified host builder. /// /// The host builder to configure. - public static abstract void PreBuildingRoutine(TelegramBotHostBuilder hostBuilder); + public static abstract void PreBuildingRoutine(ITelegramBotHostBuilder hostBuilder); } } diff --git a/Telegrator.Hosting/Components/ITelegramBotHostBuilder.cs b/Telegrator.Hosting/Components/ITelegramBotHostBuilder.cs index 69b40bd..59a1810 100644 --- a/Telegrator.Hosting/Components/ITelegramBotHostBuilder.cs +++ b/Telegrator.Hosting/Components/ITelegramBotHostBuilder.cs @@ -1,5 +1,6 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Telegrator.MadiatorCore; diff --git a/Telegrator.Hosting/Configuration/TelegramBotClientOptionsProxy.cs b/Telegrator.Hosting/Configuration/TelegramBotClientOptionsProxy.cs index 1f43344..e6786bc 100644 --- a/Telegrator.Hosting/Configuration/TelegramBotClientOptionsProxy.cs +++ b/Telegrator.Hosting/Configuration/TelegramBotClientOptionsProxy.cs @@ -6,7 +6,7 @@ namespace Telegrator.Hosting.Configuration /// Internal proxy class for configuring Telegram bot client options from configuration. /// Extends ConfigureOptionsProxy to provide specific configuration for Telegram bot client options. /// - internal class TelegramBotClientOptionsProxy : ConfigureOptionsProxy + public class TelegramBotClientOptionsProxy : ConfigureOptionsProxy { /// /// Gets or sets the bot token. diff --git a/Telegrator.Hosting/GlobalSuppressions.cs b/Telegrator.Hosting/GlobalSuppressions.cs index cba9507..e8cd1b7 100644 --- a/Telegrator.Hosting/GlobalSuppressions.cs +++ b/Telegrator.Hosting/GlobalSuppressions.cs @@ -7,3 +7,4 @@ using System.Diagnostics.CodeAnalysis; [assembly: SuppressMessage("Style", "IDE0290")] [assembly: SuppressMessage("Style", "IDE0090")] +[assembly: SuppressMessage("Usage", "CA2254")] diff --git a/Telegrator.Hosting/Polling/HostUpdateHandlersPool.cs b/Telegrator.Hosting/Polling/HostUpdateHandlersPool.cs index 35f3798..82bb1a2 100644 --- a/Telegrator.Hosting/Polling/HostUpdateHandlersPool.cs +++ b/Telegrator.Hosting/Polling/HostUpdateHandlersPool.cs @@ -1,13 +1,12 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; -using Telegrator.Configuration; using Telegrator.MadiatorCore.Descriptors; using Telegrator.Polling; namespace Telegrator.Hosting.Polling { /// - public class HostUpdateHandlersPool(IOptions options, ILogger logger) : UpdateHandlersPool(options.Value, options.Value.GlobalCancellationToken) + public class HostUpdateHandlersPool(IOptions options, ILogger logger) : UpdateHandlersPool(options.Value, options.Value.GlobalCancellationToken) { private readonly ILogger _logger = logger; diff --git a/Telegrator.Hosting/Polling/HostUpdateRouter.cs b/Telegrator.Hosting/Polling/HostUpdateRouter.cs index e949aef..be569d1 100644 --- a/Telegrator.Hosting/Polling/HostUpdateRouter.cs +++ b/Telegrator.Hosting/Polling/HostUpdateRouter.cs @@ -21,7 +21,7 @@ namespace Telegrator.Hosting.Polling public HostUpdateRouter( IHandlersProvider handlersProvider, IAwaitingProvider awaitingProvider, - IOptions options, + IOptions options, IUpdateHandlersPool handlersPool, ITelegramBotInfo botInfo, ILogger logger) : base(handlersProvider, awaitingProvider, options.Value, handlersPool, botInfo) @@ -33,7 +33,7 @@ namespace Telegrator.Hosting.Polling /// public override Task HandleUpdateAsync(ITelegramBotClient botClient, Update update, CancellationToken cancellationToken) { - //Logger.LogInformation("Received update of type \"{type}\"", update.Type); + Logger.LogInformation("Received update of type \"{type}\"", update.Type); return base.HandleUpdateAsync(botClient, update, cancellationToken); } diff --git a/Telegrator.Hosting/Providers/HostAwaitingProvider.cs b/Telegrator.Hosting/Providers/HostAwaitingProvider.cs index b7ec5e5..e5a9475 100644 --- a/Telegrator.Hosting/Providers/HostAwaitingProvider.cs +++ b/Telegrator.Hosting/Providers/HostAwaitingProvider.cs @@ -1,12 +1,11 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; -using Telegrator.Configuration; using Telegrator.Providers; namespace Telegrator.Hosting.Providers { /// - public class HostAwaitingProvider(IOptions options, ILogger logger) : AwaitingProvider(options.Value) + public class HostAwaitingProvider(IOptions options, ILogger logger) : AwaitingProvider(options.Value) { private readonly ILogger _logger = logger; } diff --git a/Telegrator.Hosting/Providers/HostHandlersCollection.cs b/Telegrator.Hosting/Providers/HostHandlersCollection.cs index bef073c..39cd5ec 100644 --- a/Telegrator.Hosting/Providers/HostHandlersCollection.cs +++ b/Telegrator.Hosting/Providers/HostHandlersCollection.cs @@ -12,10 +12,10 @@ namespace Telegrator.Hosting.Providers /// Pre host building task /// /// - public delegate void PreBuildingRoutine(TelegramBotHostBuilder builder); + public delegate void PreBuildingRoutine(ITelegramBotHostBuilder builder); /// - public class HostHandlersCollection(IServiceCollection hostServiceColletion, IHandlersCollectingOptions options) : HandlersCollection(options) + public class HostHandlersCollection(IServiceCollection hostServiceColletion, ITelegratorOptions options) : HandlersCollection(options) { private readonly IServiceCollection Services = hostServiceColletion; diff --git a/Telegrator.Hosting/Providers/HostHandlersProvider.cs b/Telegrator.Hosting/Providers/HostHandlersProvider.cs index a378b3a..75f7faa 100644 --- a/Telegrator.Hosting/Providers/HostHandlersProvider.cs +++ b/Telegrator.Hosting/Providers/HostHandlersProvider.cs @@ -1,7 +1,6 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; -using Telegrator.Configuration; using Telegrator.Handlers.Components; using Telegrator.MadiatorCore; using Telegrator.MadiatorCore.Descriptors; @@ -18,7 +17,7 @@ namespace Telegrator.Hosting.Providers /// public HostHandlersProvider( IHandlersCollection handlers, - IOptions options, + IOptions options, IServiceProvider serviceProvider, ILogger logger) : base(handlers, options.Value) { diff --git a/Telegrator.Hosting/README.md b/Telegrator.Hosting/README.md new file mode 100644 index 0000000..41917e4 --- /dev/null +++ b/Telegrator.Hosting/README.md @@ -0,0 +1,93 @@ +# Telegrator.Hosting + +**Telegrator.Hosting** is an extension for the Telegrator framework that provides seamless integration with the .NET Generic Host, enabling production-ready, scalable, and maintainable Telegram bot applications. + +--- + +## Features +- Integration with `Microsoft.Extensions.Hosting` (background services, DI, configuration, logging) +- Automatic handler discovery and registration +- Strongly-typed configuration via `appsettings.json` and environment variables +- Graceful startup/shutdown and lifecycle management +- Advanced error handling and logging +- Supports all Telegrator handler/filter/state features + +--- + +## Requirements +- .NET 8.0 or later +- [Telegrator](https://github.com/Rikitav/Telegrator) + +--- + +## Installation + +```shell +dotnet add package Telegrator.Hosting +``` + +--- + +## Quick Start Example + +**Program.cs:** +```csharp +using Telegrator.Hosting; + +// Creating builder +TelegramBotHostBuilder builder = TelegramBotHost.CreateBuilder(new TelegramBotHostBuilderSettings() +{ + Args = args, + DescendDescriptorIndex = false, + ExceptIntersectingCommandAliases = true +}); + +// Registerring handlers +builder.Handlers.CollectHandlersAssemblyWide(); + +// Register your services +builder.Services.AddSingleton(); + +// Building and running application +TelegramBotHost telegramBot = builder.Build(); +telegramBot.SetBotCommands(); +telegramBot.Run(); +``` + +--- + +## Configuration (appsettings.json) + +```json +{ + "TelegramBotClientOptions": { + "Token": "YOUR_BOT_TOKEN" + }, + + "HostOptions": { + "ShutdownTimeout": 10, + "BackgroundServiceExceptionBehavior": "StopHost" + }, + + "ReceiverOptions": { + "DropPendingUpdates": true, + "Limit": 10 + } +} +``` + +- `TelegramBotClientOptions`: Bot token and client settings +- `HostOptions`: Host lifecycle and shutdown behavior +- `ReceiverOptions`: Long-polling configuration + +--- + +## Documentation +- [Telegrator Main Docs](https://github.com/Rikitav/Telegrator) +- [Getting Started Guide](https://github.com/Rikitav/Telegrator/wiki/Getting-started) +- [Annotation Overview](https://github.com/Rikitav/Telegrator/wiki/Annotation-overview) + +--- + +## License +GPLv3 \ No newline at end of file diff --git a/Telegrator.Hosting/TelegramBotHost.cs b/Telegrator.Hosting/TelegramBotHost.cs index cb5fca1..878db88 100644 --- a/Telegrator.Hosting/TelegramBotHost.cs +++ b/Telegrator.Hosting/TelegramBotHost.cs @@ -114,6 +114,8 @@ namespace Telegrator.Hosting if (_disposed) return; + _innerHost.Dispose(); + GC.SuppressFinalize(this); _disposed = true; } diff --git a/Telegrator.Hosting/TelegramBotHostBuilder.cs b/Telegrator.Hosting/TelegramBotHostBuilder.cs index 80cc490..fc1ef65 100644 --- a/Telegrator.Hosting/TelegramBotHostBuilder.cs +++ b/Telegrator.Hosting/TelegramBotHostBuilder.cs @@ -4,10 +4,9 @@ using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Telegram.Bot; using Telegram.Bot.Polling; -using Telegrator.Hosting.Configuration; -using Telegrator.Configuration; using Telegrator.Hosting; using Telegrator.Hosting.Components; +using Telegrator.Hosting.Configuration; using Telegrator.Hosting.Providers; using Telegrator.MadiatorCore; @@ -51,7 +50,7 @@ namespace Telegrator.Hosting _innerBuilder.Logging.ClearProviders(); - Services.Configure(Configuration.GetSection(nameof(TelegramBotOptions))); + Services.Configure(Configuration.GetSection(nameof(TelegratorOptions))); Services.Configure(Configuration.GetSection(nameof(ReceiverOptions))); Services.Configure(Configuration.GetSection(nameof(TelegramBotClientOptions)), new TelegramBotClientOptionsProxy()); } diff --git a/Telegrator.Hosting/TelegramBotHostBuilderSettings.cs b/Telegrator.Hosting/TelegramBotHostBuilderSettings.cs index 3e7d1bf..6f3b891 100644 --- a/Telegrator.Hosting/TelegramBotHostBuilderSettings.cs +++ b/Telegrator.Hosting/TelegramBotHostBuilderSettings.cs @@ -1,13 +1,12 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Hosting; -using Telegrator.Configuration; namespace Telegrator.Hosting { /// /// Settings os hosted Telegram bot /// - public class TelegramBotHostBuilderSettings() : IHandlersCollectingOptions + public class TelegramBotHostBuilderSettings() : TelegratorOptions { /// public bool DisableDefaults { get; set; } @@ -27,12 +26,6 @@ namespace Telegrator.Hosting /// public string? ContentRootPath { get; set; } - /// - public bool DescendDescriptorIndex { get; set; } = true; - - /// - public bool ExceptIntersectingCommandAliases { get; set; } = true; - internal HostApplicationBuilderSettings ToApplicationBuilderSettings() => new HostApplicationBuilderSettings() { DisableDefaults = DisableDefaults, diff --git a/Telegrator.Hosting/Telegrator.Hosting.csproj b/Telegrator.Hosting/Telegrator.Hosting.csproj index dc65e1d..19ace06 100644 --- a/Telegrator.Hosting/Telegrator.Hosting.csproj +++ b/Telegrator.Hosting/Telegrator.Hosting.csproj @@ -15,7 +15,8 @@ True True LICENSE - 1.0.6 + README.md + 1.0.7 @@ -38,4 +39,11 @@ + + + True + \ + + + diff --git a/Telegrator.Hosting/TypesExtensions.cs b/Telegrator.Hosting/TypesExtensions.cs index de8f8ce..3ea32be 100644 --- a/Telegrator.Hosting/TypesExtensions.cs +++ b/Telegrator.Hosting/TypesExtensions.cs @@ -4,7 +4,6 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using System.Diagnostics.CodeAnalysis; using System.Reflection; -using System.Runtime.CompilerServices; using Telegram.Bot; using Telegram.Bot.Types; using Telegrator.Configuration; diff --git a/Telegrator.sln b/Telegrator.sln index 84f7362..60116a7 100644 --- a/Telegrator.sln +++ b/Telegrator.sln @@ -20,6 +20,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Telegrator.Analyzers", "Tel EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Telegrator.ConsoleHost", "..\Telegram.Reactive.TestApps\Telegrator.ConsoleHost\Telegrator.ConsoleHost.csproj", "{78691EF7-6009-3BB1-C90D-1D1D95442041}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Telegrator.Hosting.Web", "Telegrator.Hosting.Web\Telegrator.Hosting.Web.csproj", "{98AB490F-6A36-CCFF-F6E6-B029D1665965}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Telegrator.ConsoleHost.Web", "Telegrator.ConsoleHost.Web\Telegrator.ConsoleHost.Web.csproj", "{C18E1844-64A7-4FF8-BDB9-24F8FBFC5CF6}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution AnalyzersDebug|Any CPU = AnalyzersDebug|Any CPU @@ -63,6 +67,18 @@ Global {78691EF7-6009-3BB1-C90D-1D1D95442041}.Debug|Any CPU.Build.0 = Debug|Any CPU {78691EF7-6009-3BB1-C90D-1D1D95442041}.Release|Any CPU.ActiveCfg = Release|Any CPU {78691EF7-6009-3BB1-C90D-1D1D95442041}.Release|Any CPU.Build.0 = Release|Any CPU + {98AB490F-6A36-CCFF-F6E6-B029D1665965}.AnalyzersDebug|Any CPU.ActiveCfg = Release|Any CPU + {98AB490F-6A36-CCFF-F6E6-B029D1665965}.AnalyzersDebug|Any CPU.Build.0 = Release|Any CPU + {98AB490F-6A36-CCFF-F6E6-B029D1665965}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {98AB490F-6A36-CCFF-F6E6-B029D1665965}.Debug|Any CPU.Build.0 = Debug|Any CPU + {98AB490F-6A36-CCFF-F6E6-B029D1665965}.Release|Any CPU.ActiveCfg = Release|Any CPU + {98AB490F-6A36-CCFF-F6E6-B029D1665965}.Release|Any CPU.Build.0 = Release|Any CPU + {C18E1844-64A7-4FF8-BDB9-24F8FBFC5CF6}.AnalyzersDebug|Any CPU.ActiveCfg = Release|Any CPU + {C18E1844-64A7-4FF8-BDB9-24F8FBFC5CF6}.AnalyzersDebug|Any CPU.Build.0 = Release|Any CPU + {C18E1844-64A7-4FF8-BDB9-24F8FBFC5CF6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C18E1844-64A7-4FF8-BDB9-24F8FBFC5CF6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C18E1844-64A7-4FF8-BDB9-24F8FBFC5CF6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C18E1844-64A7-4FF8-BDB9-24F8FBFC5CF6}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/Telegrator/Attributes/StateKeeperAttribute.cs b/Telegrator/Attributes/StateKeeperAttribute.cs index 85ac133..dcf0e02 100644 --- a/Telegrator/Attributes/StateKeeperAttribute.cs +++ b/Telegrator/Attributes/StateKeeperAttribute.cs @@ -64,36 +64,6 @@ namespace Telegrator.Attributes SpecialState = specialState; } - /* - /// - /// Initializes the attribute with a custom state keeper, a specific state, and a custom key resolver. - /// - /// The state keeper instance - /// The state value to associate - /// The key resolver for state keeping - protected StateKeeperAttribute(TKeeper keeper, TState myState, IStateKeyResolver keyResolver) : base(typeof(TKeeper)) - { - StateKeeper ??= keeper; - StateKeeper.KeyResolver = keyResolver; - MyState = myState; - SpecialState = SpecialState.None; - } - - /// - /// Initializes the attribute with a custom state keeper, a special state, and a custom key resolver. - /// - /// The state keeper instance - /// The special state mode - /// The key resolver for state keeping - protected StateKeeperAttribute(TKeeper keeper, SpecialState specialState, IStateKeyResolver keyResolver) : base(typeof(TKeeper)) - { - StateKeeper ??= keeper; - StateKeeper.KeyResolver = keyResolver; - MyState = StateKeeper.DefaultState; - SpecialState = specialState; - } - */ - /// /// Determines whether the current update context passes the state filter. /// diff --git a/Telegrator/Configuration/IHandlersCollectingOptions.cs b/Telegrator/Configuration/IHandlersCollectingOptions.cs deleted file mode 100644 index 912aba5..0000000 --- a/Telegrator/Configuration/IHandlersCollectingOptions.cs +++ /dev/null @@ -1,19 +0,0 @@ -namespace Telegrator.Configuration -{ - /// - /// Interface for configuring handler collection behavior. - /// Defines options that control how handlers are collected and processed during initialization. - /// - public interface IHandlersCollectingOptions - { - /// - /// Gets or sets a value indicating whether to descend the indexr of handler's index on register. ('false' by default) - /// - public bool DescendDescriptorIndex { get; set; } - - /// - /// Gets or sets a value indicating whether to exclude intersecting command aliases. - /// - public bool ExceptIntersectingCommandAliases { get; set; } - } -} diff --git a/Telegrator/Configuration/TelegramBotOptions.cs b/Telegrator/Configuration/ITelegratorOptions.cs similarity index 63% rename from Telegrator/Configuration/TelegramBotOptions.cs rename to Telegrator/Configuration/ITelegratorOptions.cs index 57f90ef..82b61b9 100644 --- a/Telegrator/Configuration/TelegramBotOptions.cs +++ b/Telegrator/Configuration/ITelegratorOptions.cs @@ -1,10 +1,10 @@ namespace Telegrator.Configuration { /// - /// Configuration options for Telegram bot behavior and execution settings. - /// Controls various aspects of bot operation including concurrency, routing, and execution policies. + /// Interface for configuring Telegram bot behavior and execution settings. + /// Controls various aspects of bot operation including concurrency, routing, collecting, and execution policies. /// - public class TelegramBotOptions + public interface ITelegratorOptions { /// /// Gets or sets a value indicating whether only the first found handler should be executed for each update. @@ -25,5 +25,15 @@ /// Gets or sets the global cancellation token for all bot operations. /// public CancellationToken GlobalCancellationToken { get; set; } + + /// + /// Gets or sets a value indicating whether to descend the indexr of handler's index on register. ('false' by default) + /// + public bool DescendDescriptorIndex { get; set; } + + /// + /// Gets or sets a value indicating whether to exclude intersecting command aliases. + /// + public bool ExceptIntersectingCommandAliases { get; set; } } } diff --git a/Telegrator/Enums.cs b/Telegrator/Enums.cs index 6326246..aa0c4a0 100644 --- a/Telegrator/Enums.cs +++ b/Telegrator/Enums.cs @@ -76,6 +76,11 @@ [Flags] public enum DebugLevel { + /// + /// None to write + /// + None = 0x0, + /// /// Write debug messages from filters execution /// diff --git a/Telegrator/LeveledDebug.cs b/Telegrator/LeveledDebug.cs index 2a02515..6f810d9 100644 --- a/Telegrator/LeveledDebug.cs +++ b/Telegrator/LeveledDebug.cs @@ -1,5 +1,4 @@ using System.Diagnostics; -using Telegram.Bot.Types.Enums; namespace Telegrator { @@ -8,13 +7,18 @@ namespace Telegrator /// public static class LeveledDebug { + /// + /// Gets or sets flags of what debug messages to write + /// + public static DebugLevel IndentFlags { get; set; } = DebugLevel.None; + /// /// Writes debug message if Indent level has Router flag /// /// public static void RouterWriteLine(string message) { - if (Debug.IndentLevel.HasFlag(DebugLevel.Router)) + if (IndentFlags.HasFlag(DebugLevel.Router)) Debug.WriteLine(message); } @@ -25,7 +29,7 @@ namespace Telegrator /// public static void RouterWriteLine(string message, params object[] args) { - if (Debug.IndentLevel.HasFlag(DebugLevel.Router)) + if (IndentFlags.HasFlag(DebugLevel.Router)) Debug.WriteLine(message, args); } @@ -35,7 +39,7 @@ namespace Telegrator /// public static void ProviderWriteLine(string message) { - if (Debug.IndentLevel.HasFlag(DebugLevel.Providers)) + if (IndentFlags.HasFlag(DebugLevel.Providers)) Debug.WriteLine(message); } @@ -46,7 +50,7 @@ namespace Telegrator /// public static void ProviderWriteLine(string message, params object[] args) { - if (Debug.IndentLevel.HasFlag(DebugLevel.Providers)) + if (IndentFlags.HasFlag(DebugLevel.Providers)) Debug.WriteLine(message, args); } @@ -56,7 +60,7 @@ namespace Telegrator /// public static void FilterWriteLine(string message) { - if (Debug.IndentLevel.HasFlag(DebugLevel.Filters)) + if (IndentFlags.HasFlag(DebugLevel.Filters)) Debug.WriteLine(message); } @@ -67,7 +71,7 @@ namespace Telegrator /// public static void FilterWriteLine(string message, params object[] args) { - if (Debug.IndentLevel.HasFlag(DebugLevel.Filters)) + if (IndentFlags.HasFlag(DebugLevel.Filters)) Debug.WriteLine(message, args); } @@ -77,7 +81,7 @@ namespace Telegrator /// public static void PoolWriteLine(string message) { - if (Debug.IndentLevel.HasFlag(DebugLevel.HandlersPool)) + if (IndentFlags.HasFlag(DebugLevel.HandlersPool)) Debug.WriteLine(message); } @@ -88,7 +92,7 @@ namespace Telegrator /// public static void PoolWriteLine(string message, params object[] args) { - if (Debug.IndentLevel.HasFlag(DebugLevel.HandlersPool)) + if (IndentFlags.HasFlag(DebugLevel.HandlersPool)) Debug.WriteLine(message, args); } } diff --git a/Telegrator/MadiatorCore/Descriptors/DescriptorFiltersSet.cs b/Telegrator/MadiatorCore/Descriptors/DescriptorFiltersSet.cs index 2d58018..fc73f62 100644 --- a/Telegrator/MadiatorCore/Descriptors/DescriptorFiltersSet.cs +++ b/Telegrator/MadiatorCore/Descriptors/DescriptorFiltersSet.cs @@ -47,7 +47,7 @@ namespace Telegrator.MadiatorCore.Descriptors { if (!UpdateValidator.CanPass(filterContext)) { - LeveledDebug.FilterWriteLine("(E) UpdateValidator filter of {0} for Update ({2}) didnt pass!", filterContext.Data["handler_name"]); + LeveledDebug.FilterWriteLine("(E) UpdateValidator filter of {0} for Update ({1}) didnt pass!", filterContext.Data["handler_name"], filterContext.Update.Id); return false; } @@ -59,7 +59,7 @@ namespace Telegrator.MadiatorCore.Descriptors { if (!StateKeeperValidator.CanPass(filterContext)) { - LeveledDebug.FilterWriteLine("(E) StateKeeperValidator filter of {0} for Update ({2}) didnt pass!", filterContext.Data["handler_name"]); + LeveledDebug.FilterWriteLine("(E) StateKeeperValidator filter of {0} for Update ({1}) didnt pass!", filterContext.Data["handler_name"], filterContext.Update.Id); return false; } @@ -74,7 +74,7 @@ namespace Telegrator.MadiatorCore.Descriptors if (!filter.CanPass(filterContext)) { if (filter is not AnonymousCompiledFilter && filter is not AnonymousTypeFilter) - LeveledDebug.FilterWriteLine("(E) {0} filter of {1} didnt pass!", filter.GetType().Name, filterContext.Data["handler_name"]); + LeveledDebug.FilterWriteLine("(E) {0} filter of {1} for Update ({2}) didnt pass!", filter.GetType().Name, filterContext.Data["handler_name"], filterContext.Update.Id); return false; } diff --git a/Telegrator/MadiatorCore/Descriptors/HandlerDescriptorList.cs b/Telegrator/MadiatorCore/Descriptors/HandlerDescriptorList.cs index 660deae..4430864 100644 --- a/Telegrator/MadiatorCore/Descriptors/HandlerDescriptorList.cs +++ b/Telegrator/MadiatorCore/Descriptors/HandlerDescriptorList.cs @@ -12,7 +12,7 @@ namespace Telegrator.MadiatorCore.Descriptors { private readonly object _lock = new object(); private readonly SortedList _innerCollection; - private readonly IHandlersCollectingOptions? _options; + private readonly ITelegratorOptions? _options; private readonly UpdateType _handlingType; private int count; @@ -54,7 +54,7 @@ namespace Telegrator.MadiatorCore.Descriptors /// /// The update type for the handlers. /// The collecting options. - public HandlerDescriptorList(UpdateType updateType, IHandlersCollectingOptions? options) + public HandlerDescriptorList(UpdateType updateType, ITelegratorOptions? options) { _innerCollection = []; _handlingType = updateType; diff --git a/Telegrator/MadiatorCore/IUpdateRouter.cs b/Telegrator/MadiatorCore/IUpdateRouter.cs index 6992231..53fbe87 100644 --- a/Telegrator/MadiatorCore/IUpdateRouter.cs +++ b/Telegrator/MadiatorCore/IUpdateRouter.cs @@ -1,5 +1,4 @@ using Telegram.Bot.Polling; -using Telegrator.Configuration; using Telegrator.Handlers.Components; namespace Telegrator.MadiatorCore @@ -11,9 +10,9 @@ namespace Telegrator.MadiatorCore public interface IUpdateRouter : IUpdateHandler, IPollingProvider { /// - /// Gets the for the router. + /// Gets the for the router. /// - public TelegramBotOptions Options { get; } + public TelegratorOptions Options { get; } /// /// Gets the that manages handler execution. diff --git a/Telegrator/Polling/UpdateHandlersPool.cs b/Telegrator/Polling/UpdateHandlersPool.cs index c4a029d..b1a3ee9 100644 --- a/Telegrator/Polling/UpdateHandlersPool.cs +++ b/Telegrator/Polling/UpdateHandlersPool.cs @@ -1,5 +1,4 @@ using System.Collections.Concurrent; -using Telegrator.Configuration; using Telegrator.MadiatorCore; using Telegrator.MadiatorCore.Descriptors; @@ -39,7 +38,7 @@ namespace Telegrator.Polling /// /// The bot configuration options. /// - protected readonly TelegramBotOptions Options; + protected readonly TelegratorOptions Options; /// /// The global cancellation token for stopping all operations. @@ -62,7 +61,7 @@ namespace Telegrator.Polling /// /// The bot configuration options. /// The global cancellation token. - public UpdateHandlersPool(TelegramBotOptions options, CancellationToken globalCancellationToken) + public UpdateHandlersPool(TelegratorOptions options, CancellationToken globalCancellationToken) { Options = options; GlobalCancellationToken = globalCancellationToken; diff --git a/Telegrator/Polling/UpdateRouter.cs b/Telegrator/Polling/UpdateRouter.cs index bd8e9ce..fb6811c 100644 --- a/Telegrator/Polling/UpdateRouter.cs +++ b/Telegrator/Polling/UpdateRouter.cs @@ -17,7 +17,7 @@ namespace Telegrator.Polling /// public class UpdateRouter : IUpdateRouter { - private readonly TelegramBotOptions _options; + private readonly TelegratorOptions _options; private readonly IHandlersProvider _handlersProvider; private readonly IAwaitingProvider _awaitingProvider; private readonly IUpdateHandlersPool _HandlersPool; @@ -30,7 +30,7 @@ namespace Telegrator.Polling public IAwaitingProvider AwaitingProvider => _awaitingProvider; /// - public TelegramBotOptions Options => _options; + public TelegratorOptions Options => _options; /// public IUpdateHandlersPool HandlersPool => _HandlersPool; @@ -48,7 +48,7 @@ namespace Telegrator.Polling /// The provider for awaiting handlers. /// The bot configuration options. /// - public UpdateRouter(IHandlersProvider handlersProvider, IAwaitingProvider awaitingProvider, TelegramBotOptions options, ITelegramBotInfo botInfo) + public UpdateRouter(IHandlersProvider handlersProvider, IAwaitingProvider awaitingProvider, TelegratorOptions options, ITelegramBotInfo botInfo) { _options = options; _handlersProvider = handlersProvider; @@ -65,7 +65,7 @@ namespace Telegrator.Polling /// The bot configuration options. /// The custom handlers pool to use. /// - public UpdateRouter(IHandlersProvider handlersProvider, IAwaitingProvider awaitingProvider, TelegramBotOptions options, IUpdateHandlersPool handlersPool, ITelegramBotInfo botInfo) + public UpdateRouter(IHandlersProvider handlersProvider, IAwaitingProvider awaitingProvider, TelegratorOptions options, IUpdateHandlersPool handlersPool, ITelegramBotInfo botInfo) { _options = options; _handlersProvider = handlersProvider; @@ -149,22 +149,22 @@ namespace Telegrator.Polling /// A collection of described handler information for the update protected virtual IEnumerable GetHandlers(IHandlersProvider provider, IUpdateRouter updateRouter, ITelegramBotClient client, Update update, CancellationToken cancellationToken = default) { - LeveledDebug.ProviderWriteLine("Requested handlers for UpdateType.{0}", update.Type); + LeveledDebug.RouterWriteLine("Requested handlers for UpdateType.{0}", update.Type); if (!provider.TryGetDescriptorList(update.Type, out HandlerDescriptorList? descriptors)) { - LeveledDebug.ProviderWriteLine("No registered, providing Any"); + LeveledDebug.RouterWriteLine("No registered, providing Any"); provider.TryGetDescriptorList(UpdateType.Unknown, out descriptors); } if (descriptors == null || descriptors.Count == 0) { - LeveledDebug.ProviderWriteLine("No handlers provided"); + LeveledDebug.RouterWriteLine("No handlers provided"); return []; } IEnumerable described = DescribeDescriptors(provider, descriptors, updateRouter, client, update, cancellationToken); - LeveledDebug.ProviderWriteLine("Described total of {0} handlers for Update ({1}) from {2} provider", described.Count(), update.Id, provider.GetType().Name); - LeveledDebug.ProviderWriteLine("Described handlers : {0}", string.Join(", ", described)); + LeveledDebug.RouterWriteLine("Described total of {0} handlers for Update ({1}) from {2} provider", described.Count(), update.Id, provider.GetType().Name); + LeveledDebug.RouterWriteLine("Described handlers : {0}", string.Join(", ", described)); return described; } @@ -183,7 +183,7 @@ namespace Telegrator.Polling { try { - LeveledDebug.ProviderWriteLine("Describing descriptors of descriptorsList.HandlingType.{0} for Update ({1})", descriptors.HandlingType, update.Id); + LeveledDebug.RouterWriteLine("Describing descriptors of descriptorsList.HandlingType.{0} for Update ({1})", descriptors.HandlingType, update.Id); foreach (HandlerDescriptor descriptor in descriptors.Reverse()) { cancellationToken.ThrowIfCancellationRequested(); @@ -198,7 +198,7 @@ namespace Telegrator.Polling } finally { - LeveledDebug.ProviderWriteLine("Describing for Update ({0}) finished", update.Id); + LeveledDebug.RouterWriteLine("Describing for Update ({0}) finished", update.Id); } } @@ -247,7 +247,25 @@ namespace Telegrator.Polling sb.AppendFormat(" from {0} ({1})", msg.From.Username, msg.From.Id); if (msg.Text != null) - sb.AppendFormat("'{0}'", msg.Text); + sb.AppendFormat(" with text '{0}'", msg.Text); + + if (msg.Sticker != null) + sb.AppendFormat(" with sticker '{0}'", msg.Sticker.Emoji); + + LeveledDebug.RouterWriteLine(sb.ToString()); + break; + } + + case UpdateType.CallbackQuery: + { + CallbackQuery cq = update.CallbackQuery ?? throw new NullReferenceException(); + StringBuilder sb = new StringBuilder("Update.CallbackQuery"); + + if (cq.From != null) + sb.AppendFormat(" from {0} ({1})", cq.From.Username, cq.From.Id); + + if (cq.From != null) + sb.AppendFormat(" with data '{0}'", cq.Data); LeveledDebug.RouterWriteLine(sb.ToString()); break; diff --git a/Telegrator/Providers/AwaitingProvider.cs b/Telegrator/Providers/AwaitingProvider.cs index 49ad817..356e35e 100644 --- a/Telegrator/Providers/AwaitingProvider.cs +++ b/Telegrator/Providers/AwaitingProvider.cs @@ -1,5 +1,4 @@ using Telegram.Bot.Types.Enums; -using Telegrator.Configuration; using Telegrator.MadiatorCore; using Telegrator.MadiatorCore.Descriptors; @@ -10,7 +9,7 @@ namespace Telegrator.Providers /// Extends HandlersProvider to provide functionality for creating and managing awaiter handlers. /// /// The bot configuration options. - public class AwaitingProvider(TelegramBotOptions options) : HandlersProvider([], options), IAwaitingProvider + public class AwaitingProvider(TelegratorOptions options) : HandlersProvider([], options), IAwaitingProvider { /// /// List of handler descriptors for awaiting handlers. diff --git a/Telegrator/Providers/HandlersCollection.cs b/Telegrator/Providers/HandlersCollection.cs index ecb0f4b..0ecb87d 100644 --- a/Telegrator/Providers/HandlersCollection.cs +++ b/Telegrator/Providers/HandlersCollection.cs @@ -14,7 +14,7 @@ namespace Telegrator.Providers /// Provides functionality for collecting, adding, and organizing handlers. /// /// Optional configuration options for handler collecting. - public class HandlersCollection(IHandlersCollectingOptions? options) : IHandlersCollection + public class HandlersCollection(ITelegratorOptions? options) : IHandlersCollection { private readonly List _allowedTypes = []; @@ -26,7 +26,7 @@ namespace Telegrator.Providers /// /// Configuration options for handler collecting. /// - protected readonly IHandlersCollectingOptions? Options = options; + protected readonly ITelegratorOptions? Options = options; /// /// Gets whether handlers must have a parameterless constructor. diff --git a/Telegrator/Providers/HandlersProvider.cs b/Telegrator/Providers/HandlersProvider.cs index fed5f5f..fb8a7f9 100644 --- a/Telegrator/Providers/HandlersProvider.cs +++ b/Telegrator/Providers/HandlersProvider.cs @@ -2,7 +2,6 @@ using System.Runtime.CompilerServices; using Telegram.Bot.Types; using Telegram.Bot.Types.Enums; -using Telegrator.Configuration; using Telegrator.Handlers.Components; using Telegrator.MadiatorCore; using Telegrator.MadiatorCore.Descriptors; @@ -28,7 +27,7 @@ namespace Telegrator.Providers /// /// Configuration options for the bot and handler execution behavior. /// - protected readonly TelegramBotOptions Options; + protected readonly TelegratorOptions Options; /// /// Initializes a new instance of with the specified handler collections and configuration. @@ -36,11 +35,12 @@ namespace Telegrator.Providers /// 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, TelegramBotOptions options) + public HandlersProvider(IHandlersCollection handlers, TelegratorOptions options) { AllowedTypes = handlers.AllowedTypes; HandlersDictionary = handlers.Values.ForEach(list => list.Freeze()).ToReadOnlyDictionary(list => list.HandlingType); Options = options ?? throw new ArgumentNullException(nameof(options)); + LeveledDebug.ProviderWriteLine("{0} created!", GetType().Name); } /// @@ -49,27 +49,36 @@ namespace Telegrator.Providers /// 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, TelegramBotOptions options) + 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)); + LeveledDebug.ProviderWriteLine("{0} created!", GetType().Name); } /// /// Thrown when the descriptor type is not recognized public virtual UpdateHandlerBase GetHandlerInstance(HandlerDescriptor descriptor, CancellationToken cancellationToken = default) { - cancellationToken.ThrowIfCancellationRequested(); - bool useSingleton = UseSingleton(descriptor); + try + { + cancellationToken.ThrowIfCancellationRequested(); + bool useSingleton = UseSingleton(descriptor); - if (useSingleton && descriptor.SingletonInstance != null) - return descriptor.SingletonInstance; + if (useSingleton && descriptor.SingletonInstance != null) + return descriptor.SingletonInstance; - UpdateHandlerBase instance = GetHandlerInstanceInternal(descriptor); - descriptor.SingletonInstance = useSingleton ? instance : null; - descriptor.LazyInitialization?.Invoke(instance); - return instance; + UpdateHandlerBase instance = GetHandlerInstanceInternal(descriptor); + descriptor.SingletonInstance = useSingleton ? instance : null; + descriptor.LazyInitialization?.Invoke(instance); + return instance; + } + catch + { + LeveledDebug.ProviderWriteLine("Failed to create instance of {0}", descriptor.ToString()); + throw; + } } private static UpdateHandlerBase GetHandlerInstanceInternal(HandlerDescriptor descriptor) @@ -84,7 +93,7 @@ namespace Telegrator.Providers { DescriptorType.General or DescriptorType.Keyed => false, DescriptorType.Implicit or DescriptorType.Singleton => true, - _ => throw new Exception() + _ => throw new Exception("Unknown decriptor type") }; /// diff --git a/Telegrator/Telegrator.csproj b/Telegrator/Telegrator.csproj index 952510e..cd0d8fa 100644 --- a/Telegrator/Telegrator.csproj +++ b/Telegrator/Telegrator.csproj @@ -17,7 +17,7 @@ True True LICENSE - 1.0.6 + 1.0.7 diff --git a/Telegrator/TelegratorClient.cs b/Telegrator/TelegratorClient.cs index 73e760e..499d1b3 100644 --- a/Telegrator/TelegratorClient.cs +++ b/Telegrator/TelegratorClient.cs @@ -19,7 +19,7 @@ namespace Telegrator private IUpdateRouter? updateRouter = null; /// - public TelegramBotOptions Options { get; private set; } + public TelegratorOptions Options { get; private set; } /// public IHandlersCollection Handlers { get; private set; } @@ -47,7 +47,7 @@ namespace Telegrator /// The cancellation token. public TelegratorClient(TelegramBotClientOptions options, HttpClient? httpClient = null, CancellationToken cancellationToken = default) : base(options, httpClient, cancellationToken) { - Options = new TelegramBotOptions(); + Options = new TelegratorOptions(); Handlers = new HandlersCollection(default); BotInfo = new TelegramBotInfo(this.GetMe(cancellationToken).Result); } diff --git a/Telegrator/TelegratorOptions.cs b/Telegrator/TelegratorOptions.cs new file mode 100644 index 0000000..6343b2d --- /dev/null +++ b/Telegrator/TelegratorOptions.cs @@ -0,0 +1,30 @@ +using Telegrator.Configuration; + +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 : ITelegratorOptions + { + /// + public bool ExecuteOnlyFirstFoundHanlder { get; set; } + + /// + public int? MaximumParallelWorkingHandlers { get; set; } + + /// + public bool ExclusiveAwaitingHandlerRouting { get; set; } + + /// + public CancellationToken GlobalCancellationToken { get; set; } + + /// + public bool DescendDescriptorIndex { get; set; } = true; + + /// + public bool ExceptIntersectingCommandAliases { get; set; } = true; + + } +}