Rewrote hosts again

This commit is contained in:
2026-03-07 02:35:53 +04:00
parent 3cdc058fb5
commit 370c0cecda
24 changed files with 184 additions and 10040 deletions
@@ -1,14 +0,0 @@
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Routing;
namespace Telegrator.Hosting.Web
{
/// <summary>
/// Interface for Telegram bot hosts with Webhook update receiving.
/// Combines wbe application capabilities with reactive Telegram bot functionality.
/// </summary>
public interface ITelegramBotWebHost : ITelegramBotHost, IEndpointRouteBuilder, IApplicationBuilder, IAsyncDisposable
{
}
}
@@ -12,7 +12,7 @@ namespace Telegrator.Hosting.Web
/// <summary>
/// Represents a web hosted telegram bot
/// </summary>
public class TelegramBotWebHost : ITelegramBotWebHost
public class TelegramBotWebHost : IHost, IApplicationBuilder, IEndpointRouteBuilder, IAsyncDisposable
{
private readonly WebApplication _innerApp;
private readonly IUpdateRouter _updateRouter;
@@ -51,11 +51,6 @@ namespace Telegrator.Hosting.Web
/// <param name="webApplicationBuilder">The proxied instance of host builder.</param>
public TelegramBotWebHost(WebApplicationBuilder webApplicationBuilder)
{
// Registering this host in services for easy access
webApplicationBuilder.Services.AddSingleton<ITelegramBotHost>(this);
webApplicationBuilder.Services.AddSingleton<ITelegramBotWebHost>(this);
webApplicationBuilder.Services.AddSingleton<ITelegratorBot>(this);
// Building proxy application
_innerApp = webApplicationBuilder.Build();
_innerApp.UseTelegratorWeb();
@@ -69,11 +64,12 @@ namespace Telegrator.Hosting.Web
/// Creates new <see cref="TelegramBotHostBuilder"/> with default services and webhook update receiving scheme
/// </summary>
/// <returns></returns>
public static TelegramBotWebHostBuilder CreateBuilder(TelegramBotWebOptions settings)
public static TelegramBotWebHostBuilder CreateBuilder(WebApplicationOptions settings)
{
ArgumentNullException.ThrowIfNull(settings, nameof(settings));
WebApplicationBuilder innerApp = WebApplication.CreateBuilder(settings.ToWebApplicationOptions());
WebApplicationBuilder innerApp = WebApplication.CreateBuilder(settings);
TelegramBotWebHostBuilder builder = new TelegramBotWebHostBuilder(innerApp, settings);
builder.Services.AddTelegramBotHostDefaults();
builder.Services.AddTelegramWebhook();
return builder;
@@ -83,11 +79,12 @@ namespace Telegrator.Hosting.Web
/// Creates new SLIM <see cref="TelegramBotHostBuilder"/> with default services and webhook update receiving scheme
/// </summary>
/// <returns></returns>
public static TelegramBotWebHostBuilder CreateSlimBuilder(TelegramBotWebOptions settings)
public static TelegramBotWebHostBuilder CreateSlimBuilder(WebApplicationOptions settings)
{
ArgumentNullException.ThrowIfNull(settings, nameof(settings));
WebApplicationBuilder innerApp = WebApplication.CreateSlimBuilder(settings.ToWebApplicationOptions());
WebApplicationBuilder innerApp = WebApplication.CreateSlimBuilder(settings);
TelegramBotWebHostBuilder builder = new TelegramBotWebHostBuilder(innerApp, settings);
builder.Services.AddTelegramBotHostDefaults();
builder.Services.AddTelegramWebhook();
return builder;
@@ -97,10 +94,10 @@ namespace Telegrator.Hosting.Web
/// Creates new EMPTY <see cref="TelegramBotHostBuilder"/> WITHOUT any services or update receiving schemes
/// </summary>
/// <returns></returns>
public static TelegramBotWebHostBuilder CreateEmptyBuilder(TelegramBotWebOptions settings)
public static TelegramBotWebHostBuilder CreateEmptyBuilder(WebApplicationOptions settings)
{
ArgumentNullException.ThrowIfNull(settings, nameof(settings));
WebApplicationBuilder innerApp = WebApplication.CreateEmptyBuilder(settings.ToWebApplicationOptions());
WebApplicationBuilder innerApp = WebApplication.CreateEmptyBuilder(settings);
return new TelegramBotWebHostBuilder(innerApp, settings);
}
@@ -154,10 +151,8 @@ namespace Telegrator.Hosting.Web
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);
disposeTask.AsTask().Wait();
GC.SuppressFinalize(this);
_disposed = true;
@@ -18,8 +18,8 @@ namespace Telegrator.Hosting.Web
public class TelegramBotWebHostBuilder : ITelegramBotHostBuilder
{
private readonly WebApplicationBuilder _innerBuilder;
private readonly TelegramBotWebOptions _settings;
private readonly IHandlersCollection _handlers;
private readonly WebApplicationOptions _settings;
internal IHandlersCollection _handlers = null!;
/// <inheritdoc/>
public IHandlersCollection Handlers => _handlers;
@@ -41,11 +41,10 @@ namespace Telegrator.Hosting.Web
/// </summary>
/// <param name="webApplicationBuilder"></param>
/// <param name="settings"></param>
public TelegramBotWebHostBuilder(WebApplicationBuilder webApplicationBuilder, TelegramBotWebOptions settings)
public TelegramBotWebHostBuilder(WebApplicationBuilder webApplicationBuilder, WebApplicationOptions settings)
{
_innerBuilder = webApplicationBuilder ?? throw new ArgumentNullException(nameof(webApplicationBuilder));
_settings = settings ?? throw new ArgumentNullException(nameof(settings));
_handlers = new HostHandlersCollection(Services, _settings);
_innerBuilder.AddTelegratorWeb(settings);
}
@@ -56,13 +55,12 @@ namespace Telegrator.Hosting.Web
/// <param name="webApplicationBuilder"></param>
/// <param name="handlers"></param>
/// <param name="settings"></param>
public TelegramBotWebHostBuilder(WebApplicationBuilder webApplicationBuilder, TelegramBotWebOptions settings, IHandlersCollection handlers)
public TelegramBotWebHostBuilder(WebApplicationBuilder webApplicationBuilder, IHandlersCollection handlers, WebApplicationOptions settings)
{
_innerBuilder = webApplicationBuilder ?? throw new ArgumentNullException(nameof(webApplicationBuilder));
_settings = settings ?? throw new ArgumentNullException(nameof(settings));
_handlers = handlers ?? throw new ArgumentNullException(nameof(settings));
_innerBuilder.AddTelegratorWeb(settings, handlers);
_innerBuilder.AddTelegratorWeb(settings, null, handlers);
}
/// <summary>
@@ -71,7 +69,9 @@ namespace Telegrator.Hosting.Web
/// <returns></returns>
public TelegramBotWebHost Build()
{
return new TelegramBotWebHost(_innerBuilder);
TelegramBotWebHost host = new TelegramBotWebHost(_innerBuilder);
host.UseTelegrator();
return host;
}
}
}
@@ -1,40 +0,0 @@
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.Options;
namespace Telegrator.Hosting.Web
{
/// <summary>
/// Options for configuring the behavior for TelegramBotWebHost.
/// </summary>
public class TelegramBotWebOptions : TelegratorOptions
{
/// <summary>
/// Disables automatic configuration for all of required <see cref="IOptions{TOptions}"/> instances
/// </summary>
public bool DisableAutoConfigure { get; set; }
/// <inheritdoc cref="WebApplicationOptions.Args"/>
public string[]? Args { get; init; }
/// <inheritdoc cref="WebApplicationOptions.EnvironmentName"/>
public string? EnvironmentName { get; init; }
/// <inheritdoc cref="WebApplicationOptions.ApplicationName"/>
public string? ApplicationName { get; init; }
/// <inheritdoc cref="WebApplicationOptions.ContentRootPath"/>
public string? ContentRootPath { get; init; }
/// <inheritdoc cref="WebApplicationOptions.WebRootPath"/>
public string? WebRootPath { get; init; }
internal WebApplicationOptions ToWebApplicationOptions() => new WebApplicationOptions()
{
ApplicationName = ApplicationName,
Args = Args,
ContentRootPath = ContentRootPath,
EnvironmentName = EnvironmentName,
WebRootPath = WebRootPath
};
}
}
@@ -6,20 +6,20 @@ 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.
/// </summary>
public class TelegratorWebOptions
public class WebhookerOptions
{
/// <summary>
/// Gets or sets HTTPS URL to send updates to. Use an empty string to remove webhook integration
/// </summary>
[StringSyntax(StringSyntaxAttribute.Uri)]
public required string WebhookUri { get; set; }
public string WebhookUri { get; set; } = string.Empty;
/// <summary>
/// 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.
/// </summary>
public string? SecretToken { get; set; }
public string? SecretToken { get; set; } = null;
/// <summary>
/// The maximum allowed number of simultaneous HTTPS connections to the webhook for update delivery, 1-100. Defaults to 40.
@@ -30,6 +30,6 @@ namespace Telegrator.Hosting.Web
/// <summary>
/// Pass true to drop all pending updates
/// </summary>
public bool DropPendingUpdates { get; set; }
public bool DropPendingUpdates { get; set; } = false;
}
}
@@ -22,7 +22,7 @@ namespace Telegrator.Mediation
private readonly IEndpointRouteBuilder _botHost;
private readonly ITelegramBotClient _botClient;
private readonly IUpdateRouter _updateRouter;
private readonly TelegratorWebOptions _options;
private readonly WebhookerOptions _options;
/// <summary>
/// Initiallizes new instance of <see cref="HostedUpdateWebhooker"/>
@@ -32,7 +32,7 @@ namespace Telegrator.Mediation
/// <param name="updateRouter"></param>
/// <param name="options"></param>
/// <exception cref="ArgumentNullException"></exception>
public HostedUpdateWebhooker(IEndpointRouteBuilder botHost, ITelegramBotClient botClient, IUpdateRouter updateRouter, IOptions<TelegratorWebOptions> options)
public HostedUpdateWebhooker(IEndpointRouteBuilder botHost, ITelegramBotClient botClient, IUpdateRouter updateRouter, IOptions<WebhookerOptions> options)
{
if (string.IsNullOrEmpty(options.Value.WebhookUri))
throw new ArgumentNullException(nameof(options), "Option \"WebhookUrl\" must be set to subscribe for update recieving");
+51 -43
View File
@@ -5,10 +5,9 @@ using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using System.Diagnostics;
using Telegram.Bot;
using Telegrator;
using Telegrator.Core;
using Telegrator.Hosting;
using Telegrator.Hosting.Web;
using Telegrator.Mediation;
using Telegrator.Providers;
@@ -17,7 +16,7 @@ namespace Telegrator
{
/// <summary>
/// Contains extensions for <see cref="IServiceCollection"/>
/// Provides method to configure <see cref="ITelegramBotWebHost"/>
/// Provides method to configure Telegram Bot WebHost
/// </summary>
public static class ServicesCollectionExtensions
{
@@ -31,69 +30,78 @@ namespace Telegrator
/// <summary>
/// Gets the <see cref="IHandlersCollection"/> from the builder properties.
/// </summary>
public IHandlersCollection Handlers => (IHandlersCollection)builder.Properties[HandlersCollectionPropertyKey];
public IHandlersCollection Handlers
{
get
{
if (builder is TelegramBotHostBuilder botHostBuilder)
return botHostBuilder.Handlers;
if (builder is TelegramBotWebHostBuilder webBotHostBuilder)
return webBotHostBuilder.Handlers;
return (IHandlersCollection)builder.Properties[HandlersCollectionPropertyKey];
}
}
}
/// <summary>
/// Replaces TelegramBotWebHostBuilder. Configures DI, options, and handlers.
/// </summary>
public static WebApplicationBuilder AddTelegratorWeb(this WebApplicationBuilder builder, TelegramBotWebOptions settings, IHandlersCollection? handlers = null)
public static IHostApplicationBuilder AddTelegratorWeb(this IHostApplicationBuilder builder, WebApplicationOptions settings, TelegratorOptions? options = null, IHandlersCollection? handlers = null)
{
if (settings is null)
throw new ArgumentNullException(nameof(settings));
IServiceCollection services = builder.Services;
ConfigurationManager configuration = builder.Configuration;
IConfigurationManager configuration = builder.Configuration;
handlers ??= new HostHandlersCollection(services, settings);
builder.Host.Properties.Add(HandlersCollectionPropertyKey, handlers);
if (handlers is IHostHandlersCollection hostHandlers)
if (options == null)
{
foreach (PreBuildingRoutine preBuildRoutine in hostHandlers.PreBuilderRoutines)
options = configuration.GetSection(nameof(TelegratorOptions)).Get<TelegratorOptions>();
if (options == null)
throw new MissingMemberException("Auto configuration disabled, yet no options of type 'TelegratorOptions' wasn't registered. This configuration is runtime required!");
}
services.AddSingleton(Options.Create(options));
if (handlers != null)
{
if (handlers is IHandlersManager manager)
{
try
{
// TODO: fix
//preBuildRoutine.Invoke(builder);
Debug.WriteLine("Pre-Building routine was not executed");
}
catch (NotImplementedException)
{
_ = 0xBAD + 0xC0DE;
}
ServiceDescriptor descriptor = new ServiceDescriptor(typeof(IHandlersProvider), manager);
services.Replace(descriptor);
services.AddSingleton(manager);
}
}
if (!settings.DisableAutoConfigure)
{
services.Configure<TelegratorWebOptions>(configuration.GetSection(nameof(TelegratorWebOptions)));
services.Configure<TelegratorWebOptions>(configuration.GetSection(nameof(TelegramBotClientOptions)));
}
else
{
if (!services.Any(srvc => srvc.ImplementationType == typeof(IOptions<TelegratorWebOptions>)))
throw new MissingMemberException("Auto configuration disabled, yet no options of type 'TelegratorWebOptions' wasn't registered. This configuration is runtime required!");
if (!services.Any(srvc => srvc.ImplementationType == typeof(IOptions<TelegramBotClientOptions>)))
throw new MissingMemberException("Auto configuration disabled, yet no options of type 'TelegramBotClientOptions' wasn't registered. This configuration is runtime required!");
}
IOptions<TelegramBotWebOptions> options = Options.Create(settings);
services.AddSingleton((IOptions<TelegratorOptions>)options);
services.AddSingleton(options);
handlers ??= new HostHandlersCollection(services, options);
services.AddSingleton(handlers);
if (handlers is IHandlersManager manager)
builder.Properties.Add(HandlersCollectionPropertyKey, handlers);
if (builder is TelegramBotWebHostBuilder botHostBuilder)
botHostBuilder._handlers = handlers;
if (!services.Any(srvc => srvc.ImplementationType == typeof(IOptions<WebhookerOptions>)))
{
ServiceDescriptor descriptor = new ServiceDescriptor(typeof(IHandlersProvider), manager);
services.Replace(descriptor);
services.AddSingleton(manager);
WebhookerOptions? webhookerOptions = configuration.GetSection(nameof(WebhookerOptions)).Get<WebhookerOptions>();
if (webhookerOptions == null)
throw new MissingMemberException("Auto configuration disabled, yet no options of type 'WebhookerOptions' wasn't registered. This configuration is runtime required!");
services.AddSingleton(Options.Create(webhookerOptions));
}
if (!services.Any(srvc => srvc.ImplementationType == typeof(IOptions<TelegramBotClientOptions>)))
{
services.AddSingleton(Options.Create(new TelegramBotClientOptions(options.Token, options.BaseUrl, options.UseTestEnvironment)
{
RetryCount = options.RetryCount,
RetryThreshold = options.RetryThreshold
}));
}
services.AddTelegramBotHostDefaults();
services.AddTelegramWebhook();
return builder;
}
@@ -1,53 +0,0 @@
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
namespace Telegrator
{
/// <summary>
/// Abstract base class for configuring options from configuration sources.
/// Provides a proxy pattern for binding configuration to strongly-typed options classes.
/// </summary>
/// <typeparam name="TOptions">The type of options to configure.</typeparam>
public abstract class ConfigureOptionsProxy<TOptions> where TOptions : class
{
/// <summary>
/// Configures the options using the default configuration section.
/// </summary>
/// <param name="services">The service collection to configure.</param>
/// <param name="configuration">The configuration source.</param>
public void Configure(IServiceCollection services, IConfiguration configuration)
=> Configure(services, Options.DefaultName, configuration, null);
/// <summary>
/// Configures the options using a named configuration section.
/// </summary>
/// <param name="services">The service collection to configure.</param>
/// <param name="name">The name of the configuration section.</param>
/// <param name="configuration">The configuration source.</param>
public void Configure(IServiceCollection services, string? name, IConfiguration configuration)
=> Configure(services, name, configuration, null);
/// <summary>
/// Configures the options using a named configuration section with custom binder options.
/// </summary>
/// <param name="services">The service collection to configure.</param>
/// <param name="name">The name of the configuration section.</param>
/// <param name="configuration">The configuration source.</param>
/// <param name="configureBinder">Optional action to configure the binder options.</param>
public void Configure(IServiceCollection services, string? name, IConfiguration configuration, Action<BinderOptions>? configureBinder)
{
var namedConfigure = new NamedConfigureFromConfigurationOptions<ConfigureOptionsProxy<TOptions>>(name, configuration, configureBinder);
namedConfigure.Configure(name, this);
services.AddOptions();
services.AddSingleton(Options.Create(Realize()));
}
/// <summary>
/// Creates the actual options instance from the configuration.
/// </summary>
/// <returns>The configured options instance.</returns>
protected abstract TOptions Realize();
}
}
@@ -1,16 +0,0 @@
using Telegrator.Providers;
namespace Telegrator.Core
{
/// <summary>
/// Collection class for managing handler descriptors organized by update type for host apps.
/// Provides functionality for collecting, adding, scanning, and organizing handlers.
/// </summary>
public interface IHostHandlersCollection : IHandlersCollection
{
/// <summary>
/// List of tasks that should be completed right before building the bot
/// </summary>
public List<PreBuildingRoutine> PreBuilderRoutines { get; }
}
}
@@ -1,17 +0,0 @@
using Telegrator.Hosting;
namespace Telegrator.Handlers
{
/// <summary>
/// Interface for pre-building routines that can be executed during host construction.
/// Allows for custom initialization and configuration steps before the bot host is built.
/// </summary>
public interface IPreBuildingRoutine
{
/// <summary>
/// Executes the pre-building routine on the specified host builder.
/// </summary>
/// <param name="hostBuilder">The host builder to configure.</param>
public static abstract void PreBuildingRoutine(ITelegramBotHostBuilder hostBuilder);
}
}
@@ -1,13 +0,0 @@
using Microsoft.Extensions.Hosting;
namespace Telegrator.Hosting
{
/// <summary>
/// Interface for Telegram bot hosts.
/// Combines host application capabilities with reactive Telegram bot functionality.
/// </summary>
public interface ITelegramBotHost : IHost, ITelegratorBot
{
}
}
@@ -1,4 +1,5 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Telegrator.Core;
@@ -8,7 +9,7 @@ namespace Telegrator.Hosting
/// <summary>
/// Represents a hosted telegram bot
/// </summary>
public class TelegramBotHost : ITelegramBotHost
public class TelegramBotHost : IHost, ITelegratorBot
{
private readonly IHost _innerHost;
private readonly IServiceProvider _serviceProvider;
@@ -33,10 +34,9 @@ namespace Telegrator.Hosting
/// </summary>
/// <param name="hostApplicationBuilder">The proxied instance of host builder.</param>
/// <param name="handlers"></param>
public TelegramBotHost(HostApplicationBuilder hostApplicationBuilder, IHandlersCollection handlers)
public TelegramBotHost(HostApplicationBuilder hostApplicationBuilder)
{
// Registering this host in services for easy access
hostApplicationBuilder.Services.AddSingleton<ITelegramBotHost>(this);
hostApplicationBuilder.Services.AddSingleton<ITelegratorBot>(this);
// Building proxy hoster
@@ -47,9 +47,6 @@ namespace Telegrator.Hosting
// Reruesting services for this host
_updateRouter = Services.GetRequiredService<IUpdateRouter>();
_logger = Services.GetRequiredService<ILogger<TelegramBotHost>>();
// Logging registering handlers in DEBUG purposes
_logger.LogHandlers(handlers);
}
/// <summary>
@@ -60,6 +57,7 @@ namespace Telegrator.Hosting
{
HostApplicationBuilder innerBuilder = new HostApplicationBuilder(settings: null);
TelegramBotHostBuilder builder = new TelegramBotHostBuilder(innerBuilder, null);
builder.Services.AddTelegramBotHostDefaults();
builder.Services.AddTelegramReceiver();
return builder;
@@ -69,10 +67,11 @@ namespace Telegrator.Hosting
/// Creates new <see cref="TelegramBotHostBuilder"/> with default services and long-polling update receiving scheme
/// </summary>
/// <returns></returns>
public static TelegramBotHostBuilder CreateBuilder(TelegramBotHostBuilderSettings? settings)
public static TelegramBotHostBuilder CreateBuilder(HostApplicationBuilderSettings? settings)
{
HostApplicationBuilder innerBuilder = new HostApplicationBuilder(settings?.ToApplicationBuilderSettings());
HostApplicationBuilder innerBuilder = new HostApplicationBuilder(settings);
TelegramBotHostBuilder builder = new TelegramBotHostBuilder(innerBuilder, settings);
builder.Services.AddTelegramBotHostDefaults();
builder.Services.AddTelegramReceiver();
return builder;
@@ -92,7 +91,7 @@ namespace Telegrator.Hosting
/// Creates new EMPTY <see cref="TelegramBotHostBuilder"/> WITHOUT any services or update receiving schemes
/// </summary>
/// <returns></returns>
public static TelegramBotHostBuilder CreateEmptyBuilder(TelegramBotHostBuilderSettings? settings)
public static TelegramBotHostBuilder CreateEmptyBuilder(HostApplicationBuilderSettings? settings)
{
HostApplicationBuilder innerBuilder = Host.CreateEmptyApplicationBuilder(null);
return new TelegramBotHostBuilder(innerBuilder, settings);
@@ -11,11 +11,11 @@ namespace Telegrator.Hosting
/// <summary>
/// Represents a hosted telegram bots and services builder that helps manage configuration, logging, lifetime, and more.
/// </summary>
public class TelegramBotHostBuilder : ITelegramBotHostBuilder
public class TelegramBotHostBuilder : ICollectingProvider
{
private readonly HostApplicationBuilder _innerBuilder;
private readonly TelegramBotHostBuilderSettings _settings;
private readonly IHandlersCollection _handlers;
private readonly HostApplicationBuilderSettings _settings;
internal IHandlersCollection _handlers = null!;
/// <inheritdoc/>
public IHandlersCollection Handlers => _handlers;
@@ -37,13 +37,12 @@ namespace Telegrator.Hosting
/// </summary>
/// <param name="hostApplicationBuilder"></param>
/// <param name="settings"></param>
public TelegramBotHostBuilder(HostApplicationBuilder hostApplicationBuilder, TelegramBotHostBuilderSettings? settings = null)
public TelegramBotHostBuilder(HostApplicationBuilder hostApplicationBuilder, HostApplicationBuilderSettings? settings = null)
{
_innerBuilder = hostApplicationBuilder ?? throw new ArgumentNullException(nameof(hostApplicationBuilder));
_settings = settings ?? new TelegramBotHostBuilderSettings();
_handlers = new HostHandlersCollection(Services, _settings);
_settings = settings ?? new HostApplicationBuilderSettings();
_innerBuilder.AddTelegrator(_settings, _handlers);
_innerBuilder.AddTelegrator(_settings);
_innerBuilder.Logging.ClearProviders();
}
@@ -53,13 +52,12 @@ namespace Telegrator.Hosting
/// <param name="hostApplicationBuilder"></param>
/// <param name="handlers"></param>
/// <param name="settings"></param>
public TelegramBotHostBuilder(HostApplicationBuilder hostApplicationBuilder, IHandlersCollection handlers, TelegramBotHostBuilderSettings? settings = null)
public TelegramBotHostBuilder(HostApplicationBuilder hostApplicationBuilder, IHandlersCollection handlers, HostApplicationBuilderSettings? settings = null)
{
_innerBuilder = hostApplicationBuilder ?? throw new ArgumentNullException(nameof(hostApplicationBuilder));
_settings = settings ?? new TelegramBotHostBuilderSettings();
_handlers = handlers ?? throw new ArgumentNullException(nameof(handlers));
_settings = settings ?? new HostApplicationBuilderSettings();
_innerBuilder.AddTelegrator(_settings, _handlers);
_innerBuilder.AddTelegrator(_settings, null, handlers);
_innerBuilder.Logging.ClearProviders();
}
@@ -69,7 +67,9 @@ namespace Telegrator.Hosting
/// <returns></returns>
public TelegramBotHost Build()
{
return new TelegramBotHost(_innerBuilder, _handlers);
TelegramBotHost host = new TelegramBotHost(_innerBuilder);
host.UseTelegrator();
return host;
}
}
}
@@ -1,45 +0,0 @@
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Options;
namespace Telegrator.Hosting
{
/// <summary>
/// Settings os hosted Telegram bot
/// </summary>
public class TelegramBotHostBuilderSettings() : TelegratorOptions
{
/// <summary>
/// Disables automatic configuration for all of required <see cref="IOptions{TOptions}"/> instances
/// </summary>
public bool DisableAutoConfigure { get; set; }
/// <inheritdoc cref="HostApplicationBuilderSettings.DisableDefaults"/>
public bool DisableDefaults { get; set; }
/// <inheritdoc cref="HostApplicationBuilderSettings.Args"/>
public string[]? Args { get; set; }
/// <inheritdoc cref="HostApplicationBuilderSettings.Configuration"/>
public ConfigurationManager? Configuration { get; set; }
/// <inheritdoc cref="HostApplicationBuilderSettings.EnvironmentName"/>
public string? EnvironmentName { get; set; }
/// <inheritdoc cref="HostApplicationBuilderSettings.ApplicationName"/>
public string? ApplicationName { get; set; }
/// <inheritdoc cref="HostApplicationBuilderSettings.ContentRootPath"/>
public string? ContentRootPath { get; set; }
internal HostApplicationBuilderSettings ToApplicationBuilderSettings() => new HostApplicationBuilderSettings()
{
DisableDefaults = DisableDefaults,
Args = Args,
Configuration = Configuration,
EnvironmentName = EnvironmentName,
ApplicationName = ApplicationName,
ContentRootPath = ContentRootPath
};
}
}
@@ -4,7 +4,6 @@ using Microsoft.Extensions.Options;
using Telegram.Bot;
using Telegram.Bot.Polling;
using Telegrator.Core;
using Telegrator.Hosting;
using Telegrator.Mediation;
namespace Telegrator.Polling
@@ -17,7 +16,7 @@ namespace Telegrator.Polling
/// <param name="updateRouter"></param>
/// <param name="options"></param>
/// <param name="logger"></param>
public class HostedUpdateReceiver(ITelegramBotHost botHost, ITelegramBotClient botClient, IUpdateRouter updateRouter, IOptions<ReceiverOptions> options, ILogger<HostedUpdateReceiver> logger) : BackgroundService
public class HostedUpdateReceiver(ITelegramBotClient botClient, IUpdateRouter updateRouter, IOptions<ReceiverOptions> options, ILogger<HostedUpdateReceiver> logger) : BackgroundService
{
private readonly ReceiverOptions _receiverOptions = options.Value;
private readonly IUpdateRouter _updateRouter = updateRouter;
@@ -26,7 +25,7 @@ namespace Telegrator.Polling
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
logger.LogInformation("Starting receiving updates via long-polling");
_receiverOptions.AllowedUpdates = botHost.UpdateRouter.HandlersProvider.AllowedTypes.ToArray();
_receiverOptions.AllowedUpdates = _updateRouter.HandlersProvider.AllowedTypes.ToArray();
DefaultUpdateReceiver updateReceiver = new DefaultUpdateReceiver(botClient, _receiverOptions);
await updateReceiver.ReceiveAsync(_updateRouter, stoppingToken).ConfigureAwait(false);
}
@@ -1,36 +1,20 @@
using Microsoft.Extensions.DependencyInjection;
using System.Reflection;
using Telegrator.Core;
using Telegrator.Core.Descriptors;
using Telegrator.Hosting;
namespace Telegrator.Providers
{
/// <summary>
/// Pre host building task
/// </summary>
/// <param name="builder"></param>
public delegate void PreBuildingRoutine(ITelegramBotHostBuilder builder);
/// <inheritdoc/>
public class HostHandlersCollection(IServiceCollection hostServiceColletion, TelegratorOptions options) : HandlersCollection(options), IHostHandlersCollection
public class HostHandlersCollection(IServiceCollection hostServiceColletion, TelegratorOptions options) : HandlersCollection(options)
{
private readonly IServiceCollection Services = hostServiceColletion;
/// <inheritdoc/>
protected override bool MustHaveParameterlessCtor => false;
/// <summary>
/// List of tasks that should be completed right before building the bot
/// </summary>
public List<PreBuildingRoutine> PreBuilderRoutines { get; } = [];
/// <inheritdoc/>
public override IHandlersCollection AddDescriptor(HandlerDescriptor descriptor)
{
if (descriptor.HandlerType.IsPreBuildingRoutine(out MethodInfo? routineMethod))
PreBuilderRoutines.Add(routineMethod.CreateDelegate<PreBuildingRoutine>(null));
switch (descriptor.Type)
{
case DescriptorType.General:
@@ -1,46 +0,0 @@
using Telegram.Bot;
namespace Telegrator
{
/// <summary>
/// Internal proxy class for configuring Telegram bot client options from configuration.
/// Extends ConfigureOptionsProxy to provide specific configuration for Telegram bot client options.
/// </summary>
public class TelegramBotClientOptionsProxy : ConfigureOptionsProxy<TelegramBotClientOptions>
{
/// <summary>
/// Gets or sets the bot token.
/// </summary>
public string Token { get; set; } = string.Empty;
/// <summary>
/// Gets or sets the base URL for the bot API.
/// </summary>
public string? BaseUrl { get; set; } = null;
/// <summary>
/// Gets or sets whether to use the test environment.
/// </summary>
public bool UseTestEnvironment { get; set; } = false;
/// <summary>
/// Gets or sets the retry threshold in seconds.
/// </summary>
public int RetryThreshold { get; set; } = 60;
/// <summary>
/// Gets or sets the number of retry attempts.
/// </summary>
public int RetryCount { get; set; } = 3;
/// <summary>
/// Creates a TelegramBotClientOptions instance from the proxy configuration.
/// </summary>
/// <returns>The configured TelegramBotClientOptions instance.</returns>
protected override TelegramBotClientOptions Realize() => new TelegramBotClientOptions(Token, BaseUrl, UseTestEnvironment)
{
RetryCount = RetryCount,
RetryThreshold = RetryThreshold
};
}
}
+48 -81
View File
@@ -4,18 +4,13 @@ using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using System.Text;
using Telegram.Bot;
using Telegram.Bot.Polling;
using Telegram.Bot.Types;
using Telegram.Bot.Types.Enums;
using Telegrator;
using Telegrator.Core;
using Telegrator.Core.Descriptors;
using Telegrator.Handlers;
using Telegrator.Hosting;
using Telegrator.Logging;
using Telegrator.Polling;
@@ -38,13 +33,22 @@ public static class HostBuilderExtensions
/// <summary>
/// Gets the <see cref="IHandlersCollection"/> from the builder properties.
/// </summary>
public IHandlersCollection Handlers => (IHandlersCollection)builder.Properties[HandlersCollectionPropertyKey];
public IHandlersCollection Handlers
{
get
{
if (builder is TelegramBotHostBuilder botHostBuilder)
return botHostBuilder.Handlers;
return (IHandlersCollection)builder.Properties[HandlersCollectionPropertyKey];
}
}
}
/// <summary>
/// Replaces TelegramBotWebHostBuilder. Configures DI, options, and handlers.
/// </summary>
public static IHostApplicationBuilder AddTelegrator(this IHostApplicationBuilder builder, TelegramBotHostBuilderSettings settings, IHandlersCollection? handlers = null)
public static IHostApplicationBuilder AddTelegrator(this IHostApplicationBuilder builder, HostApplicationBuilderSettings settings, TelegratorOptions? options = null, IHandlersCollection? handlers = null)
{
if (settings is null)
throw new ArgumentNullException(nameof(settings));
@@ -52,53 +56,52 @@ public static class HostBuilderExtensions
IServiceCollection services = builder.Services;
IConfigurationManager configuration = builder.Configuration;
handlers ??= new HostHandlersCollection(services, settings);
builder.Properties.Add(HandlersCollectionPropertyKey, handlers);
if (handlers is IHostHandlersCollection hostHandlers)
if (options == null)
{
foreach (PreBuildingRoutine preBuildRoutine in hostHandlers.PreBuilderRoutines)
options = configuration.GetSection(nameof(TelegratorOptions)).Get<TelegratorOptions>();
if (options == null)
throw new MissingMemberException("Auto configuration disabled, yet no options of type 'TelegratorOptions' wasn't registered. This configuration is runtime required!");
}
services.AddSingleton(Options.Create(options));
if (handlers != null)
{
if (handlers is IHandlersManager manager)
{
try
{
// TODO: fix
//preBuildRoutine.Invoke(builder);
Debug.WriteLine("Pre-Building routine was not executed");
}
catch (NotImplementedException)
{
_ = 0xBAD + 0xC0DE;
}
ServiceDescriptor descriptor = new ServiceDescriptor(typeof(IHandlersProvider), manager);
services.Replace(descriptor);
services.AddSingleton(manager);
}
}
if (!settings.DisableAutoConfigure)
{
services.Configure<ReceiverOptions>(configuration.GetSection(nameof(ReceiverOptions)));
services.Configure(configuration.GetSection(nameof(TelegramBotClientOptions)), new TelegramBotClientOptionsProxy());
}
else
{
if (null == services.SingleOrDefault(srvc => srvc.ImplementationType == typeof(IOptions<ReceiverOptions>)))
throw new MissingMemberException("Auto configuration disabled, yet no options of type 'ReceiverOptions' wasn't registered. This configuration is runtime required!");
if (null == services.SingleOrDefault(srvc => srvc.ImplementationType == typeof(IOptions<TelegramBotClientOptions>)))
throw new MissingMemberException("Auto configuration disabled, yet no options of type 'TelegramBotClientOptions' wasn't registered. This configuration is runtime required!");
}
IOptions<TelegramBotHostBuilderSettings> options = Options.Create(settings);
services.AddSingleton((IOptions<TelegratorOptions>)options);
services.AddTelegramBotHostDefaults();
services.AddSingleton(options);
handlers ??= new HostHandlersCollection(services, options);
services.AddSingleton(handlers);
if (handlers is IHandlersManager manager)
builder.Properties.Add(HandlersCollectionPropertyKey, handlers);
if (builder is TelegramBotHostBuilder botHostBuilder)
botHostBuilder._handlers = handlers;
if (!services.Any(srvc => srvc.ImplementationType == typeof(IOptions<ReceiverOptions>)))
{
ServiceDescriptor descriptor = new ServiceDescriptor(typeof(IHandlersProvider), manager);
services.Replace(descriptor);
services.AddSingleton(manager);
ReceiverOptions? receiverOptions = configuration.GetSection(nameof(ReceiverOptions)).Get<ReceiverOptions>();
if (receiverOptions == null)
throw new MissingMemberException("Auto configuration disabled, yet no options of type 'ReceiverOptions' wasn't registered. This configuration is runtime required!");
services.AddSingleton(Options.Create(receiverOptions));
}
if (!services.Any(srvc => srvc.ImplementationType == typeof(IOptions<TelegramBotClientOptions>)))
{
services.AddSingleton(Options.Create(new TelegramBotClientOptions(options.Token, options.BaseUrl, options.UseTestEnvironment)
{
RetryCount = options.RetryCount,
RetryThreshold = options.RetryThreshold
}));
}
services.AddTelegramReceiver();
services.AddTelegramBotHostDefaults();
return builder;
}
}
@@ -109,20 +112,6 @@ public static class HostBuilderExtensions
/// </summary>
public static class ServicesCollectionExtensions
{
/// <summary>
/// Registers a configuration instance that strongly-typed <typeparamref name="TOptions"/> will bind against using <see cref="ConfigureOptionsProxy{TOptions}"/>.
/// </summary>
/// <typeparam name="TOptions"></typeparam>
/// <param name="services"></param>
/// <param name="configuration"></param>
/// <param name="optionsProxy"></param>
/// <returns></returns>
public static IServiceCollection Configure<TOptions>(this IServiceCollection services, IConfiguration configuration, ConfigureOptionsProxy<TOptions> optionsProxy) where TOptions : class
{
optionsProxy.Configure(services, configuration);
return services;
}
/// <summary>
/// Registers <see cref="TelegramBotHost"/> default services
/// </summary>
@@ -163,7 +152,7 @@ public static class ServicesCollectionExtensions
}
/// <summary>
/// Provides useful methods to adjust <see cref="ITelegramBotHost"/>
/// Provides useful methods to adjust Telegram bot Host
/// </summary>
public static class TelegramBotHostExtensions
{
@@ -218,28 +207,6 @@ public static class TelegramBotHostExtensions
}
}
/// <summary>
/// Provides extension methods for reflection and type inspection.
/// </summary>
public static class ReflectionExtensions
{
/// <summary>
/// Checks if a type implements the <see cref="IPreBuildingRoutine"/> interface.
/// </summary>
/// <param name="handlerType">The type to check.</param>
/// <param name="routineMethod"></param>
/// <returns>True if the type implements IPreBuildingRoutine; otherwise, false.</returns>
public static bool IsPreBuildingRoutine(this Type handlerType, [NotNullWhen(true)] out MethodInfo? routineMethod)
{
routineMethod = null;
if (handlerType.GetInterface(nameof(IPreBuildingRoutine)) == null)
return false;
routineMethod = handlerType.GetMethod(nameof(IPreBuildingRoutine.PreBuildingRoutine), BindingFlags.Static | BindingFlags.Public);
return routineMethod != null;
}
}
/// <summary>
/// Provides extension methods for logging Telegrator-related information.
/// </summary>
+38 -5
View File
@@ -6,16 +6,49 @@
/// </summary>
public class TelegratorOptions
{
/// <inheritdoc/>
/// <summary>
/// Gets or sets the bot token.
/// </summary>
public string Token { get; set; } = string.Empty;
/// <summary>
/// Gets or sets the base URL for the bot API.
/// </summary>
public string? BaseUrl { get; set; } = null;
/// <summary>
/// Gets or sets whether to use the test environment.
/// </summary>
public bool UseTestEnvironment { get; set; } = false;
/// <summary>
/// Gets or sets the retry threshold in seconds.
/// </summary>
public int RetryThreshold { get; set; } = 60;
/// <summary>
/// Gets or sets the number of retry attempts.
/// </summary>
public int RetryCount { get; set; } = 3;
/// <summary>
/// Gets or sets the maximum number of parallel working handlers. Null means no limit.
/// </summary>
public int? MaximumParallelWorkingHandlers { get; set; } = null;
/// <inheritdoc/>
/// <summary>
/// Gets or sets a value indicating whether awaiting handlers should be routed separately from regular handlers.
/// </summary>
public bool ExclusiveAwaitingHandlerRouting { get; set; } = false;
/// <inheritdoc/>
/// <summary>
/// Gets or sets a value indicating whether to exclude intersecting command aliases.
/// </summary>
public bool ExceptIntersectingCommandAliases { get; set; } = true;
/// <inheritdoc/>
public CancellationToken GlobalCancellationToken { get; set; }
/// <summary>
/// Gets or sets the global cancellation token for all bot operations.
/// </summary>
public CancellationToken GlobalCancellationToken { get; set; } = default;
}
}