* Moved source code projects directory to 'src'

* Updatedt Solution format
This commit is contained in:
2026-03-06 21:12:21 +04:00
parent a18eab0f31
commit f1927cdda0
194 changed files with 10 additions and 83 deletions
@@ -0,0 +1,15 @@
namespace Telegrator.Hosting.Components
{
/// <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);
}
}
@@ -0,0 +1,14 @@
using Microsoft.Extensions.Hosting;
using Telegrator;
namespace Telegrator.Hosting.Components
{
/// <summary>
/// Interface for Telegram bot hosts.
/// Combines host application capabilities with reactive Telegram bot functionality.
/// </summary>
public interface ITelegramBotHost : IHost, ITelegratorBot
{
}
}
@@ -0,0 +1,29 @@
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Telegrator.MadiatorCore;
namespace Telegrator.Hosting.Components
{
/// <summary>
/// Interface for building Telegram bot hosts with dependency injection support.
/// Combines host application building capabilities with handler collection functionality.
/// </summary>
public interface ITelegramBotHostBuilder : ICollectingProvider
{
/// <summary>
/// Gets the set of key/value configuration properties.
/// </summary>
IConfigurationManager Configuration { get; }
/// <summary>
/// Gets a collection of logging providers for the application to compose. This is useful for adding new logging providers.
/// </summary>
ILoggingBuilder Logging { get; }
/// <summary>
/// Gets a collection of services for the application to compose. This is useful for adding user provided or framework provided services.
/// </summary>
IServiceCollection Services { get; }
}
}
@@ -0,0 +1,53 @@
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
namespace Telegrator.Hosting.Configuration
{
/// <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();
}
}
@@ -0,0 +1,46 @@
using Telegram.Bot;
namespace Telegrator.Hosting.Configuration
{
/// <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
};
}
}
@@ -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")]
@@ -0,0 +1,30 @@
using Microsoft.Extensions.Configuration;
using Telegram.Bot;
using Telegram.Bot.Types;
using Telegrator.Configuration;
namespace Telegrator.Hosting
{
/// <summary>
/// Implementation of <see cref="ITelegramBotInfo"/> that provides bot information.
/// Contains metadata about the Telegram bot including user details and service provider for wider filterring abilities
/// </summary>
/// <param name="client"></param>
/// <param name="services"></param>
/// <param name="configuration"></param>
public class HostedTelegramBotInfo(ITelegramBotClient client, IServiceProvider services, IConfigurationManager configuration) : ITelegramBotInfo
{
/// <inheritdoc/>
public User User { get; } = client.GetMe().Result;
/// <summary>
/// Provides access to services of this Hosted telegram bot
/// </summary>
public IServiceProvider Services { get; } = services;
/// <summary>
/// Provides access to configuration of this Hosted telegram bot
/// </summary>
public IConfigurationManager Configuration { get; } = configuration;
}
}
@@ -0,0 +1,46 @@
using Microsoft.Extensions.Logging;
using Telegrator.Logging;
namespace Telegrator.Hosting.Logging
{
/// <summary>
/// Adapter for Microsoft.Extensions.Logging to work with Telegrator logging system.
/// This allows seamless integration with ASP.NET Core logging infrastructure.
/// </summary>
public class MicrosoftLoggingAdapter : ITelegratorLogger
{
private readonly ILogger _logger;
/// <summary>
/// Initializes a new instance of MicrosoftLoggingAdapter.
/// </summary>
/// <param name="logger">The Microsoft.Extensions.Logging logger instance.</param>
public MicrosoftLoggingAdapter(ILogger logger)
{
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
/// <inheritdoc/>
public void Log(Telegrator.Logging.LogLevel level, string message, Exception? exception = null)
{
var msLogLevel = level switch
{
Telegrator.Logging.LogLevel.Trace => Microsoft.Extensions.Logging.LogLevel.Trace,
Telegrator.Logging.LogLevel.Debug => Microsoft.Extensions.Logging.LogLevel.Debug,
Telegrator.Logging.LogLevel.Information => Microsoft.Extensions.Logging.LogLevel.Information,
Telegrator.Logging.LogLevel.Warning => Microsoft.Extensions.Logging.LogLevel.Warning,
Telegrator.Logging.LogLevel.Error => Microsoft.Extensions.Logging.LogLevel.Error,
_ => Microsoft.Extensions.Logging.LogLevel.Information
};
if (exception != null)
{
_logger.Log(msLogLevel, default, message, exception, (str, exc) => string.Format("{0} : {1}", str, exc));
}
else
{
_logger.Log(msLogLevel, default, message, null, (str, _) => str);
}
}
}
}
@@ -0,0 +1,13 @@
using Microsoft.Extensions.Options;
using Telegrator.MadiatorCore;
using Telegrator.Polling;
namespace Telegrator.Hosting.Polling
{
/// <inheritdoc/>
public class HostUpdateHandlersPool(IUpdateRouter router, IOptions<TelegratorOptions> options)
: UpdateHandlersPool(router, options.Value, options.Value.GlobalCancellationToken)
{
}
}
@@ -0,0 +1,60 @@
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Telegram.Bot;
using Telegram.Bot.Polling;
using Telegram.Bot.Types;
using Telegrator.Configuration;
using Telegrator.MadiatorCore;
using Telegrator.Polling;
namespace Telegrator.Hosting.Polling
{
/// <inheritdoc/>
public class HostUpdateRouter : UpdateRouter
{
/// <summary>
/// <see cref="ILogger"/> of this router
/// </summary>
protected readonly ILogger<HostUpdateRouter> Logger;
/// <inheritdoc/>
public HostUpdateRouter(
IHandlersProvider handlersProvider,
IAwaitingProvider awaitingProvider,
IOptions<TelegratorOptions> options,
IUpdateHandlersPool handlersPool,
ITelegramBotInfo botInfo,
ILogger<HostUpdateRouter> logger) : base(handlersProvider, awaitingProvider, options.Value, handlersPool, botInfo)
{
Logger = logger;
ExceptionHandler = new DefaultRouterExceptionHandler(HandleException);
}
/// <inheritdoc/>
public override Task HandleUpdateAsync(ITelegramBotClient botClient, Update update, CancellationToken cancellationToken)
{
//Logger.LogInformation("Received update of type \"{type}\"", update.Type);
return base.HandleUpdateAsync(botClient, update, cancellationToken);
}
/// <summary>
/// Default exception handler of this router
/// </summary>
/// <param name="botClient"></param>
/// <param name="exception"></param>
/// <param name="source"></param>
/// <param name="cancellationToken"></param>
public void HandleException(ITelegramBotClient botClient, Exception exception, HandleErrorSource source, CancellationToken cancellationToken)
{
if (exception is HandlerFaultedException handlerFaultedException)
{
Logger.LogError("\"{handler}\" handler's execution was faulted :\n{exception}",
handlerFaultedException.HandlerInfo.ToString(),
handlerFaultedException.InnerException?.ToString() ?? "No inner exception");
return;
}
Logger.LogError("Exception was thrown during update routing faulted :\n{exception}", exception.ToString());
}
}
}
@@ -0,0 +1,34 @@
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.MadiatorCore;
using Telegrator.Polling;
namespace Telegrator.Hosting.Polling
{
/// <summary>
/// Service for receiving updates for Hosted telegram bots
/// </summary>
/// <param name="botHost"></param>
/// <param name="botClient"></param>
/// <param name="updateRouter"></param>
/// <param name="options"></param>
/// <param name="logger"></param>
public class HostedUpdateReceiver(ITelegramBotHost botHost, ITelegramBotClient botClient, IUpdateRouter updateRouter, IOptions<ReceiverOptions> options, ILogger<HostedUpdateReceiver> logger) : BackgroundService
{
private readonly ReceiverOptions _receiverOptions = options.Value;
private readonly IUpdateRouter _updateRouter = updateRouter;
/// <inheritdoc/>
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
logger.LogInformation("Starting receiving updates via long-polling");
_receiverOptions.AllowedUpdates = botHost.UpdateRouter.HandlersProvider.AllowedTypes.ToArray();
DefaultUpdateReceiver updateReceiver = new DefaultUpdateReceiver(botClient, _receiverOptions);
await updateReceiver.ReceiveAsync(_updateRouter, stoppingToken).ConfigureAwait(false);
}
}
}
@@ -0,0 +1,16 @@
using Telegrator.MadiatorCore;
namespace Telegrator.Hosting.Providers.Components
{
/// <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; }
}
}
@@ -0,0 +1,12 @@
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Telegrator.Providers;
namespace Telegrator.Hosting.Providers
{
/// <inheritdoc/>
public class HostAwaitingProvider(IOptions<TelegratorOptions> options, ILogger<HostAwaitingProvider> logger) : AwaitingProvider(options.Value)
{
private readonly ILogger<HostAwaitingProvider> _logger = logger;
}
}
@@ -0,0 +1,81 @@
using Microsoft.Extensions.DependencyInjection;
using System.Reflection;
using Telegrator.Configuration;
using Telegrator.Hosting.Components;
using Telegrator.Hosting.Providers.Components;
using Telegrator.MadiatorCore;
using Telegrator.MadiatorCore.Descriptors;
using Telegrator.Providers;
namespace Telegrator.Hosting.Providers
{
/// <summary>
/// Pre host building task
/// </summary>
/// <param name="builder"></param>
public delegate void PreBuildingRoutine(ITelegramBotHostBuilder builder);
/// <inheritdoc/>
public class HostHandlersCollection(IServiceCollection hostServiceColletion, ITelegratorOptions options) : HandlersCollection(options), IHostHandlersCollection
{
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:
{
if (descriptor.InstanceFactory != null)
Services.AddScoped(descriptor.HandlerType, _ => descriptor.InstanceFactory.Invoke());
else
Services.AddScoped(descriptor.HandlerType);
break;
}
case DescriptorType.Keyed:
{
if (descriptor.InstanceFactory != null)
Services.AddKeyedScoped(descriptor.HandlerType, descriptor.ServiceKey, (_, _) => descriptor.InstanceFactory.Invoke());
else
Services.AddKeyedScoped(descriptor.HandlerType, descriptor.ServiceKey);
break;
}
case DescriptorType.Singleton:
{
Services.AddSingleton(descriptor.HandlerType, descriptor.SingletonInstance ?? (descriptor.InstanceFactory != null
? descriptor.InstanceFactory.Invoke()
: throw new Exception()));
break;
}
case DescriptorType.Implicit:
{
Services.AddKeyedSingleton(descriptor.HandlerType, descriptor.ServiceKey, descriptor.SingletonInstance ?? (descriptor.InstanceFactory != null
? descriptor.InstanceFactory.Invoke()
: throw new Exception()));
break;
}
}
return base.AddDescriptor(descriptor);
}
}
}
@@ -0,0 +1,46 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Telegrator.Handlers.Components;
using Telegrator.MadiatorCore;
using Telegrator.MadiatorCore.Descriptors;
using Telegrator.Providers;
namespace Telegrator.Hosting.Providers
{
/// <inheritdoc/>
public class HostHandlersProvider : HandlersProvider
{
private readonly IServiceProvider Services;
private readonly ILogger<HostHandlersProvider> Logger;
/// <inheritdoc/>
public HostHandlersProvider(
IHandlersCollection handlers,
IOptions<TelegratorOptions> options,
IServiceProvider serviceProvider,
ILogger<HostHandlersProvider> logger) : base(handlers, options.Value)
{
Services = serviceProvider;
Logger = logger;
}
/// <inheritdoc/>
public override UpdateHandlerBase GetHandlerInstance(HandlerDescriptor descriptor, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
IServiceScope scope = Services.CreateScope();
object handlerInstance = descriptor.ServiceKey == null
? scope.ServiceProvider.GetRequiredService(descriptor.HandlerType)
: scope.ServiceProvider.GetRequiredKeyedService(descriptor.HandlerType, descriptor.ServiceKey);
if (handlerInstance is not UpdateHandlerBase updateHandler)
throw new InvalidOperationException("Failed to resolve " + descriptor.HandlerType + " as UpdateHandlerBase");
descriptor.LazyInitialization?.Invoke(updateHandler);
updateHandler.LifetimeToken.OnLifetimeEnded += _ => scope.Dispose();
return updateHandler;
}
}
}
+92
View File
@@ -0,0 +1,92 @@
# 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,
ExceptIntersectingCommandAliases = true
});
// Registerring handlers
builder.Handlers.CollectHandlersAssemblyWide();
// Register your services
builder.Services.AddSingleton<IMyService, MyService>();
// 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
+136
View File
@@ -0,0 +1,136 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Telegrator.Hosting.Components;
using Telegrator.MadiatorCore;
namespace Telegrator.Hosting
{
/// <summary>
/// Represents a hosted telegram bot
/// </summary>
public class TelegramBotHost : ITelegramBotHost
{
private readonly IHost _innerHost;
private readonly IServiceProvider _serviceProvider;
private readonly IUpdateRouter _updateRouter;
private readonly ILogger<TelegramBotHost> _logger;
private bool _disposed;
/// <inheritdoc/>
public IServiceProvider Services => _serviceProvider;
/// <inheritdoc/>
public IUpdateRouter UpdateRouter => _updateRouter;
/// <summary>
/// This application's logger
/// </summary>
public ILogger<TelegramBotHost> Logger => _logger;
/// <summary>
/// Initializes a new instance of the <see cref="TelegramBotHost"/> class.
/// </summary>
/// <param name="hostApplicationBuilder">The proxied instance of host builder.</param>
/// <param name="handlers"></param>
public TelegramBotHost(HostApplicationBuilder hostApplicationBuilder, IHandlersCollection handlers)
{
// Registering this host in services for easy access
RegisterHostServices(hostApplicationBuilder.Services, handlers);
// Building proxy hoster
_innerHost = hostApplicationBuilder.Build();
_serviceProvider = _innerHost.Services;
_innerHost.UseTelegrator();
// Reruesting services for this host
_updateRouter = Services.GetRequiredService<IUpdateRouter>();
_logger = Services.GetRequiredService<ILogger<TelegramBotHost>>();
// Logging registering handlers in DEBUG purposes
_logger.LogHandlers(handlers);
}
/// <summary>
/// Creates new <see cref="TelegramBotHostBuilder"/> with default configuration, services and long-polling update receiving scheme
/// </summary>
/// <returns></returns>
public static TelegramBotHostBuilder CreateBuilder()
{
HostApplicationBuilder innerBuilder = new HostApplicationBuilder(settings: null);
TelegramBotHostBuilder builder = new TelegramBotHostBuilder(innerBuilder, null);
builder.Services.AddTelegramBotHostDefaults();
builder.Services.AddTelegramReceiver();
return builder;
}
/// <summary>
/// Creates new <see cref="TelegramBotHostBuilder"/> with default services and long-polling update receiving scheme
/// </summary>
/// <returns></returns>
public static TelegramBotHostBuilder CreateBuilder(TelegramBotHostBuilderSettings? settings)
{
HostApplicationBuilder innerBuilder = new HostApplicationBuilder(settings?.ToApplicationBuilderSettings());
TelegramBotHostBuilder builder = new TelegramBotHostBuilder(innerBuilder, settings);
builder.Services.AddTelegramBotHostDefaults();
builder.Services.AddTelegramReceiver();
return builder;
}
/// <summary>
/// Creates new EMPTY <see cref="TelegramBotHostBuilder"/> WITHOUT any services or update receiving schemes
/// </summary>
/// <returns></returns>
public static TelegramBotHostBuilder CreateEmptyBuilder()
{
HostApplicationBuilder innerBuilder = Host.CreateEmptyApplicationBuilder(null);
return new TelegramBotHostBuilder(innerBuilder, null);
}
/// <summary>
/// Creates new EMPTY <see cref="TelegramBotHostBuilder"/> WITHOUT any services or update receiving schemes
/// </summary>
/// <returns></returns>
public static TelegramBotHostBuilder CreateEmptyBuilder(TelegramBotHostBuilderSettings? settings)
{
HostApplicationBuilder innerBuilder = Host.CreateEmptyApplicationBuilder(null);
return new TelegramBotHostBuilder(innerBuilder, settings);
}
/// <inheritdoc/>
public async Task StartAsync(CancellationToken cancellationToken = default)
{
await _innerHost.StartAsync(cancellationToken);
}
/// <inheritdoc/>
public async Task StopAsync(CancellationToken cancellationToken = default)
{
await _innerHost.StopAsync(cancellationToken);
}
/// <summary>
/// Disposes the host.
/// </summary>
public void Dispose()
{
if (_disposed)
return;
_innerHost.Dispose();
GC.SuppressFinalize(this);
_disposed = true;
}
private void RegisterHostServices(IServiceCollection services, IHandlersCollection handlers)
{
//services.RemoveAll<IHost>();
//services.AddSingleton<IHost>(this);
services.AddSingleton<ITelegramBotHost>(this);
services.AddSingleton<ITelegratorBot>(this);
}
}
}
@@ -0,0 +1,76 @@
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Telegrator.Hosting.Components;
using Telegrator.Hosting.Providers;
using Telegrator.MadiatorCore;
#pragma warning disable IDE0001
namespace Telegrator.Hosting
{
/// <summary>
/// Represents a hosted telegram bots and services builder that helps manage configuration, logging, lifetime, and more.
/// </summary>
public class TelegramBotHostBuilder : ITelegramBotHostBuilder
{
private readonly HostApplicationBuilder _innerBuilder;
private readonly TelegramBotHostBuilderSettings _settings;
private readonly IHandlersCollection _handlers;
/// <inheritdoc/>
public IHandlersCollection Handlers => _handlers;
/// <inheritdoc/>
public IServiceCollection Services => _innerBuilder.Services;
/// <inheritdoc/>
public IConfigurationManager Configuration => _innerBuilder.Configuration;
/// <inheritdoc/>
public ILoggingBuilder Logging => _innerBuilder.Logging;
/// <inheritdoc/>
public IHostEnvironment Environment => _innerBuilder.Environment;
/// <summary>
/// Initializes a new instance of the <see cref="TelegramBotHostBuilder"/> class.
/// </summary>
/// <param name="hostApplicationBuilder"></param>
/// <param name="settings"></param>
public TelegramBotHostBuilder(HostApplicationBuilder hostApplicationBuilder, TelegramBotHostBuilderSettings? settings = null)
{
_innerBuilder = hostApplicationBuilder ?? throw new ArgumentNullException(nameof(hostApplicationBuilder));
_settings = settings ?? new TelegramBotHostBuilderSettings();
_handlers = new HostHandlersCollection(Services, _settings);
_innerBuilder.AddTelegrator(_settings, _handlers);
_innerBuilder.Logging.ClearProviders();
}
/// <summary>
/// Initializes a new instance of the <see cref="TelegramBotHostBuilder"/> class.
/// </summary>
/// <param name="hostApplicationBuilder"></param>
/// <param name="handlers"></param>
/// <param name="settings"></param>
public TelegramBotHostBuilder(HostApplicationBuilder hostApplicationBuilder, IHandlersCollection handlers, TelegramBotHostBuilderSettings? settings = null)
{
_innerBuilder = hostApplicationBuilder ?? throw new ArgumentNullException(nameof(hostApplicationBuilder));
_settings = settings ?? new TelegramBotHostBuilderSettings();
_handlers = handlers ?? throw new ArgumentNullException(nameof(handlers));
_innerBuilder.AddTelegrator(_settings, _handlers);
_innerBuilder.Logging.ClearProviders();
}
/// <summary>
/// Builds the host.
/// </summary>
/// <returns></returns>
public TelegramBotHost Build()
{
return new TelegramBotHost(_innerBuilder, _handlers);
}
}
}
@@ -0,0 +1,45 @@
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
};
}
}
@@ -0,0 +1,43 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<LangVersion>latest</LangVersion>
<BaseOutputPath>..\..\bin</BaseOutputPath>
<DocumentationFile>..\..\docs\$(AssemblyName).xml</DocumentationFile>
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
<GenerateDocumentationFile>True</GenerateDocumentationFile>
<EnableNETAnalyzers>True</EnableNETAnalyzers>
<EnforceCodeStyleInBuild>True</EnforceCodeStyleInBuild>
<Title>Telegrator.Hosting</Title>
<Version>1.16.0</Version>
<Authors>Rikitav Tim4ik</Authors>
<Company>Rikitav Tim4ik</Company>
<RepositoryUrl>https://github.com/Rikitav/Telegrator</RepositoryUrl>
<PackageTags>telegram;bot;mediator;attributes;aspect;hosting;host;framework;easy;simple;handlers</PackageTags>
<PackageIcon>telegrator_nuget.png</PackageIcon>
<PackageReadmeFile>README.md</PackageReadmeFile>
<PackageLicenseFile>LICENSE</PackageLicenseFile>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Hosting" Version="10.0.3" />
<PackageReference Include="Microsoft.Extensions.Http" Version="10.0.3" />
</ItemGroup>
<ItemGroup>
<None Include="..\LICENSE" Pack="True" PackagePath="\" />
<None Include="..\README.md" Pack="True" PackagePath="\" />
<None Include="..\resources\telegrator_nuget.png" Pack="True" PackagePath="\" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Telegrator\Telegrator.csproj" />
</ItemGroup>
</Project>
+250
View File
@@ -0,0 +1,250 @@
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
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.Configuration;
using Telegrator.Hosting.Components;
using Telegrator.Hosting.Configuration;
using Telegrator.Hosting.Logging;
using Telegrator.Hosting.Polling;
using Telegrator.Hosting.Providers;
using Telegrator.Hosting.Providers.Components;
using Telegrator.Logging;
using Telegrator.MadiatorCore;
using Telegrator.MadiatorCore.Descriptors;
namespace Telegrator.Hosting
{
public static class HostBuilderExtensions
{
/// <summary>
/// Replaces TelegramBotWebHostBuilder. Configures DI, options, and handlers.
/// </summary>
public static IHostApplicationBuilder AddTelegrator(this IHostApplicationBuilder builder, TelegramBotHostBuilderSettings settings, IHandlersCollection? handlers = null)
{
if (settings is null)
throw new ArgumentNullException(nameof(settings));
IServiceCollection services = builder.Services;
IConfigurationManager configuration = builder.Configuration;
handlers ??= new HostHandlersCollection(services, settings);
if (handlers is IHostHandlersCollection hostHandlers)
{
foreach (PreBuildingRoutine preBuildRoutine in hostHandlers.PreBuilderRoutines)
{
try
{
// TODO: fix
//preBuildRoutine.Invoke(builder);
Debug.WriteLine("Pre-Building routine was not executed");
}
catch (NotImplementedException)
{
_ = 0xBAD + 0xC0DE;
}
}
}
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);
services.AddSingleton(handlers);
if (handlers is IHandlersManager manager)
{
ServiceDescriptor descriptor = new ServiceDescriptor(typeof(IHandlersProvider), manager);
services.Replace(descriptor);
services.AddSingleton(manager);
}
return builder;
}
}
/// <summary>
/// Contains extensions for <see cref="IServiceCollection"/>
/// Provides method to configure <see cref="ITelegramBotHost"/>
/// </summary>
public static class ServicesCollectionExtensions
{
/// <summary>
/// Registers a configuration instance that strongly-typed <typeparamref name="TOptions"/> will bind against using <see cref="ConfigureOptionsProxy{TOptions}"/>.
/// </summary>
/// <typeparam name="TOptions"></typeparam>
/// <param name="services"></param>
/// <param name="configuration"></param>
/// <param name="optionsProxy"></param>
/// <returns></returns>
public static IServiceCollection Configure<TOptions>(this IServiceCollection services, IConfiguration configuration, ConfigureOptionsProxy<TOptions> optionsProxy) where TOptions : class
{
optionsProxy.Configure(services, configuration);
return services;
}
/// <summary>
/// Registers <see cref="TelegramBotHost"/> default services
/// </summary>
/// <param name="services"></param>
/// <returns></returns>
public static IServiceCollection AddTelegramBotHostDefaults(this IServiceCollection services)
{
services.AddLogging(builder => builder.AddConsole().AddDebug());
services.AddSingleton<IUpdateHandlersPool, HostUpdateHandlersPool>();
services.AddSingleton<IAwaitingProvider, HostAwaitingProvider>();
services.AddSingleton<IHandlersProvider, HostHandlersProvider>();
services.AddSingleton<IUpdateRouter, HostUpdateRouter>();
services.AddSingleton<ITelegramBotInfo, HostedTelegramBotInfo>();
return services;
}
/// <summary>
/// Registers <see cref="ITelegramBotClient"/> service with <see cref="HostedUpdateReceiver"/> to receive updates using long polling
/// </summary>
/// <param name="services"></param>
/// <returns></returns>
public static IServiceCollection AddTelegramReceiver(this IServiceCollection services)
{
services.AddHttpClient<ITelegramBotClient>("tgreceiver").RemoveAllLoggers().AddTypedClient(TypedTelegramBotClientFactory);
services.AddHostedService<HostedUpdateReceiver>();
return services;
}
/// <summary>
/// <see cref="ITelegramBotClient"/> factory method
/// </summary>
/// <param name="httpClient"></param>
/// <param name="provider"></param>
/// <returns></returns>
private static ITelegramBotClient TypedTelegramBotClientFactory(HttpClient httpClient, IServiceProvider provider)
=> new TelegramBotClient(provider.GetRequiredService<IOptions<TelegramBotClientOptions>>().Value, httpClient);
}
/// <summary>
/// Provides useful methods to adjust <see cref="ITelegramBotHost"/>
/// </summary>
public static class TelegramBotHostExtensions
{
/// <summary>
/// Replaces the initialization logic from TelegramBotWebHost constructor.
/// Initializes the bot and logs handlers on application startup.
/// </summary>
public static IHost UseTelegrator(this IHost botHost)
{
ITelegramBotInfo info = botHost.Services.GetRequiredService<ITelegramBotInfo>();
IHandlersCollection handlers = botHost.Services.GetRequiredService<IHandlersCollection>();
ILoggerFactory loggerFactory = botHost.Services.GetRequiredService<ILoggerFactory>();
ILogger logger = loggerFactory.CreateLogger("Telegrator.Hosting.Web.TelegratorHost");
logger.LogInformation("Telegrator Bot .NET Host started");
logger.LogHandlers(handlers);
return botHost;
}
/// <summary>
/// Configures bots available commands depending on what handlers was registered
/// </summary>
/// <param name="botHost"></param>
/// <returns></returns>
public static IHost SetBotCommands(this IHost botHost)
{
ITelegramBotClient client = botHost.Services.GetRequiredService<ITelegramBotClient>();
IUpdateRouter router = botHost.Services.GetRequiredService<IUpdateRouter>();
IEnumerable<BotCommand> aliases = router.HandlersProvider.GetBotCommands();
client.SetMyCommands(aliases).Wait();
return botHost;
}
/// <summary>
/// Adds a Microsoft.Extensions.Logging adapter to Alligator using a logger factory.
/// </summary>
/// <param name="host"></param>
public static IHost AddLoggingAdapter(this IHost host)
{
ILoggerFactory loggerFactory = host.Services.GetRequiredService<ILoggerFactory>();
ILogger logger = loggerFactory.CreateLogger("Telegrator");
MicrosoftLoggingAdapter adapter = new MicrosoftLoggingAdapter(logger);
Alligator.AddAdapter(adapter);
return host;
}
}
/// <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;
}
}
public static class LoggerExtensions
{
public static void LogHandlers(this ILogger logger, IHandlersCollection 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());
}
}
}