18 Commits

Author SHA1 Message Date
Rikitav 233a67f2b0 * Added RemapWebhook method to WebHost
* Updatetd dependencues
* Fixed some warnings
2026-04-03 19:23:53 +04:00
Rikitav ba772f3f7c * Fixed wrong UseTelegrator method using in web host
* Fixed HostedUpdateWebhooker service discovery
2026-03-15 19:23:56 +04:00
Rikitav ee78703cd1 Update README.md 2026-03-15 17:56:05 +04:00
Rikitav 3d2d21f6c0 * Added building delegate to UseTelegrator method
* Fixed Webhooker trying to resolve IEndpointRouteBuildr from DI
2026-03-15 17:20:53 +04:00
Rikitav 0dedd3c0f4 * warnings fixes 2026-03-09 13:40:58 +04:00
Rikitav 79d5df8291 * Syntaxual refactoring 2026-03-09 13:23:21 +04:00
Rikitav 899243c62b * Added default state storage registering
* Added extension method for replacing default state storage
2026-03-09 13:10:49 +04:00
Rikitav 5f9c640930 * Added missing summaries 2026-03-09 04:38:03 +04:00
Rikitav 0e445dd586 Added README 2026-03-09 04:19:36 +04:00
Rikitav da090627ff * Added Redis state storage implementation 2026-03-09 03:49:03 +04:00
Rikitav 88bd12aadd * Readme and docs updated 2026-03-09 03:31:45 +04:00
Rikitav 162d4a1d05 * StateKeeper system rework
* StateKeepers are deleted
* Added IStateMachine and IStateStorage
* Added IStateStorage as provider to containers and handlers
* Added default IStateStorage implementation
* Added default StateMachine
* minor bug fixes
2026-03-09 03:22:23 +04:00
Rikitav e28cc2b8da Removed test application 2026-03-08 23:17:52 +04:00
Rikitav c6cda703ef version incremented 2026-03-08 22:02:30 +04:00
Rikitav 401c8d02aa fixed double UseTelegrator invokation 2026-03-08 22:02:01 +04:00
Rikitav 2cf4910abd * fixed loop dependency
* fixed router not getting result
* fixed hosts configuration
2026-03-08 21:59:50 +04:00
Rikitav 81da5e0bc7 * fixed loop dependency
* fixed router not getting result
* fixed hosts configuration
2026-03-08 19:43:48 +04:00
Rikitav b42e03fe06 docs updated 2026-03-07 23:38:14 +04:00
183 changed files with 11089 additions and 11886 deletions
+2 -2
View File
@@ -361,5 +361,5 @@ MigrationBackup/
# Fody - auto-generated XML schema
FodyWeavers.xsd
/GETTING_STARTED.md
/ANNOTATION_OVERVIEW.md
.aider*
+7 -8
View File
@@ -1,14 +1,14 @@
# Telegrator
![Telegrator Banner](https://github.com/Rikitav/Telegrator/blob/master/resources%2FTelegrator_banner.png)
> **A modern reactive framework for Telegram bots in C# with aspect-oriented design, mediator-based dispatching, and flexible architecture.**
---
## 🚀 About Telegrator
Telegrator is a next-generation framework for building Telegram bots in C#, inspired by AOP (Aspect-Oriented Programming) and the mediator pattern. It enables decentralized, easily extensible, and maintainable bot logic without traditional state machines or monolithic handlers.
Telegrator is a modern C# framework for building Telegram bots, inspired by AOP (Aspect-Oriented Programming) and the mediator pattern. It enables decentralized, easily extensible, and maintainable bot logic without traditional state machines or monolithic handlers.
---
The documentation may not be completely transparent, informative or even actual to latest version, so if you have any questions or problems, please write them in the [Telegram.Bot](https://t.me/joinchat/B35YY0QbLfd034CFnvCtCA) group. I am a member of this group and will notice you! If you have any suggestions or want to participate in building documentation, make push requests and open issues on this repository!
---
@@ -95,13 +95,12 @@ bot.Handlers.AddHandler<StartCommandHandler>();
using Telegrator.Handlers;
using Telegrator.Annotations;
[CommandHandler, CommandAlias("first"), NumericState(SpecialState.NoState)]
[CommandHandler, CommandAlias("first"), State<SetupWizard>(null)]
public class StateKeepFirst : CommandHandler
{
public override async Task<Result> Execute(IHandlerContainer<Message> container, CancellationToken cancellation)
{
container.CreateNumericState();
container.ForwardNumericState();
StateStorage.GetStateMachine<SetupWizard>().BysenderId().Advance();
await Reply("first state moved (1)", cancellationToken: cancellation);
return Result.Ok();
}
+1
View File
@@ -1,5 +1,6 @@
<Solution>
<Project Path="dev/Telegrator.RoslynGenerators/Telegrator.RoslynGenerators.csproj" />
<Project Path="src/Telegartor.RedisStateStorage/Telegartor.RedisStateStorage.csproj" />
<Project Path="src/Telegrator.Analyzers/Telegrator.Analyzers.csproj" />
<Project Path="src/Telegrator.Hosting.Web/Telegrator.Hosting.Web.csproj" />
<Project Path="src/Telegrator.Hosting/Telegrator.Hosting.csproj" />
@@ -5,10 +5,6 @@ using System.Collections.Immutable;
using System.Text;
using Telegrator.RoslynGenerators.RoslynExtensions;
#if DEBUG
using System.Diagnostics;
#endif
namespace Telegrator.RoslynGenerators;
[Generator(LanguageNames.CSharp)]
@@ -72,6 +68,9 @@ public class ImplicitHandlerBuilderExtensionsGenerator : IIncrementalGenerator
if (className == "FilterAnnotation")
continue;
if (className == "StateAttribute")
continue;
MethodDeclarationSyntax? targeter = classDeclaration.Members.OfType<MethodDeclarationSyntax>().SingleOrDefault(IsTargeterMethod);
if (targeter != null)
{
@@ -101,7 +100,13 @@ public class ImplicitHandlerBuilderExtensionsGenerator : IIncrementalGenerator
continue;
usings.UnionAdd(classDeclaration.FindAncestor<CompilationUnitSyntax>().Usings, UsingEqualityComparer);
MethodDeclarationSyntax targeter = FindTargetterMethod(targetters, classDeclaration);
MethodDeclarationSyntax? targeter = FindTargetterMethod(targetters, classDeclaration);
if (targeter == null)
{
debugExport.AppendLine("Targetter not found");
continue;
}
if (classDeclaration.ParameterList != null && classDeclaration.BaseList != null)
{
@@ -140,16 +145,16 @@ public class ImplicitHandlerBuilderExtensionsGenerator : IIncrementalGenerator
{
ClassDeclarationSyntax extensionsClass = SyntaxFactory.ClassDeclaration("HandlerBuilderExtensions")
.WithModifiers(Modifiers(SyntaxKind.PublicKeyword, SyntaxKind.StaticKeyword, SyntaxKind.PartialKeyword))
.AddMembers([.. targetters.Values, .. extensions])
.DecorateType(1);
.AddMembers([.. targetters.Values, .. extensions]);
NamespaceDeclarationSyntax namespaceDeclaration = SyntaxFactory.NamespaceDeclaration(SyntaxFactory.ParseName("Telegrator"))
.WithMembers([extensionsClass])
.Decorate();
.WithLeadingTrivia(SyntaxFactory.ParseLeadingTrivia("#pragma warning disable CS1591"))
.WithMembers([extensionsClass]);
CompilationUnitSyntax compilationUnit = SyntaxFactory.CompilationUnit()
.WithUsings([.. usings])
.WithMembers([namespaceDeclaration]);
.WithMembers([namespaceDeclaration])
.NormalizeWhitespace();
context.AddSource("GeneratedHandlerBuilderExtensions.cs", compilationUnit.ToFullString());
}
@@ -175,7 +180,7 @@ public class ImplicitHandlerBuilderExtensionsGenerator : IIncrementalGenerator
if (targetterMethod.ExpressionBody != null)
method = method.WithExpressionBody(targetterMethod.ExpressionBody).WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken));
return method.DecorateMember(2);
return method;
}
private static MethodDeclarationSyntax GeneratedExtensionsMethod(ClassDeclarationSyntax classDeclaration, ParameterListSyntax methodParameters, ArgumentListSyntax invokerArguments, MethodDeclarationSyntax targetterMethod)
@@ -204,11 +209,10 @@ public class ImplicitHandlerBuilderExtensionsGenerator : IIncrementalGenerator
MethodDeclarationSyntax method = SyntaxFactory.MethodDeclaration(returnType, identifier)
.WithParameterList(parameters)
.WithBody(body.DecorateBlock(2))
.WithBody(body)
.WithTypeParameterList(typeParameters)
.WithModifiers(Modifiers(SyntaxKind.PublicKeyword, SyntaxKind.StaticKeyword))
.WithConstraintClauses([typeParameterConstraint])
.DecorateMember(2)
.WithLeadingTrivia(xmlDoc);
return method;
@@ -230,7 +234,7 @@ public class ImplicitHandlerBuilderExtensionsGenerator : IIncrementalGenerator
private static IEnumerable<ConstructorDeclarationSyntax> GetConstructors(ClassDeclarationSyntax classDeclaration)
=> classDeclaration.Members.OfType<ConstructorDeclarationSyntax>().Where(ctor => ctor.Modifiers.HasModifiers("public"));
private static MethodDeclarationSyntax FindTargetterMethod(Dictionary<string, MethodDeclarationSyntax> targeters, ClassDeclarationSyntax classDeclaration)
private static MethodDeclarationSyntax? FindTargetterMethod(Dictionary<string, MethodDeclarationSyntax> targeters, ClassDeclarationSyntax classDeclaration)
{
if (targeters.TryGetValue(classDeclaration.Identifier.ValueText, out MethodDeclarationSyntax targeter))
return targeter;
@@ -238,7 +242,7 @@ public class ImplicitHandlerBuilderExtensionsGenerator : IIncrementalGenerator
if (classDeclaration.BaseList != null && targeters.TryGetValue(classDeclaration.BaseList.Types.ElementAt(0).Type.ToString(), out targeter))
return targeter;
throw new TargteterNotFoundException();
return null;
}
private static SyntaxTriviaList BuildExtensionXmlDocTrivia(ClassDeclarationSyntax classDeclaration, ParameterListSyntax methodParameters)
@@ -1,38 +0,0 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
namespace Telegrator.RoslynGenerators.RoslynExtensions
{
public static class MemberDeclarationSyntaxExtensions
{
private static SyntaxTrivia TabulationTrivia => SyntaxFactory.SyntaxTrivia(SyntaxKind.WhitespaceTrivia, "\t");
private static SyntaxTrivia WhitespaceTrivia => SyntaxFactory.SyntaxTrivia(SyntaxKind.WhitespaceTrivia, " ");
private static SyntaxTrivia NewLineTrivia => SyntaxFactory.SyntaxTrivia(SyntaxKind.EndOfLineTrivia, "\n");
public static SyntaxTokenList Decorate(this SyntaxTokenList tokens)
=> new SyntaxTokenList(tokens.Select(token => token.WithoutTrivia().WithTrailingTrivia(WhitespaceTrivia)).ToArray());
public static BlockSyntax DecorateBlock(this BlockSyntax block, int times) => block
.WithStatements([.. block.Statements.Select(statement => statement.DecorateStatememnt(times + 1))])
.WithOpenBraceToken(SyntaxFactory.Token(SyntaxKind.OpenBraceToken).WithLeadingTrivia(WhitespaceTrivia).WithTrailingTrivia(NewLineTrivia))
.WithCloseBraceToken(SyntaxFactory.Token(SyntaxKind.CloseBraceToken).WithLeadingTrivia(TabulationTrivia.Repeat(times)).WithTrailingTrivia(NewLineTrivia));
public static T DecorateStatememnt<T>(this T statememnt, int times) where T : StatementSyntax => statememnt
.WithoutTrivia().WithLeadingTrivia(TabulationTrivia.Repeat(times)).WithTrailingTrivia(NewLineTrivia);
public static T DecorateMember<T>(this T typeDeclaration, int times) where T : MemberDeclarationSyntax => typeDeclaration
.WithoutTrivia().WithLeadingTrivia(TabulationTrivia.Repeat(times)).WithTrailingTrivia(NewLineTrivia);
public static NamespaceDeclarationSyntax Decorate(this NamespaceDeclarationSyntax namespaceDeclaration) => namespaceDeclaration
.WithName(namespaceDeclaration.Name.WithoutTrivia().WithLeadingTrivia(WhitespaceTrivia))
.WithOpenBraceToken(SyntaxFactory.Token(SyntaxKind.OpenBraceToken).WithLeadingTrivia(NewLineTrivia).WithTrailingTrivia(NewLineTrivia))
.WithCloseBraceToken(SyntaxFactory.Token(SyntaxKind.CloseBraceToken));
public static T DecorateType<T>(this T typeDeclaration, int times = 1) where T : TypeDeclarationSyntax => (T)typeDeclaration
.WithoutTrivia().WithLeadingTrivia(TabulationTrivia.Repeat(times))
.WithIdentifier(typeDeclaration.Identifier.WithoutTrivia().WithLeadingTrivia(WhitespaceTrivia).WithTrailingTrivia(NewLineTrivia))
.WithOpenBraceToken(SyntaxFactory.Token(SyntaxKind.OpenBraceToken).WithLeadingTrivia(TabulationTrivia.Repeat(times)).WithTrailingTrivia(NewLineTrivia))
.WithCloseBraceToken(SyntaxFactory.Token(SyntaxKind.CloseBraceToken).WithLeadingTrivia(TabulationTrivia.Repeat(times)).WithTrailingTrivia(NewLineTrivia));
}
}
+64 -64
View File
@@ -47,7 +47,7 @@ This guide will walk you through the core concepts and advanced features of **Te
**Telegrator** is distributed as a NuGet package. You can install it using the .NET CLI, the NuGet Package Manager Console, or by managing NuGet packages in Visual Studio.
### Prerequisites
- .NET >= 5.0 `or` .NET Core >= 2.0 `or` Framework >= 4.6.1 (.NET Standard 2.0 compatible)
- .NET >= 5.0 `or` .NET Core >= 2.0 `or` Framework >= 4.6.1 (.NET Standard 2.1 compatible)
- A Telegram Bot Token from [@BotFather](https://t.me/BotFather).
### .NET CLI
@@ -61,7 +61,7 @@ Install-Package Telegrator
```
### Hosting Integrations
- .NET Core >= 8.0
- .NET Core >= 10.0
- `Telegrator.Hosting`: For console/background services
- `Telegrator.Hosting.Web`: For ASP.NET Core/Webhook
@@ -325,7 +325,7 @@ builder.Handlers.AddMethod<CallbackQuery>(Option1Handler);
```csharp
public enum UserState
{
Start = SpecialState.NoState,
Start,
WaitingForName,
WaitingForAge
}
@@ -333,39 +333,40 @@ public enum UserState
// Start conversation
[CommandHandler]
[CommandAlias("register")]
[EnumState<UserState>(UserState.Start)]
[State<UserState>(UserState.Start)]
private static async Task<Result> StartRegistration(IHandlerContainer<Message> container, CancellationToken cancellationToken)
{
container.ForwardEnumState<UserState>();
StateStorage.GetStateMachine<UserState>().BySenderId().Advance();
await container.Reply("Please enter your name:", cancellationToken: cancellationToken);
return Ok;
}
// Handle name input
[MessageHandler]
[EnumState<UserState>(UserState.WaitingForName)]
[State<UserState>(UserState.WaitingForName)]
private static async Task<Result> HandleName(IHandlerContainer<Message> container, CancellationToken cancellationToken)
{
var name = container.Input.Text;
container.ForwardEnumState<UserState>();
StateStorage.GetStateMachine<UserState>().BySenderId().Advance();
await container.Reply($"Hello {name}! Please enter your age:", cancellationToken: cancellationToken);
return Ok;
}
// Handle age input
[MessageHandler]
[EnumState<UserState>(UserState.WaitingForAge)]
[State<UserState>(UserState.WaitingForAge)]
private static async Task<Result> HandleAge(IHandlerContainer<Message> container, CancellationToken cancellationToken)
{
if (int.TryParse(container.Input.Text, out int age))
{
container.DeleteEnumState<UserState>();
StateStorage.GetStateMachine<UserState>().BySenderId().Reset();
await container.Reply($"Registration complete! Name: {name}, Age: {age}", cancellationToken: cancellationToken);
}
else
{
await container.Reply("Please enter a valid age (number):", cancellationToken: cancellationToken);
}
return Ok;
}
@@ -497,50 +498,43 @@ public class RestrictedHandler : MessageHandler
### 3.3. State Management
Telegrator provides built-in state management for multi-step conversations (wizards, forms, quizzes) without a database.
Telegrator provides built-in state management for multi-step conversations (wizards, forms, quizzes) with or without a database.
> [!NOTE]
> Each type of `StateKeeper`'s (EnumStateKeeper, NumericStateKeeper) is shared between **EVERY** handler in project.
**Types of State:**
- **NumericState**: Integer-based steps
- **StringState**: Named steps
- **EnumState**: Enum-based scenarios
> Each type of `StateKeeper`'s keys and states are shared between **EVERY** handler in project.
**How to Use:**
1. Define your state (enum/int/string)
2. Use a state filter attribute on your handler:
- `[EnumState<MyEnum>(MyEnum.Step1)]`
- `[NumericState(1)]`
- `[StringState("waiting_input")]`
- `[State<MyEnum>(MyEnum.Step1)]`
3. Change state inside the handler using extension methods:
- `container.ForwardEnumState<MyEnum>()`
- `container.ForwardNumericState()`
- `container.ForwardStringState()`
- `container.DeleteEnumState<MyEnum>()`
- `StateStorage.GetStateMachine<MyEnum>().BySenderId().Current()`
- `StateStorage.GetStateMachine<MyEnum>().BySenderId().Advance()`
- `StateStorage.GetStateMachine<MyEnum>().BySenderId().Retreat()`
- `StateStorage.GetStateMachine<MyEnum>().BySenderId().Reset()`
**Example:**
```csharp
public enum QuizState
{
Start = SpecialState.NoState, Q1, Q2
Start, Q1, Q2
}
[CommandHandler]
[CommandAlias("quiz")]
[EnumState<QuizState>(QuizState.Start)]
[State<QuizState>(QuizState.Start)]
public class StartQuizHandler : CommandHandler
{
public override async Task<Result> Execute(IHandlerContainer<Message> container, CancellationToken cancellation)
{
container.ForwardEnumState<QuizState>();
StateStorage.GetStateMachine<QuizState>().BySenderId().Advance();
await Reply("Quiz started! Question 1: What is the capital of France?");
return Ok;
}
}
[MessageHandler]
[EnumState<QuizState>(QuizState.Q1)]
[State<QuizState>(QuizState.Q1)]
public class Q1Handler : MessageHandler
{
public override async Task<Result> Execute(IHandlerContainer<Message> container, CancellationToken cancellation)
@@ -550,7 +544,7 @@ public class Q1Handler : MessageHandler
else
await Reply("Incorrect. The answer is Paris.");
container.ForwardEnumState<QuizState>();
StateStorage.GetStateMachine<QuizState>().BySenderId().Advance();
await Reply("Question 2: What is 2 + 2?");
return Ok;
}
@@ -559,8 +553,8 @@ public class Q1Handler : MessageHandler
> **How is it working?**
> 1. **Enum State Definition**: `QuizState` enum defines the conversation flow with `Start = SpecialState.NoState` indicating no initial state.
> 2. **State Filter**: `[EnumState<QuizState>(QuizState.Start)]` ensures the handler only runs when the user is in the "Start" state.
> 3. **State Transition**: `container.ForwardEnumState<QuizState>()` moves the user to the next state (Q1).
> 2. **State Filter**: `[State<QuizState>(QuizState.Start)]` ensures the handler only runs when the user is in the "Start" state.
> 3. **State Transition**: `StateStorage.GetStateMachine<QuizState>().BySenderId().Advance()` moves the user to the next state (Q1).
> 4. **Next Handler**: The `Q1Handler` will only run when the user is in state `QuizState.Q1`.
> 5. **State Management**: Each handler manages its own state transition, creating a clear conversation flow.
@@ -1078,7 +1072,7 @@ var host = webHost.Build();
await host.StartAsync();
```
**Note:** For web hosting, you need to configure both `TelegramBotClientOptions` (for bot token) and `TelegratorWebOptions` (for webhook settings) in your `appsettings.json` file.
**Note:** For web hosting, you need to configure both `TelegratorOptions` (for bot token) and `WebhookerOptions` (for webhook settings) in your `appsettings.json` file.
> **How is it working?**
> 1. **Console Integration**: `TelegratorClient` provides a simple way to create bots in console applications.
@@ -1425,28 +1419,28 @@ public enum UserState
// Start conversation
[CommandHandler]
[CommandAlias("register")]
[EnumState<UserState>(UserState.Start)]
[State<UserState>(UserState.Start)]
private static async Task<Result> StartRegistration(IHandlerContainer<Message> container, CancellationToken cancellationToken)
{
container.ForwardEnumState<UserState>();
StateStorage.GetStateMachine<UserState>().BySenderId().Advance();
await container.Reply("Please enter your name:", cancellationToken: cancellationToken);
return Ok;
}
// Handle name input
[MessageHandler]
[EnumState<UserState>(UserState.WaitingForName)]
[State<UserState>(UserState.WaitingForName)]
private static async Task<Result> HandleName(IHandlerContainer<Message> container, CancellationToken cancellationToken)
{
var name = container.Input.Text;
container.ForwardEnumState<UserState>();
StateStorage.GetStateMachine<UserState>().BySenderId().Advance();
await container.Reply($"Hello {name}! Please enter your age:", cancellationToken: cancellationToken);
return Ok;
}
// Handle age input
[MessageHandler]
[EnumState<UserState>(UserState.WaitingForAge)]
[State<UserState>(UserState.WaitingForAge)]
private static async Task<Result> HandleAge(IHandlerContainer<Message> container, CancellationToken cancellationToken)
{
if (int.TryParse(container.Input.Text, out int age))
@@ -1723,12 +1717,11 @@ dotnet add package Telegrator.Hosting.Web
**Core Components:**
- `TelegramBotWebHost` - The main web hosted service for webhook handling
- `TelegramBotWebHostBuilder` - Builder pattern for configuring the web host
- `TelegramBotWebOptions` - Configuration options for web application settings
- `TelegratorWebOptions` - Configuration options for webhook settings
- `WebhookerOptions` - Configuration options for webhook settings
**Configuration Requirements:**
- `TelegramBotClientOptions` must be configured as it contains the bot token
- `TelegratorWebOptions` must be configured through external sources (appsettings.json) for webhook settings
- `TelegratorOptions` must be configured as it contains the bot token
- `WebhookerOptions` must be configured through external sources (appsettings.json) for webhook settings
**Basic Example:**
```csharp
@@ -1750,11 +1743,11 @@ await host.StartAsync();
**Configuration via appsettings.json:**
```json
{
"TelegramBotClientOptions": {
"TelegratorOptions": {
"Token": "YOUR_BOT_TOKEN"
},
"TelegratorWebOptions": {
"WebhookerOptions": {
"WebhookUri": "https://your-domain.com/webhook",
"SecretToken": "your-secret-token",
"MaxConnections": 40,
@@ -1790,7 +1783,7 @@ The bot token must be configured either in `appsettings.json` or through environ
```json
{
"TelegramBotClientOptions": {
"TelegratorOptions": {
"Token": "YOUR_BOT_TOKEN"
}
}
@@ -1805,7 +1798,7 @@ export TelegramBotClientOptions__Token="YOUR_BOT_TOKEN"
**Advanced Configuration:**
```json
{
"TelegramBotClientOptions": {
"TelegratorOptions": {
"Token": "YOUR_BOT_TOKEN",
"BaseUrl": "https://api.telegram.org"
},
@@ -2158,10 +2151,11 @@ await host.StartAsync();
**Configuration (appsettings.json):**
```json
{
"TelegramBotClientOptions": {
"TelegratorOptions": {
"Token": "YOUR_BOT_TOKEN"
},
"TelegratorWebOptions": {
"WebhookerOptions": {
"WebhookUri": "https://your-domain.com/webhook",
"SecretToken": "your-secret-token",
"MaxConnections": 40,
@@ -2283,12 +2277,12 @@ public class StartWizardHandler : CommandHandler
### 7.5. Logging System
Telegrator provides a centralized logging system called "Alligator" that allows integration with various logging frameworks while maintaining zero dependencies in the core library.
Telegrator provides a centralized logging system called "TelegratorLogging" that allows integration with various logging frameworks while maintaining zero dependencies in the core library.
#### Overview
The logging system consists of:
- **Alligator** - Centralized static logging system
- **TelegratorLogging** - Centralized static logging system
- **ITelegratorLogger** - Core logging interface
- **NullLogger** - No-op logger
- **ConsoleLogger** - Simple console output
@@ -2301,11 +2295,11 @@ The logging system consists of:
using Telegrator.Logging;
// Add console adapter
Alligator.AddAdapter(new ConsoleLoggerAdapter(LogLevel.Debug, includeTimestamp: true));
TelegratorLogging.AddAdapter(new ConsoleLoggerAdapter(LogLevel.Debug, includeTimestamp: true));
// Use logging
Alligator.LogInformation("Bot started");
Alligator.LogError("Something went wrong", exception);
TelegratorLogging.LogInformation("Bot started");
TelegratorLogging.LogError("Something went wrong", exception);
```
**Custom Logger Adapter:**
@@ -2322,7 +2316,7 @@ public class CustomLogger : ITelegratorLogger
}
// Add custom adapter
Alligator.AddAdapter(new CustomLogger());
TelegratorLogging.AddAdapter(new CustomLogger());
```
#### Hosting Integration
@@ -2340,7 +2334,7 @@ var loggerFactory = LoggerFactory.Create(builder =>
// Add Microsoft.Extensions.Logging adapter
ILogger<Program> logger = loggerFactory.CreateLogger<Program>();
MicrosoftLoggingAdapter adapter = new MicrosoftLoggingAdapter(logger);
Alligator.AddAdapter(adapter);
TelegratorLogging.AddAdapter(adapter);
var bot = new TelegratorClient("<BOT_TOKEN>");
```
@@ -2359,14 +2353,14 @@ All logging methods support simple message logging:
```csharp
// In your handlers or aspects
Alligator.LogInformation("Handler executed");
Alligator.LogError("Something went wrong", exception);
Alligator.LogWarning("User exceeded rate limit");
TelegratorLogging.LogInformation("Handler executed");
TelegratorLogging.LogError("Something went wrong", exception);
TelegratorLogging.LogWarning("User exceeded rate limit");
```
#### Performance Considerations
- **Alligator** has minimal overhead with thread-safe adapter management
- **TelegratorLogging** has minimal overhead with thread-safe adapter management
- **NullLogger** has zero overhead
- **ConsoleLogger** is lightweight for development
- **MicrosoftLoggingAdapter** delegates to the underlying framework
@@ -2576,11 +2570,11 @@ For detailed information about the logging system, see [section 7.5 - Logging Sy
using Telegrator.Logging;
// Add console adapter for debugging
Alligator.AddConsoleAdapter(LogLevel.Debug, includeTimestamp: true);
TelegratorLogging.AddConsoleAdapter(LogLevel.Debug, includeTimestamp: true);
// Use logging
Alligator.LogInformation("Bot started");
Alligator.LogError("Something went wrong", exception);
TelegratorLogging.LogInformation("Bot started");
TelegratorLogging.LogError("Something went wrong", exception);
```
#### Common Debugging Steps
@@ -2599,9 +2593,9 @@ Alligator.LogError("Something went wrong", exception);
#### Simple Logging
```csharp
// In your handlers or aspects
Alligator.LogInformation("Handler executed");
Alligator.LogError("Something went wrong", exception);
Alligator.LogWarning("User exceeded rate limit");
TelegratorLogging.LogInformation("Handler executed");
TelegratorLogging.LogError("Something went wrong", exception);
TelegratorLogging.LogWarning("User exceeded rate limit");
```
---
@@ -2617,4 +2611,10 @@ Alligator.LogWarning("User exceeded rate limit");
> **Feel free to contribute, ask questions, or open issues!**
Сишарпилло Крокодилло
В главных ролях :
> Сишарпилло Крокодилло,
> Дыкий Сишарп,
> Шарпенко Михаил Дотнетович
Кастинг и Тестирование :
> не проводилось
+29
View File
@@ -0,0 +1,29 @@
<?xml version="1.0"?>
<doc>
<assembly>
<name>Telegartor.RedisStateStorage</name>
</assembly>
<members>
<member name="T:Telegrator.States.RedisStateStorage">
<summary>
Provides a Redis-based implementation of the <see cref="T:Telegrator.Core.States.IStateStorage"/> interface.
Serializes state objects to JSON format before storing them in the Redis database.
</summary>
</member>
<member name="M:Telegrator.States.RedisStateStorage.#ctor(StackExchange.Redis.IConnectionMultiplexer)">
<summary>
Provides a Redis-based implementation of the <see cref="T:Telegrator.Core.States.IStateStorage"/> interface.
Serializes state objects to JSON format before storing them in the Redis database.
</summary>
</member>
<member name="M:Telegrator.States.RedisStateStorage.SetAsync``1(System.String,``0,System.Threading.CancellationToken)">
<inheritdoc/>
</member>
<member name="M:Telegrator.States.RedisStateStorage.GetAsync``1(System.String,System.Threading.CancellationToken)">
<inheritdoc/>
</member>
<member name="M:Telegrator.States.RedisStateStorage.DeleteAsync(System.String,System.Threading.CancellationToken)">
<inheritdoc/>
</member>
</members>
</doc>
+61 -11
View File
@@ -28,6 +28,9 @@
This application's logger
</summary>
</member>
<member name="P:Telegrator.Hosting.Web.TelegramBotWebHost.Properties">
<inheritdoc/>
</member>
<member name="M:Telegrator.Hosting.Web.TelegramBotWebHost.#ctor(Microsoft.AspNetCore.Builder.WebApplicationBuilder)">
<summary>
Initializes a new instance of the <see cref="T:Microsoft.AspNetCore.Builder.WebApplicationBuilder"/> class.
@@ -81,9 +84,7 @@
</summary>
</member>
<member name="T:Telegrator.Hosting.Web.TelegramBotWebHostBuilder">
<summary>
Represents a web hosted telegram bots and services builder that helps manage configuration, logging, lifetime, and more.
</summary>
<inheritdoc/>
</member>
<member name="P:Telegrator.Hosting.Web.TelegramBotWebHostBuilder.Handlers">
<inheritdoc/>
@@ -100,20 +101,24 @@
<member name="P:Telegrator.Hosting.Web.TelegramBotWebHostBuilder.Environment">
<inheritdoc/>
</member>
<member name="M:Telegrator.Hosting.Web.TelegramBotWebHostBuilder.#ctor(Microsoft.AspNetCore.Builder.WebApplicationBuilder,Microsoft.AspNetCore.Builder.WebApplicationOptions)">
<member name="P:Telegrator.Hosting.Web.TelegramBotWebHostBuilder.Properties">
<inheritdoc/>
</member>
<member name="P:Telegrator.Hosting.Web.TelegramBotWebHostBuilder.Metrics">
<inheritdoc/>
</member>
<member name="M:Telegrator.Hosting.Web.TelegramBotWebHostBuilder.#ctor(Microsoft.AspNetCore.Builder.WebApplicationBuilder)">
<summary>
Initializes a new instance of the <see cref="T:Telegrator.Hosting.Web.TelegramBotWebHostBuilder"/> class.
</summary>
<param name="webApplicationBuilder"></param>
<param name="settings"></param>
</member>
<member name="M:Telegrator.Hosting.Web.TelegramBotWebHostBuilder.#ctor(Microsoft.AspNetCore.Builder.WebApplicationBuilder,Telegrator.Core.IHandlersCollection,Microsoft.AspNetCore.Builder.WebApplicationOptions)">
<member name="M:Telegrator.Hosting.Web.TelegramBotWebHostBuilder.#ctor(Microsoft.AspNetCore.Builder.WebApplicationBuilder,Telegrator.Core.IHandlersCollection)">
<summary>
Initializes a new instance of the <see cref="T:Telegrator.Hosting.Web.TelegramBotWebHostBuilder"/> class.
</summary>
<param name="webApplicationBuilder"></param>
<param name="handlers"></param>
<param name="settings"></param>
</member>
<member name="M:Telegrator.Hosting.Web.TelegramBotWebHostBuilder.Build">
<summary>
@@ -121,6 +126,9 @@
</summary>
<returns></returns>
</member>
<member name="M:Telegrator.Hosting.Web.TelegramBotWebHostBuilder.ConfigureContainer``1(Microsoft.Extensions.DependencyInjection.IServiceProviderFactory{``0},System.Action{``0})">
<inheritdoc/>
</member>
<member name="T:Telegrator.Hosting.Web.WebhookerOptions">
<summary>
Configuration options for Telegram bot behavior and execution settings.
@@ -155,11 +163,10 @@
Service for receiving updates for Hosted telegram bots via Webhooks
</summary>
</member>
<member name="M:Telegrator.Mediation.HostedUpdateWebhooker.#ctor(Microsoft.AspNetCore.Routing.IEndpointRouteBuilder,Telegram.Bot.ITelegramBotClient,Telegrator.Core.IUpdateRouter,Microsoft.Extensions.Options.IOptions{Telegrator.Hosting.Web.WebhookerOptions})">
<member name="M:Telegrator.Mediation.HostedUpdateWebhooker.#ctor(Telegram.Bot.ITelegramBotClient,Telegrator.Core.IUpdateRouter,Microsoft.Extensions.Options.IOptions{Telegrator.Hosting.Web.WebhookerOptions})">
<summary>
Initiallizes new instance of <see cref="T:Telegrator.Mediation.HostedUpdateWebhooker"/>
</summary>
<param name="botHost"></param>
<param name="botClient"></param>
<param name="updateRouter"></param>
<param name="options"></param>
@@ -171,6 +178,22 @@
<member name="M:Telegrator.Mediation.HostedUpdateWebhooker.StopAsync(System.Threading.CancellationToken)">
<inheritdoc/>
</member>
<member name="M:Telegrator.Mediation.HostedUpdateWebhooker.RemapWebhook(Microsoft.AspNetCore.Routing.IEndpointRouteBuilder,System.String,System.Threading.CancellationToken)">
<summary>
Allows to remap receiving webhook endpoint and map new route to webhost.
</summary>
<param name="routeBuilder"></param>
<param name="webhookUri"></param>
<param name="cancellationToken"></param>
<returns></returns>
<exception cref="T:System.ArgumentException"></exception>
</member>
<member name="M:Telegrator.Mediation.HostedUpdateWebhooker.MapWebhook(Microsoft.AspNetCore.Routing.IEndpointRouteBuilder)">
<summary>
Maps bot webhook to application builder
</summary>
<param name="routeBuilder"></param>
</member>
<member name="T:Telegrator.ServicesCollectionExtensions">
<summary>
Contains extensions for <see cref="T:Microsoft.Extensions.DependencyInjection.IServiceCollection"/>
@@ -185,17 +208,44 @@
<member name="M:Telegrator.ServicesCollectionExtensions.get_Handlers(Microsoft.AspNetCore.Builder.WebApplicationBuilder)">
<inheritdoc cref="P:Telegrator.ServicesCollectionExtensions.&lt;G&gt;$41F16C2D39AF52899E745C9C9F42FF83.Handlers"/>
</member>
<member name="M:Telegrator.ServicesCollectionExtensions.AddTelegratorWeb(Microsoft.Extensions.Hosting.IHostApplicationBuilder,Microsoft.AspNetCore.Builder.WebApplicationOptions,Telegrator.TelegratorOptions,Telegrator.Core.IHandlersCollection)">
<member name="M:Telegrator.ServicesCollectionExtensions.AddTelegratorWeb(Telegrator.Hosting.ITelegramBotHostBuilder,Telegrator.TelegratorOptions,Telegrator.Core.IHandlersCollection,System.Action{Telegrator.Hosting.ITelegramBotHostBuilder})">
<summary>
Replaces TelegramBotWebHostBuilder. Configures DI, options, and handlers.
</summary>
</member>
<member name="M:Telegrator.ServicesCollectionExtensions.UseTelegratorWeb(Microsoft.AspNetCore.Builder.WebApplication)">
<member name="M:Telegrator.ServicesCollectionExtensions.AddTelegratorWeb(Microsoft.AspNetCore.Builder.WebApplicationBuilder,Telegrator.TelegratorOptions,Telegrator.Core.IHandlersCollection,System.Action{Telegrator.Hosting.ITelegramBotHostBuilder})">
<summary>
Replaces TelegramBotWebHostBuilder. Configures DI, options, and handlers.
</summary>
</member>
<member name="M:Telegrator.ServicesCollectionExtensions.AddTelegratorWebInternal(Microsoft.Extensions.DependencyInjection.IServiceCollection,Microsoft.Extensions.Configuration.IConfiguration,System.Collections.Generic.IDictionary{System.Object,System.Object},Telegrator.Core.IHandlersCollection@,Telegrator.TelegratorOptions)">
<summary>
Replaces TelegramBotWebHostBuilder. Configures DI, options, and handlers.
</summary>
</member>
<member name="M:Telegrator.ServicesCollectionExtensions.TryFindWebhooker(System.IServiceProvider,Telegrator.Mediation.HostedUpdateWebhooker@)">
<summary>
Searchs for <see cref="T:Telegrator.Mediation.HostedUpdateWebhooker"/> hosted service inside hosts services
</summary>
<param name="services"></param>
<param name="webhooker"></param>
<returns></returns>
</member>
<member name="M:Telegrator.ServicesCollectionExtensions.UseTelegratorWeb``1(``0,System.Boolean)">
<summary>
Replaces the initialization logic from TelegramBotWebHost constructor.
Initializes the bot and logs handlers on application startup.
</summary>
</member>
<member name="M:Telegrator.ServicesCollectionExtensions.RemapWebhook``1(``0,System.String)">
<summary>
Allows to remap receiving webhook endpoint and map new route to webhost.
</summary>
<param name="app"></param>
<param name="webhookUri"></param>
<returns></returns>
<exception cref="T:System.ArgumentException"></exception>
</member>
<member name="M:Telegrator.ServicesCollectionExtensions.AddTelegramWebhook(Microsoft.Extensions.DependencyInjection.IServiceCollection)">
<summary>
Registers <see cref="T:Telegram.Bot.ITelegramBotClient"/> service with <see cref="T:Telegrator.Mediation.HostedUpdateWebhooker"/> to receive updates using webhook
+50 -39
View File
@@ -13,7 +13,7 @@
<param name="services"></param>
<param name="configuration"></param>
</member>
<member name="M:Telegrator.Hosting.HostedTelegramBotInfo.#ctor(Telegram.Bot.ITelegramBotClient,System.IServiceProvider,Microsoft.Extensions.Configuration.IConfigurationManager)">
<member name="M:Telegrator.Hosting.HostedTelegramBotInfo.#ctor(Telegram.Bot.ITelegramBotClient,System.IServiceProvider,Microsoft.Extensions.Configuration.IConfiguration)">
<summary>
Implementation of <see cref="T:Telegrator.Core.ITelegramBotInfo"/> that provides bot information.
Contains metadata about the Telegram bot including user details and service provider for wider filterring abilities
@@ -37,23 +37,7 @@
</member>
<member name="T:Telegrator.Hosting.ITelegramBotHostBuilder">
<summary>
Interface for building Telegram bot hosts with dependency injection support.
Combines host application building capabilities with handler collection functionality.
</summary>
</member>
<member name="P:Telegrator.Hosting.ITelegramBotHostBuilder.Configuration">
<summary>
Gets the set of key/value configuration properties.
</summary>
</member>
<member name="P:Telegrator.Hosting.ITelegramBotHostBuilder.Logging">
<summary>
Gets a collection of logging providers for the application to compose. This is useful for adding new logging providers.
</summary>
</member>
<member name="P:Telegrator.Hosting.ITelegramBotHostBuilder.Services">
<summary>
Gets a collection of services for the application to compose. This is useful for adding user provided or framework provided services.
Represents a hosted telegram bots and services builder that helps manage configuration, logging, lifetime, and more.
</summary>
</member>
<member name="T:Telegrator.Hosting.TelegramBotHost">
@@ -114,9 +98,7 @@
</summary>
</member>
<member name="T:Telegrator.Hosting.TelegramBotHostBuilder">
<summary>
Represents a hosted telegram bots and services builder that helps manage configuration, logging, lifetime, and more.
</summary>
<inheritdoc/>
</member>
<member name="P:Telegrator.Hosting.TelegramBotHostBuilder.Handlers">
<inheritdoc/>
@@ -133,20 +115,24 @@
<member name="P:Telegrator.Hosting.TelegramBotHostBuilder.Environment">
<inheritdoc/>
</member>
<member name="M:Telegrator.Hosting.TelegramBotHostBuilder.#ctor(Microsoft.Extensions.Hosting.HostApplicationBuilder,Microsoft.Extensions.Hosting.HostApplicationBuilderSettings)">
<member name="P:Telegrator.Hosting.TelegramBotHostBuilder.Properties">
<inheritdoc/>
</member>
<member name="P:Telegrator.Hosting.TelegramBotHostBuilder.Metrics">
<inheritdoc/>
</member>
<member name="M:Telegrator.Hosting.TelegramBotHostBuilder.#ctor(Microsoft.Extensions.Hosting.HostApplicationBuilder)">
<summary>
Initializes a new instance of the <see cref="T:Telegrator.Hosting.TelegramBotHostBuilder"/> class.
</summary>
<param name="hostApplicationBuilder"></param>
<param name="settings"></param>
</member>
<member name="M:Telegrator.Hosting.TelegramBotHostBuilder.#ctor(Microsoft.Extensions.Hosting.HostApplicationBuilder,Telegrator.Core.IHandlersCollection,Microsoft.Extensions.Hosting.HostApplicationBuilderSettings)">
<member name="M:Telegrator.Hosting.TelegramBotHostBuilder.#ctor(Microsoft.Extensions.Hosting.HostApplicationBuilder,Telegrator.Core.IHandlersCollection)">
<summary>
Initializes a new instance of the <see cref="T:Telegrator.Hosting.TelegramBotHostBuilder"/> class.
</summary>
<param name="hostApplicationBuilder"></param>
<param name="handlers"></param>
<param name="settings"></param>
</member>
<member name="M:Telegrator.Hosting.TelegramBotHostBuilder.Build">
<summary>
@@ -154,6 +140,9 @@
</summary>
<returns></returns>
</member>
<member name="M:Telegrator.Hosting.TelegramBotHostBuilder.ConfigureContainer``1(Microsoft.Extensions.DependencyInjection.IServiceProviderFactory{``0},System.Action{``0})">
<inheritdoc/>
</member>
<member name="T:Telegrator.Logging.MicrosoftLoggingAdapter">
<summary>
Adapter for Microsoft.Extensions.Logging to work with Telegrator logging system.
@@ -190,12 +179,6 @@
<member name="M:Telegrator.Polling.HostedUpdateReceiver.ExecuteAsync(System.Threading.CancellationToken)">
<inheritdoc/>
</member>
<member name="T:Telegrator.Polling.HostUpdateHandlersPool">
<inheritdoc/>
</member>
<member name="M:Telegrator.Polling.HostUpdateHandlersPool.#ctor(Telegrator.Core.IUpdateRouter,Microsoft.Extensions.Options.IOptions{Telegrator.TelegratorOptions})">
<inheritdoc/>
</member>
<member name="T:Telegrator.Polling.HostUpdateRouter">
<inheritdoc/>
</member>
@@ -204,7 +187,7 @@
<see cref="T:Microsoft.Extensions.Logging.ILogger"/> of this router
</summary>
</member>
<member name="M:Telegrator.Polling.HostUpdateRouter.#ctor(Telegrator.Core.IHandlersProvider,Telegrator.Core.IAwaitingProvider,Microsoft.Extensions.Options.IOptions{Telegrator.TelegratorOptions},Telegrator.Core.IUpdateHandlersPool,Telegrator.Core.ITelegramBotInfo,Microsoft.Extensions.Logging.ILogger{Telegrator.Polling.HostUpdateRouter})">
<member name="M:Telegrator.Polling.HostUpdateRouter.#ctor(Telegrator.Core.IHandlersProvider,Telegrator.Core.IAwaitingProvider,Telegrator.Core.States.IStateStorage,Microsoft.Extensions.Options.IOptions{Telegrator.TelegratorOptions},Telegrator.Core.ITelegramBotInfo,Microsoft.Extensions.Logging.ILogger{Telegrator.Polling.HostUpdateRouter})">
<inheritdoc/>
</member>
<member name="M:Telegrator.Polling.HostUpdateRouter.HandleUpdateAsync(Telegram.Bot.ITelegramBotClient,Telegram.Bot.Types.Update,System.Threading.CancellationToken)">
@@ -222,7 +205,7 @@
<member name="T:Telegrator.Providers.HostAwaitingProvider">
<inheritdoc/>
</member>
<member name="M:Telegrator.Providers.HostAwaitingProvider.#ctor(Microsoft.Extensions.Options.IOptions{Telegrator.TelegratorOptions},Microsoft.Extensions.Logging.ILogger{Telegrator.Providers.HostAwaitingProvider})">
<member name="M:Telegrator.Providers.HostAwaitingProvider.#ctor(Microsoft.Extensions.Options.IOptions{Telegrator.TelegratorOptions})">
<inheritdoc/>
</member>
<member name="T:Telegrator.Providers.HostHandlersCollection">
@@ -240,7 +223,7 @@
<member name="T:Telegrator.Providers.HostHandlersProvider">
<inheritdoc/>
</member>
<member name="M:Telegrator.Providers.HostHandlersProvider.#ctor(Telegrator.Core.IHandlersCollection,Microsoft.Extensions.Options.IOptions{Telegrator.TelegratorOptions},System.IServiceProvider,Microsoft.Extensions.Logging.ILogger{Telegrator.Providers.HostHandlersProvider})">
<member name="M:Telegrator.Providers.HostHandlersProvider.#ctor(Telegrator.Core.IHandlersCollection,Microsoft.Extensions.Options.IOptions{Telegrator.TelegratorOptions},System.IServiceProvider)">
<inheritdoc/>
</member>
<member name="M:Telegrator.Providers.HostHandlersProvider.GetHandlerInstance(Telegrator.Core.Descriptors.HandlerDescriptor,System.Threading.CancellationToken)">
@@ -256,15 +239,35 @@
The key used to store the <see cref="T:Telegrator.Core.IHandlersCollection"/> in the builder properties.
</summary>
</member>
<member name="M:Telegrator.HostBuilderExtensions.get_Handlers(Microsoft.Extensions.Hosting.IHostApplicationBuilder)">
<inheritdoc cref="P:Telegrator.HostBuilderExtensions.&lt;G&gt;$605D8CCF64349EA050C790D67C500BD9.Handlers"/>
<member name="M:Telegrator.HostBuilderExtensions.get_Handlers(Microsoft.Extensions.Hosting.HostApplicationBuilder)">
<inheritdoc cref="P:Telegrator.HostBuilderExtensions.&lt;G&gt;$BF7227490CCA365283B3A9274C9033C7.Handlers"/>
</member>
<member name="M:Telegrator.HostBuilderExtensions.AddTelegrator(Microsoft.Extensions.Hosting.IHostApplicationBuilder,Microsoft.Extensions.Hosting.HostApplicationBuilderSettings,Telegrator.TelegratorOptions,Telegrator.Core.IHandlersCollection)">
<member name="M:Telegrator.HostBuilderExtensions.AddTelegrator(Telegrator.Hosting.ITelegramBotHostBuilder,Telegrator.TelegratorOptions,Telegrator.Core.IHandlersCollection,System.Action{Telegrator.Hosting.ITelegramBotHostBuilder})">
<summary>
Replaces TelegramBotWebHostBuilder. Configures DI, options, and handlers.
Replaces TelegramBotHostBuilder. Configures DI, options, and handlers.
</summary>
</member>
<member name="P:Telegrator.HostBuilderExtensions.&lt;G&gt;$605D8CCF64349EA050C790D67C500BD9.Handlers">
<member name="M:Telegrator.HostBuilderExtensions.AddTelegrator(Microsoft.Extensions.Hosting.HostApplicationBuilder,Telegrator.TelegratorOptions,Telegrator.Core.IHandlersCollection,System.Action{Telegrator.Hosting.ITelegramBotHostBuilder})">
<summary>
Replaces TelegramBotHostBuilder. Configures DI, options, and handlers.
</summary>
</member>
<member name="M:Telegrator.HostBuilderExtensions.AddTelegrator(Microsoft.Extensions.Hosting.IHostApplicationBuilder,Telegrator.TelegratorOptions,Telegrator.Core.IHandlersCollection)">
<summary>
Replaces TelegramBotHostBuilder. Configures DI, options, and handlers.
</summary>
</member>
<member name="M:Telegrator.HostBuilderExtensions.AddTelegrator(Microsoft.Extensions.Hosting.IHostBuilder,Telegrator.TelegratorOptions,Telegrator.Core.IHandlersCollection)">
<summary>
Replaces TelegramBotHostBuilder. Configures DI, options, and handlers.
</summary>
</member>
<member name="M:Telegrator.HostBuilderExtensions.AddTelegratorInternal(Microsoft.Extensions.DependencyInjection.IServiceCollection,Microsoft.Extensions.Configuration.IConfiguration,System.Collections.Generic.IDictionary{System.Object,System.Object},Telegrator.Core.IHandlersCollection@,Telegrator.TelegratorOptions)">
<summary>
Replaces TelegramBotHostBuilder. Configures DI, options, and handlers.
</summary>
</member>
<member name="P:Telegrator.HostBuilderExtensions.&lt;G&gt;$BF7227490CCA365283B3A9274C9033C7.Handlers">
<summary>
Gets the <see cref="T:Telegrator.Core.IHandlersCollection"/> from the builder properties.
</summary>
@@ -275,6 +278,14 @@
Provides method to configure Telegram Bot Host
</summary>
</member>
<member name="M:Telegrator.ServicesCollectionExtensions.AddStateStorage``1(Microsoft.Extensions.DependencyInjection.IServiceCollection)">
<summary>
Registers <see cref="T:Telegrator.Core.States.IStateStorage"/> service
</summary>
<typeparam name="TStorage"></typeparam>
<param name="services"></param>
<returns></returns>
</member>
<member name="M:Telegrator.ServicesCollectionExtensions.AddTelegramBotHostDefaults(Microsoft.Extensions.DependencyInjection.IServiceCollection)">
<summary>
Registers <see cref="T:Telegrator.Hosting.TelegramBotHost"/> default services
+480 -900
View File
File diff suppressed because it is too large Load Diff
+13
View File
@@ -0,0 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Telegrator" Version="1.16.2"/>
</ItemGroup>
</Project>
+11
View File
@@ -0,0 +1,11 @@
using Telegrator;
namespace Telegrator.Examples;
public class EchoBot
{
public static void Main(string[] args)
{
var client = new TelegratorClient();
}
}
@@ -0,0 +1,57 @@
# Telegrator.RedisStateStorage
**Telegrator.RedisStateStorage** is an extension for the Telegrator framework that provides Redis powered IStateStorage implementation.
---
## Requirements
- .NET standart 2.1 or later
- [Telegrator](https://github.com/Rikitav/Telegrator)
---
## Installation
```shell
dotnet add package Telegrator.RedisStateStorage
```
---
## Quick Start Example
**Program.cs:**
```csharp
using Telegrator.Hosting;
// Creating builder
TelegramBotHostBuilder builder = TelegramBotHost.CreateBuilder(new HostApplicationBuilderSettings()
{
Args = args,
ApplicationName = "TelegramBotHost example",
});
// Registerring handlers
builder.Handlers.CollectHandlersAssemblyWide();
// Register your services and
builder.Services.AddService<IStateStorage, RedisStateStorage>(services =>
new RedisStateStorage(ConnectionMultiplexer.Connect("server1:6379, server2:6379")));
// Building and running application
TelegramBotHost telegramBot = builder.Build();
telegramBot.SetBotCommands();
telegramBot.Run();
```
---
## 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
@@ -0,0 +1,39 @@
using StackExchange.Redis;
using System.Text.Json;
using Telegrator.Core.States;
namespace Telegrator.States;
/// <summary>
/// Provides a Redis-based implementation of the <see cref="IStateStorage"/> interface.
/// Serializes state objects to JSON format before storing them in the Redis database.
/// </summary>
public class RedisStateStorage(IConnectionMultiplexer redis) : IStateStorage
{
private readonly IDatabase _db = redis.GetDatabase();
/// <inheritdoc/>
public async Task SetAsync<T>(string key, T state, CancellationToken cancellationToken)
{
string json = JsonSerializer.Serialize(state);
await _db.StringSetAsync(key, json);
}
/// <inheritdoc/>
public async Task<T?> GetAsync<T>(string key, CancellationToken cancellationToken)
{
RedisValue json = await _db.StringGetAsync(key);
string? jsonStr = json;
if (jsonStr is null)
return default;
return JsonSerializer.Deserialize<T?>(json: jsonStr);
}
/// <inheritdoc/>
public async Task DeleteAsync(string key, CancellationToken cancellationToken = default)
{
await _db.KeyDeleteAsync(key);
}
}
@@ -0,0 +1,43 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<LangVersion>latest</LangVersion>
<RootNamespace>Telegrator</RootNamespace>
<BaseOutputPath>..\..\bin</BaseOutputPath>
<DocumentationFile>..\..\docs\$(AssemblyName).xml</DocumentationFile>
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
<GenerateDocumentationFile>True</GenerateDocumentationFile>
<EnableNETAnalyzers>True</EnableNETAnalyzers>
<EnforceCodeStyleInBuild>True</EnforceCodeStyleInBuild>
<Title>Telegrator.RedisStateStorage</Title>
<Version>1.16.6</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>
<ProjectReference Include="..\Telegrator\Telegrator.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="StackExchange.Redis" Version="2.12.14" />
</ItemGroup>
<ItemGroup>
<None Include=".\README.md" Pack="True" PackagePath="\" />
<None Include="..\..\LICENSE" Pack="True" PackagePath="\" />
<None Include="..\..\resources\telegrator_nuget.png" Pack="True" PackagePath="\" />
</ItemGroup>
</Project>
@@ -9,3 +9,4 @@ using System.Diagnostics.CodeAnalysis;
[assembly: SuppressMessage("Style", "IDE0090")]
[assembly: SuppressMessage("Usage", "CA2254")]
[assembly: SuppressMessage("Maintainability", "CA1510")]
[assembly: SuppressMessage("Style", "IDE0270")]
@@ -7,155 +7,153 @@ using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Telegrator.Core;
namespace Telegrator.Hosting.Web
namespace Telegrator.Hosting.Web;
/// <summary>
/// Represents a web hosted telegram bot
/// </summary>
public class TelegramBotWebHost : IHost, IApplicationBuilder, IEndpointRouteBuilder, IAsyncDisposable
{
private readonly WebApplication _innerApp;
private readonly IUpdateRouter _updateRouter;
private readonly ILogger<TelegramBotWebHost> _logger;
private bool _disposed;
/// <inheritdoc/>
public IServiceProvider Services => _innerApp.Services;
/// <inheritdoc/>
public IUpdateRouter UpdateRouter => _updateRouter;
/// <inheritdoc/>
public ICollection<EndpointDataSource> DataSources => ((IEndpointRouteBuilder)_innerApp).DataSources;
/// <summary>
/// Represents a web hosted telegram bot
/// Allows consumers to be notified of application lifetime events.
/// </summary>
public class TelegramBotWebHost : IHost, IApplicationBuilder, IEndpointRouteBuilder, IAsyncDisposable
public IHostApplicationLifetime Lifetime => _innerApp.Lifetime;
/// <summary>
/// This application's logger
/// </summary>
public ILogger<TelegramBotWebHost> Logger => _logger;
/// <inheritdoc/>
public IDictionary<string, object?> Properties => ((IApplicationBuilder)_innerApp).Properties;
// Private interface fields
IServiceProvider IEndpointRouteBuilder.ServiceProvider => Services;
IServiceProvider IApplicationBuilder.ApplicationServices { get => Services; set => throw new NotImplementedException(); }
IFeatureCollection IApplicationBuilder.ServerFeatures => ((IApplicationBuilder)_innerApp).ServerFeatures;
/// <summary>
/// Initializes a new instance of the <see cref="WebApplicationBuilder"/> class.
/// </summary>
/// <param name="webApplicationBuilder">The proxied instance of host builder.</param>
public TelegramBotWebHost(WebApplicationBuilder webApplicationBuilder)
{
private readonly WebApplication _innerApp;
private readonly IUpdateRouter _updateRouter;
private readonly ILogger<TelegramBotWebHost> _logger;
// Building proxy application
_innerApp = webApplicationBuilder.Build();
private bool _disposed;
// Reruesting services for this host
_updateRouter = Services.GetRequiredService<IUpdateRouter>();
_logger = Services.GetRequiredService<ILogger<TelegramBotWebHost>>();
}
/// <inheritdoc/>
public IServiceProvider Services => _innerApp.Services;
/// <summary>
/// Creates new <see cref="TelegramBotHostBuilder"/> with default services and webhook update receiving scheme
/// </summary>
/// <returns></returns>
public static TelegramBotWebHostBuilder CreateBuilder(WebApplicationOptions? settings)
{
ArgumentNullException.ThrowIfNull(settings, nameof(settings));
WebApplicationBuilder innerApp = WebApplication.CreateBuilder(settings);
TelegramBotWebHostBuilder builder = new TelegramBotWebHostBuilder(innerApp);
builder.AddTelegratorWeb();
return builder;
}
/// <inheritdoc/>
public IUpdateRouter UpdateRouter => _updateRouter;
/// <summary>
/// Creates new SLIM <see cref="TelegramBotHostBuilder"/> with default services and webhook update receiving scheme
/// </summary>
/// <returns></returns>
public static TelegramBotWebHostBuilder CreateSlimBuilder(WebApplicationOptions? settings)
{
ArgumentNullException.ThrowIfNull(settings, nameof(settings));
WebApplicationBuilder innerApp = WebApplication.CreateSlimBuilder(settings);
TelegramBotWebHostBuilder builder = new TelegramBotWebHostBuilder(innerApp);
builder.AddTelegratorWeb();
return builder;
}
/// <inheritdoc/>
public ICollection<EndpointDataSource> DataSources => ((IEndpointRouteBuilder)_innerApp).DataSources;
/// <summary>
/// Creates new EMPTY <see cref="TelegramBotHostBuilder"/> WITHOUT any services or update receiving schemes
/// </summary>
/// <returns></returns>
public static TelegramBotWebHostBuilder CreateEmptyBuilder(WebApplicationOptions? settings)
{
ArgumentNullException.ThrowIfNull(settings, nameof(settings));
WebApplicationBuilder innerApp = WebApplication.CreateEmptyBuilder(settings);
TelegramBotWebHostBuilder builder = new TelegramBotWebHostBuilder(innerApp);
builder.AddTelegratorWeb();
return builder;
}
/// <summary>
/// Allows consumers to be notified of application lifetime events.
/// </summary>
public IHostApplicationLifetime Lifetime => _innerApp.Lifetime;
/// <inheritdoc/>
public async Task StartAsync(CancellationToken cancellationToken = default)
{
await _innerApp.StartAsync(cancellationToken);
}
/// <summary>
/// This application's logger
/// </summary>
public ILogger<TelegramBotWebHost> Logger => _logger;
/// <inheritdoc/>
public async Task StopAsync(CancellationToken cancellationToken = default)
{
await _innerApp.StopAsync(cancellationToken);
}
// Private interface fields
IServiceProvider IEndpointRouteBuilder.ServiceProvider => Services;
IServiceProvider IApplicationBuilder.ApplicationServices { get => Services; set => throw new NotImplementedException(); }
IFeatureCollection IApplicationBuilder.ServerFeatures => ((IApplicationBuilder)_innerApp).ServerFeatures;
IDictionary<string, object?> IApplicationBuilder.Properties => ((IApplicationBuilder)_innerApp).Properties;
/// <inheritdoc/>
public IApplicationBuilder CreateApplicationBuilder()
=> ((IEndpointRouteBuilder)_innerApp).CreateApplicationBuilder();
/// <summary>
/// Initializes a new instance of the <see cref="WebApplicationBuilder"/> class.
/// </summary>
/// <param name="webApplicationBuilder">The proxied instance of host builder.</param>
public TelegramBotWebHost(WebApplicationBuilder webApplicationBuilder)
{
// Building proxy application
_innerApp = webApplicationBuilder.Build();
_innerApp.UseTelegratorWeb();
/// <inheritdoc/>
public IApplicationBuilder Use(Func<RequestDelegate, RequestDelegate> middleware)
=> _innerApp.Use(middleware);
// Reruesting services for this host
_updateRouter = Services.GetRequiredService<IUpdateRouter>();
_logger = Services.GetRequiredService<ILogger<TelegramBotWebHost>>();
}
/// <inheritdoc/>
public IApplicationBuilder New()
=> ((IApplicationBuilder)_innerApp).New();
/// <summary>
/// Creates new <see cref="TelegramBotHostBuilder"/> with default services and webhook update receiving scheme
/// </summary>
/// <returns></returns>
public static TelegramBotWebHostBuilder CreateBuilder(WebApplicationOptions settings)
{
ArgumentNullException.ThrowIfNull(settings, nameof(settings));
WebApplicationBuilder innerApp = WebApplication.CreateBuilder(settings);
TelegramBotWebHostBuilder builder = new TelegramBotWebHostBuilder(innerApp, settings);
/// <inheritdoc/>
public RequestDelegate Build()
=> ((IApplicationBuilder)_innerApp).Build();
builder.Services.AddTelegramBotHostDefaults();
builder.Services.AddTelegramWebhook();
return builder;
}
/// <summary>
/// Disposes the host.
/// </summary>
public async ValueTask DisposeAsync()
{
if (_disposed)
return;
/// <summary>
/// Creates new SLIM <see cref="TelegramBotHostBuilder"/> with default services and webhook update receiving scheme
/// </summary>
/// <returns></returns>
public static TelegramBotWebHostBuilder CreateSlimBuilder(WebApplicationOptions settings)
{
ArgumentNullException.ThrowIfNull(settings, nameof(settings));
WebApplicationBuilder innerApp = WebApplication.CreateSlimBuilder(settings);
TelegramBotWebHostBuilder builder = new TelegramBotWebHostBuilder(innerApp, settings);
await _innerApp.DisposeAsync();
builder.Services.AddTelegramBotHostDefaults();
builder.Services.AddTelegramWebhook();
return builder;
}
GC.SuppressFinalize(this);
_disposed = true;
}
/// <summary>
/// Creates new EMPTY <see cref="TelegramBotHostBuilder"/> WITHOUT any services or update receiving schemes
/// </summary>
/// <returns></returns>
public static TelegramBotWebHostBuilder CreateEmptyBuilder(WebApplicationOptions settings)
{
ArgumentNullException.ThrowIfNull(settings, nameof(settings));
WebApplicationBuilder innerApp = WebApplication.CreateEmptyBuilder(settings);
return new TelegramBotWebHostBuilder(innerApp, settings);
}
/// <summary>
/// Disposes the host.
/// </summary>
public void Dispose()
{
if (_disposed)
return;
/// <inheritdoc/>
public async Task StartAsync(CancellationToken cancellationToken = default)
{
await _innerApp.StartAsync(cancellationToken);
}
ValueTask disposeTask = _innerApp.DisposeAsync();
disposeTask.AsTask().Wait();
/// <inheritdoc/>
public async Task StopAsync(CancellationToken cancellationToken = default)
{
await _innerApp.StopAsync(cancellationToken);
}
/// <inheritdoc/>
public IApplicationBuilder CreateApplicationBuilder()
=> ((IEndpointRouteBuilder)_innerApp).CreateApplicationBuilder();
/// <inheritdoc/>
public IApplicationBuilder Use(Func<RequestDelegate, RequestDelegate> middleware)
=> _innerApp.Use(middleware);
/// <inheritdoc/>
public IApplicationBuilder New()
=> ((IApplicationBuilder)_innerApp).New();
/// <inheritdoc/>
public RequestDelegate Build()
=> ((IApplicationBuilder)_innerApp).Build();
/// <summary>
/// Disposes the host.
/// </summary>
public async ValueTask DisposeAsync()
{
if (_disposed)
return;
await _innerApp.DisposeAsync();
GC.SuppressFinalize(this);
_disposed = true;
}
/// <summary>
/// Disposes the host.
/// </summary>
public void Dispose()
{
if (_disposed)
return;
ValueTask disposeTask = _innerApp.DisposeAsync();
disposeTask.AsTask().Wait();
GC.SuppressFinalize(this);
_disposed = true;
}
GC.SuppressFinalize(this);
_disposed = true;
}
}
@@ -1,73 +1,75 @@
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Diagnostics.Metrics;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Telegrator.Core;
#pragma warning disable IDE0001
namespace Telegrator.Hosting.Web
namespace Telegrator.Hosting.Web;
/// <inheritdoc/>
public class TelegramBotWebHostBuilder : ITelegramBotHostBuilder
{
private readonly WebApplicationBuilder _innerBuilder;
internal IHandlersCollection _handlers = null!;
/// <inheritdoc/>
public IHandlersCollection Handlers => _handlers;
/// <inheritdoc/>
public IConfigurationManager Configuration => _innerBuilder.Configuration;
/// <inheritdoc/>
public ILoggingBuilder Logging => _innerBuilder.Logging;
/// <inheritdoc/>
public IServiceCollection Services => _innerBuilder.Services;
/// <inheritdoc/>
public IHostEnvironment Environment => _innerBuilder.Environment;
/// <inheritdoc/>
public IDictionary<object, object> Properties => ((IHostApplicationBuilder)_innerBuilder).Properties;
/// <inheritdoc/>
public IMetricsBuilder Metrics => _innerBuilder.Metrics;
/// <summary>
/// Represents a web hosted telegram bots and services builder that helps manage configuration, logging, lifetime, and more.
/// Initializes a new instance of the <see cref="TelegramBotWebHostBuilder"/> class.
/// </summary>
public class TelegramBotWebHostBuilder : ITelegramBotHostBuilder
/// <param name="webApplicationBuilder"></param>
public TelegramBotWebHostBuilder(WebApplicationBuilder webApplicationBuilder)
{
private readonly WebApplicationBuilder _innerBuilder;
private readonly WebApplicationOptions _settings;
internal IHandlersCollection _handlers = null!;
_innerBuilder = webApplicationBuilder ?? throw new ArgumentNullException(nameof(webApplicationBuilder));
}
/// <inheritdoc/>
public IHandlersCollection Handlers => _handlers;
/// <summary>
/// Initializes a new instance of the <see cref="TelegramBotWebHostBuilder"/> class.
/// </summary>
/// <param name="webApplicationBuilder"></param>
/// <param name="handlers"></param>
public TelegramBotWebHostBuilder(WebApplicationBuilder webApplicationBuilder, IHandlersCollection handlers)
{
_innerBuilder = webApplicationBuilder ?? throw new ArgumentNullException(nameof(webApplicationBuilder));
_handlers = handlers ?? throw new ArgumentNullException(nameof(handlers));
}
/// <inheritdoc/>
public IConfigurationManager Configuration => _innerBuilder.Configuration;
/// <summary>
/// Builds the host.
/// </summary>
/// <returns></returns>
public TelegramBotWebHost Build()
{
TelegramBotWebHost host = new TelegramBotWebHost(_innerBuilder);
host.UseTelegratorWeb();
return host;
}
/// <inheritdoc/>
public ILoggingBuilder Logging => _innerBuilder.Logging;
/// <inheritdoc/>
public IServiceCollection Services => _innerBuilder.Services;
/// <inheritdoc/>
public IHostEnvironment Environment => _innerBuilder.Environment;
/// <summary>
/// Initializes a new instance of the <see cref="TelegramBotWebHostBuilder"/> class.
/// </summary>
/// <param name="webApplicationBuilder"></param>
/// <param name="settings"></param>
public TelegramBotWebHostBuilder(WebApplicationBuilder webApplicationBuilder, WebApplicationOptions settings)
{
_innerBuilder = webApplicationBuilder ?? throw new ArgumentNullException(nameof(webApplicationBuilder));
_settings = settings ?? throw new ArgumentNullException(nameof(settings));
_innerBuilder.AddTelegratorWeb();
}
/// <summary>
/// Initializes a new instance of the <see cref="TelegramBotWebHostBuilder"/> class.
/// </summary>
/// <param name="webApplicationBuilder"></param>
/// <param name="handlers"></param>
/// <param name="settings"></param>
public TelegramBotWebHostBuilder(WebApplicationBuilder webApplicationBuilder, IHandlersCollection handlers, WebApplicationOptions settings)
{
_innerBuilder = webApplicationBuilder ?? throw new ArgumentNullException(nameof(webApplicationBuilder));
_settings = settings ?? throw new ArgumentNullException(nameof(settings));
_innerBuilder.AddTelegratorWeb(null, handlers);
}
/// <summary>
/// Builds the host.
/// </summary>
/// <returns></returns>
public TelegramBotWebHost Build()
{
TelegramBotWebHost host = new TelegramBotWebHost(_innerBuilder);
host.UseTelegrator();
return host;
}
/// <inheritdoc/>
public void ConfigureContainer<TContainerBuilder>(IServiceProviderFactory<TContainerBuilder> factory, Action<TContainerBuilder>? configure = null) where TContainerBuilder : notnull
{
((IHostApplicationBuilder)_innerBuilder).ConfigureContainer(factory, configure);
}
}
@@ -1,35 +1,34 @@
using System.Diagnostics.CodeAnalysis;
namespace Telegrator.Hosting.Web
namespace Telegrator.Hosting.Web;
/// <summary>
/// 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 WebhookerOptions
{
/// <summary>
/// Configuration options for Telegram bot behavior and execution settings.
/// Controls various aspects of bot operation including concurrency, routing, webhook receiving, and execution policies.
/// Gets or sets HTTPS URL to send updates to. Use an empty string to remove webhook integration
/// </summary>
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 string WebhookUri { get; set; } = string.Empty;
[StringSyntax(StringSyntaxAttribute.Uri)]
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; } = null;
/// <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; } = null;
/// <summary>
/// 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.
/// </summary>
public int MaxConnections { get; set; } = 40;
/// <summary>
/// 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.
/// </summary>
public int MaxConnections { get; set; } = 40;
/// <summary>
/// Pass true to drop all pending updates
/// </summary>
public bool DropPendingUpdates { get; set; } = false;
}
/// <summary>
/// Pass true to drop all pending updates
/// </summary>
public bool DropPendingUpdates { get; set; } = false;
}
@@ -10,88 +10,114 @@ using Telegram.Bot.Types;
using Telegrator.Core;
using Telegrator.Hosting.Web;
namespace Telegrator.Mediation
namespace Telegrator.Mediation;
/// <summary>
/// Service for receiving updates for Hosted telegram bots via Webhooks
/// </summary>
public class HostedUpdateWebhooker : IHostedService
{
private const string SecretTokenHeader = "X-Telegram-Bot-Api-Secret-Token";
private readonly ITelegramBotClient _botClient;
private readonly IUpdateRouter _updateRouter;
private readonly WebhookerOptions _options;
/// <summary>
/// Service for receiving updates for Hosted telegram bots via Webhooks
/// Initiallizes new instance of <see cref="HostedUpdateWebhooker"/>
/// </summary>
public class HostedUpdateWebhooker : IHostedService
/// <param name="botClient"></param>
/// <param name="updateRouter"></param>
/// <param name="options"></param>
/// <exception cref="ArgumentNullException"></exception>
public HostedUpdateWebhooker(ITelegramBotClient botClient, IUpdateRouter updateRouter, IOptions<WebhookerOptions> options)
{
private const string SecretTokenHeader = "X-Telegram-Bot-Api-Secret-Token";
if (string.IsNullOrEmpty(options.Value.WebhookUri))
throw new ArgumentNullException(nameof(options), "Option \"WebhookUrl\" must be set to subscribe for update recieving");
private readonly IEndpointRouteBuilder _botHost;
private readonly ITelegramBotClient _botClient;
private readonly IUpdateRouter _updateRouter;
private readonly WebhookerOptions _options;
_botClient = botClient;
_updateRouter = updateRouter;
_options = options.Value;
}
/// <summary>
/// Initiallizes new instance of <see cref="HostedUpdateWebhooker"/>
/// </summary>
/// <param name="botHost"></param>
/// <param name="botClient"></param>
/// <param name="updateRouter"></param>
/// <param name="options"></param>
/// <exception cref="ArgumentNullException"></exception>
public HostedUpdateWebhooker(IEndpointRouteBuilder botHost, ITelegramBotClient botClient, IUpdateRouter updateRouter, IOptions<WebhookerOptions> options)
/// <inheritdoc/>
public Task StartAsync(CancellationToken cancellationToken)
{
StartInternal(cancellationToken);
return Task.CompletedTask;
}
/// <inheritdoc/>
public Task StopAsync(CancellationToken cancellationToken)
{
_botClient.DeleteWebhook(_options.DropPendingUpdates, cancellationToken);
return Task.CompletedTask;
}
/// <summary>
/// Allows to remap receiving webhook endpoint and map new route to webhost.
/// </summary>
/// <param name="routeBuilder"></param>
/// <param name="webhookUri"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
/// <exception cref="ArgumentException"></exception>
public async Task RemapWebhook(IEndpointRouteBuilder routeBuilder, string webhookUri, CancellationToken cancellationToken = default)
{
if (!Uri.TryCreate(webhookUri, UriKind.Absolute, out Uri? result))
throw new ArgumentException("invalid URL");
_options.WebhookUri = result.ToString();
await SetWebhook(cancellationToken);
MapWebhook(routeBuilder);
}
/// <summary>
/// Maps bot webhook to application builder
/// </summary>
/// <param name="routeBuilder"></param>
internal void MapWebhook(IEndpointRouteBuilder routeBuilder)
{
string pattern = new UriBuilder(_options.WebhookUri).Path;
routeBuilder.MapPost(pattern, (Delegate)ReceiveUpdate);
}
private async void StartInternal(CancellationToken cancellationToken)
{
await SetWebhook(cancellationToken);
}
private async Task SetWebhook(CancellationToken cancellationToken)
{
await _botClient.SetWebhook(
url: _options.WebhookUri,
maxConnections: _options.MaxConnections,
allowedUpdates: _updateRouter.HandlersProvider.AllowedTypes,
dropPendingUpdates: _options.DropPendingUpdates,
secretToken: _options.SecretToken,
cancellationToken: cancellationToken);
}
private async Task<IResult> ReceiveUpdate(HttpContext ctx)
{
if (_options.SecretToken != null)
{
if (string.IsNullOrEmpty(options.Value.WebhookUri))
throw new ArgumentNullException(nameof(options), "Option \"WebhookUrl\" must be set to subscribe for update recieving");
_botHost = botHost;
_botClient = botClient;
_updateRouter = updateRouter;
_options = options.Value;
}
/// <inheritdoc/>
public Task StartAsync(CancellationToken cancellationToken)
{
StartInternal(cancellationToken);
return Task.CompletedTask;
}
private async void StartInternal(CancellationToken cancellationToken)
{
string pattern = new UriBuilder(_options.WebhookUri).Path;
_botHost.MapPost(pattern, (Delegate)ReceiveUpdate);
await _botClient.SetWebhook(
url: _options.WebhookUri,
maxConnections: _options.MaxConnections,
allowedUpdates: _updateRouter.HandlersProvider.AllowedTypes,
dropPendingUpdates: _options.DropPendingUpdates,
secretToken: _options.SecretToken,
cancellationToken: cancellationToken);
}
/// <inheritdoc/>
public Task StopAsync(CancellationToken cancellationToken)
{
_botClient.DeleteWebhook(_options.DropPendingUpdates, cancellationToken);
return Task.CompletedTask;
}
private async Task<IResult> 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<Update>(ctx.Request.Body, JsonBotAPI.Options, ctx.RequestAborted);
if (update is not { Id: > 0 })
if (!ctx.Request.Headers.TryGetValue(SecretTokenHeader, out StringValues strings))
return Results.BadRequest();
await _updateRouter.HandleUpdateAsync(_botClient, update, ctx.RequestAborted);
return Results.Ok();
string? secret = strings.SingleOrDefault();
if (secret == null)
return Results.BadRequest();
if (_options.SecretToken != secret)
return Results.StatusCode(401);
}
Update? update = await JsonSerializer.DeserializeAsync<Update>(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();
}
}
+5
View File
@@ -1,4 +1,5 @@
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using Telegrator.Hosting;
using Telegrator.Hosting.Web;
@@ -43,10 +44,14 @@ internal class Program
public static void TelegramBotHostBuilder_Example(string[] args)
{
ConfigurationManager configuration = new ConfigurationManager();
configuration.AddJsonFile("appsettings.json");
TelegramBotHostBuilder builder = TelegramBotHost.CreateBuilder(new HostApplicationBuilderSettings()
{
Args = args,
ApplicationName = "TelegramBotHost example",
Configuration = configuration
});
builder.Handlers.CollectHandlersAssemblyWide();
@@ -15,7 +15,7 @@
<EnforceCodeStyleInBuild>True</EnforceCodeStyleInBuild>
<Title>Telegrator.Hosting.Web</Title>
<Version>1.16.1</Version>
<Version>1.16.8</Version>
<Authors>Rikitav Tim4ik</Authors>
<Company>Rikitav Tim4ik</Company>
<RepositoryUrl>https://github.com/Rikitav/Telegrator</RepositoryUrl>
+65 -12
View File
@@ -1,12 +1,15 @@
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Routing;
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.CodeAnalysis;
using Telegram.Bot;
using Telegrator.Core;
using Telegrator.Hosting;
using Telegrator.Hosting.Web;
using Telegrator.Mediation;
using Telegrator.Providers;
@@ -41,11 +44,31 @@ namespace Telegrator
/// <summary>
/// Replaces TelegramBotWebHostBuilder. Configures DI, options, and handlers.
/// </summary>
public static IHostApplicationBuilder AddTelegratorWeb(this IHostApplicationBuilder builder, TelegratorOptions? options = null, IHandlersCollection? handlers = null)
public static ITelegramBotHostBuilder AddTelegratorWeb(this ITelegramBotHostBuilder builder, TelegratorOptions? options = null, IHandlersCollection? handlers = null, Action<ITelegramBotHostBuilder>? action = null)
{
IServiceCollection services = builder.Services;
IConfigurationManager configuration = builder.Configuration;
AddTelegratorWebInternal(builder.Services, builder.Configuration, builder.Properties, ref handlers, options);
if (builder is TelegramBotWebHostBuilder telegramBotHostBuilder)
telegramBotHostBuilder._handlers = handlers;
action?.Invoke(builder);
return builder;
}
/// <summary>
/// Replaces TelegramBotWebHostBuilder. Configures DI, options, and handlers.
/// </summary>
public static IHostApplicationBuilder AddTelegratorWeb(this WebApplicationBuilder builder, TelegratorOptions? options = null, IHandlersCollection? handlers = null, Action<ITelegramBotHostBuilder>? action = null)
{
AddTelegratorWebInternal(builder.Services, builder.Configuration, ((IHostApplicationBuilder)builder).Properties, ref handlers, options);
action?.Invoke(new TelegramBotWebHostBuilder(builder));
return builder;
}
/// <summary>
/// Replaces TelegramBotWebHostBuilder. Configures DI, options, and handlers.
/// </summary>
internal static void AddTelegratorWebInternal(IServiceCollection services, IConfiguration configuration, IDictionary<object, object> properties, [NotNull] ref IHandlersCollection? handlers, TelegratorOptions? options = null)
{
if (options == null)
{
options = configuration.GetSection(nameof(TelegratorOptions)).Get<TelegratorOptions>();
@@ -70,10 +93,7 @@ namespace Telegrator
handlers ??= new HostHandlersCollection(services, options);
services.AddSingleton(handlers);
builder.Properties.Add(HandlersCollectionPropertyKey, handlers);
if (builder is TelegramBotWebHostBuilder botHostBuilder)
botHostBuilder._handlers = handlers;
properties.Add(HandlersCollectionPropertyKey, handlers);
if (!services.Any(srvc => srvc.ImplementationType == typeof(IOptions<WebhookerOptions>)))
{
@@ -95,18 +115,32 @@ namespace Telegrator
services.AddTelegramBotHostDefaults();
services.AddTelegramWebhook();
return builder;
}
/// <summary>
/// Searchs for <see cref="HostedUpdateWebhooker"/> hosted service inside hosts services
/// </summary>
/// <param name="services"></param>
/// <param name="webhooker"></param>
/// <returns></returns>
public static bool TryFindWebhooker(this IServiceProvider services, [NotNullWhen(true)] out HostedUpdateWebhooker? webhooker)
{
webhooker = services.GetServices<IHostedService>().FirstOrDefault(s => s is HostedUpdateWebhooker) as HostedUpdateWebhooker;
return webhooker != null;
}
/// <summary>
/// Replaces the initialization logic from TelegramBotWebHost constructor.
/// Initializes the bot and logs handlers on application startup.
/// </summary>
public static WebApplication UseTelegratorWeb(this WebApplication app)
public static T UseTelegratorWeb<T>(this T app, bool dontMap = false) where T : IEndpointRouteBuilder, IHost
{
ITelegramBotInfo info = app.Services.GetRequiredService<ITelegramBotInfo>();
IHandlersCollection handlers = app.Services.GetRequiredService<IHandlersCollection>();
ILoggerFactory loggerFactory = app.Services.GetRequiredService<ILoggerFactory>();
if (!app.ServiceProvider.TryFindWebhooker(out HostedUpdateWebhooker? webhooker))
throw new InvalidOperationException("No service for type 'Telegrator.Mediation.HostedUpdateWebhooker' has been registered.");
ITelegramBotInfo info = app.ServiceProvider.GetRequiredService<ITelegramBotInfo>();
IHandlersCollection handlers = app.ServiceProvider.GetRequiredService<IHandlersCollection>();
ILoggerFactory loggerFactory = app.ServiceProvider.GetRequiredService<ILoggerFactory>();
ILogger logger = loggerFactory.CreateLogger("Telegrator.Hosting.Web.TelegratorHost");
if (logger.IsEnabled(LogLevel.Information))
@@ -116,6 +150,25 @@ namespace Telegrator
logger.LogHandlers(handlers);
}
if (!dontMap)
webhooker.MapWebhook(app);
return app;
}
/// <summary>
/// Allows to remap receiving webhook endpoint and map new route to webhost.
/// </summary>
/// <param name="app"></param>
/// <param name="webhookUri"></param>
/// <returns></returns>
/// <exception cref="ArgumentException"></exception>
public static T RemapWebhook<T>(this T app, string webhookUri) where T : IEndpointRouteBuilder, IHost
{
if (!app.ServiceProvider.TryFindWebhooker(out HostedUpdateWebhooker? webhooker))
throw new InvalidOperationException("No service for type 'Telegrator.Mediation.HostedUpdateWebhooker' has been registered.");
webhooker.RemapWebhook(app, webhookUri, default).GetAwaiter().GetResult();
return app;
}
@@ -9,3 +9,4 @@ using System.Diagnostics.CodeAnalysis;
[assembly: SuppressMessage("Style", "IDE0090")]
[assembly: SuppressMessage("Usage", "CA2254")]
[assembly: SuppressMessage("Maintainability", "CA1510")]
[assembly: SuppressMessage("Style", "IDE0270")]
@@ -3,28 +3,27 @@ using Telegram.Bot;
using Telegram.Bot.Types;
using Telegrator.Core;
namespace Telegrator.Hosting
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, IConfiguration configuration) : ITelegramBotInfo
{
/// <inheritdoc/>
public User User { get; } = client.GetMe().Result;
/// <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
/// Provides access to services of this Hosted telegram bot
/// </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;
public IServiceProvider Services { get; } = services;
/// <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;
}
/// <summary>
/// Provides access to configuration of this Hosted telegram bot
/// </summary>
public IConfiguration Configuration { get; } = configuration;
}
@@ -1,29 +1,12 @@
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Hosting;
using Telegrator.Core;
namespace Telegrator.Hosting
namespace Telegrator.Hosting;
/// <summary>
/// Represents a hosted telegram bots and services builder that helps manage configuration, logging, lifetime, and more.
/// </summary>
public interface ITelegramBotHostBuilder : IHostApplicationBuilder, ICollectingProvider
{
/// <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; }
}
}
+92 -103
View File
@@ -1,125 +1,114 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Telegrator.Core;
namespace Telegrator.Hosting
namespace Telegrator.Hosting;
/// <summary>
/// Represents a hosted telegram bot
/// </summary>
public class TelegramBotHost : IHost, ITelegratorBot
{
private readonly IHost _innerHost;
private readonly IUpdateRouter _updateRouter;
private readonly ILogger<TelegramBotHost> _logger;
private bool _disposed;
/// <inheritdoc/>
public IServiceProvider Services => _innerHost.Services;
/// <inheritdoc/>
public IUpdateRouter UpdateRouter => _updateRouter;
/// <summary>
/// Represents a hosted telegram bot
/// This application's logger
/// </summary>
public class TelegramBotHost : IHost, ITelegratorBot
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>
public TelegramBotHost(HostApplicationBuilder hostApplicationBuilder)
{
private readonly IHost _innerHost;
private readonly IServiceProvider _serviceProvider;
private readonly IUpdateRouter _updateRouter;
private readonly ILogger<TelegramBotHost> _logger;
// Registering this host in services for easy access
hostApplicationBuilder.Services.AddSingleton<ITelegratorBot>(this);
private bool _disposed;
// Building proxy hoster
_innerHost = hostApplicationBuilder.Build();
/// <inheritdoc/>
public IServiceProvider Services => _serviceProvider;
// Reruesting services for this host
_updateRouter = Services.GetRequiredService<IUpdateRouter>();
_logger = Services.GetRequiredService<ILogger<TelegramBotHost>>();
}
/// <inheritdoc/>
public IUpdateRouter UpdateRouter => _updateRouter;
/// <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);
return builder;
}
/// <summary>
/// This application's logger
/// </summary>
public ILogger<TelegramBotHost> Logger => _logger;
/// <summary>
/// Creates new <see cref="TelegramBotHostBuilder"/> with default services and long-polling update receiving scheme
/// </summary>
/// <returns></returns>
public static TelegramBotHostBuilder CreateBuilder(HostApplicationBuilderSettings? settings)
{
HostApplicationBuilder innerBuilder = new HostApplicationBuilder(settings);
TelegramBotHostBuilder builder = new TelegramBotHostBuilder(innerBuilder);
return builder;
}
/// <summary>
/// Initializes a new instance of the <see cref="TelegramBotHost"/> class.
/// </summary>
/// <param name="hostApplicationBuilder">The proxied instance of host builder.</param>
public TelegramBotHost(HostApplicationBuilder hostApplicationBuilder)
{
// Registering this host in services for easy access
hostApplicationBuilder.Services.AddSingleton<ITelegratorBot>(this);
/// <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);
}
// Building proxy hoster
_innerHost = hostApplicationBuilder.Build();
_serviceProvider = _innerHost.Services;
_innerHost.UseTelegrator();
/// <summary>
/// Creates new EMPTY <see cref="TelegramBotHostBuilder"/> WITHOUT any services or update receiving schemes
/// </summary>
/// <returns></returns>
public static TelegramBotHostBuilder CreateEmptyBuilder(HostApplicationBuilderSettings? settings)
{
HostApplicationBuilder innerBuilder = Host.CreateEmptyApplicationBuilder(settings);
return new TelegramBotHostBuilder(innerBuilder);
}
// Reruesting services for this host
_updateRouter = Services.GetRequiredService<IUpdateRouter>();
_logger = Services.GetRequiredService<ILogger<TelegramBotHost>>();
}
/// <inheritdoc/>
public async Task StartAsync(CancellationToken cancellationToken = default)
{
await _innerHost.StartAsync(cancellationToken);
}
/// <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);
/// <inheritdoc/>
public async Task StopAsync(CancellationToken cancellationToken = default)
{
await _innerHost.StopAsync(cancellationToken);
}
builder.Services.AddTelegramBotHostDefaults();
builder.Services.AddTelegramReceiver();
return builder;
}
/// <summary>
/// Disposes the host.
/// </summary>
public void Dispose()
{
if (_disposed)
return;
/// <summary>
/// Creates new <see cref="TelegramBotHostBuilder"/> with default services and long-polling update receiving scheme
/// </summary>
/// <returns></returns>
public static TelegramBotHostBuilder CreateBuilder(HostApplicationBuilderSettings? settings)
{
HostApplicationBuilder innerBuilder = new HostApplicationBuilder(settings);
TelegramBotHostBuilder builder = new TelegramBotHostBuilder(innerBuilder, settings);
_innerHost.Dispose();
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(HostApplicationBuilderSettings? 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;
}
GC.SuppressFinalize(this);
_disposed = true;
}
}
@@ -1,75 +1,74 @@
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Diagnostics.Metrics;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Telegrator.Core;
using Telegrator.Providers;
#pragma warning disable IDE0001
namespace Telegrator.Hosting
namespace Telegrator.Hosting;
/// <inheritdoc/>
public class TelegramBotHostBuilder : ITelegramBotHostBuilder
{
private readonly HostApplicationBuilder _innerBuilder;
internal IHandlersCollection _handlers = null!;
/// <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;
/// <inheritdoc/>
public IDictionary<object, object> Properties => ((IHostApplicationBuilder)_innerBuilder).Properties;
/// <inheritdoc/>
public IMetricsBuilder Metrics => _innerBuilder.Metrics;
/// <summary>
/// Represents a hosted telegram bots and services builder that helps manage configuration, logging, lifetime, and more.
/// Initializes a new instance of the <see cref="TelegramBotHostBuilder"/> class.
/// </summary>
public class TelegramBotHostBuilder : ICollectingProvider
/// <param name="hostApplicationBuilder"></param>
public TelegramBotHostBuilder(HostApplicationBuilder hostApplicationBuilder)
{
private readonly HostApplicationBuilder _innerBuilder;
private readonly HostApplicationBuilderSettings _settings;
internal IHandlersCollection _handlers = null!;
_innerBuilder = hostApplicationBuilder ?? throw new ArgumentNullException(nameof(hostApplicationBuilder));
}
/// <inheritdoc/>
public IHandlersCollection Handlers => _handlers;
/// <summary>
/// Initializes a new instance of the <see cref="TelegramBotHostBuilder"/> class.
/// </summary>
/// <param name="hostApplicationBuilder"></param>
/// <param name="handlers"></param>
public TelegramBotHostBuilder(HostApplicationBuilder hostApplicationBuilder, IHandlersCollection handlers)
{
_innerBuilder = hostApplicationBuilder ?? throw new ArgumentNullException(nameof(hostApplicationBuilder));
_handlers = handlers ?? throw new ArgumentNullException(nameof(handlers));
}
/// <inheritdoc/>
public IServiceCollection Services => _innerBuilder.Services;
/// <summary>
/// Builds the host.
/// </summary>
/// <returns></returns>
public TelegramBotHost Build()
{
TelegramBotHost host = new TelegramBotHost(_innerBuilder);
host.UseTelegrator();
return host;
}
/// <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, HostApplicationBuilderSettings? settings = null)
{
_innerBuilder = hostApplicationBuilder ?? throw new ArgumentNullException(nameof(hostApplicationBuilder));
_settings = settings ?? new HostApplicationBuilderSettings();
_innerBuilder.AddTelegrator();
_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, HostApplicationBuilderSettings? settings = null)
{
_innerBuilder = hostApplicationBuilder ?? throw new ArgumentNullException(nameof(hostApplicationBuilder));
_settings = settings ?? new HostApplicationBuilderSettings();
_innerBuilder.AddTelegrator(null, handlers);
_innerBuilder.Logging.ClearProviders();
}
/// <summary>
/// Builds the host.
/// </summary>
/// <returns></returns>
public TelegramBotHost Build()
{
TelegramBotHost host = new TelegramBotHost(_innerBuilder);
host.UseTelegrator();
return host;
}
/// <inheritdoc/>
public void ConfigureContainer<TContainerBuilder>(IServiceProviderFactory<TContainerBuilder> factory, Action<TContainerBuilder>? configure = null) where TContainerBuilder : notnull
{
_innerBuilder.ConfigureContainer(factory, configure);
}
}
@@ -1,45 +1,42 @@
using Microsoft.Extensions.Logging;
namespace Telegrator.Logging;
namespace Telegrator.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 Microsoft.Extensions.Logging.ILogger _logger;
/// <summary>
/// Adapter for Microsoft.Extensions.Logging to work with Telegrator logging system.
/// This allows seamless integration with ASP.NET Core logging infrastructure.
/// Initializes a new instance of MicrosoftLoggingAdapter.
/// </summary>
public class MicrosoftLoggingAdapter : ITelegratorLogger
/// <param name="logger">The Microsoft.Extensions.Logging logger instance.</param>
public MicrosoftLoggingAdapter(Microsoft.Extensions.Logging.ILogger logger)
{
private readonly ILogger _logger;
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
/// <summary>
/// Initializes a new instance of MicrosoftLoggingAdapter.
/// </summary>
/// <param name="logger">The Microsoft.Extensions.Logging logger instance.</param>
public MicrosoftLoggingAdapter(ILogger logger)
/// <inheritdoc/>
public void Log(LogLevel level, string message, Exception? exception = null)
{
var msLogLevel = level switch
{
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
LogLevel.Trace => Microsoft.Extensions.Logging.LogLevel.Trace,
LogLevel.Debug => Microsoft.Extensions.Logging.LogLevel.Debug,
LogLevel.Information => Microsoft.Extensions.Logging.LogLevel.Information,
LogLevel.Warning => Microsoft.Extensions.Logging.LogLevel.Warning,
LogLevel.Error => Microsoft.Extensions.Logging.LogLevel.Error,
_ => Microsoft.Extensions.Logging.LogLevel.Information
};
if (exception != null)
{
_logger.Log(msLogLevel, default, message, exception, (str, exc) => string.Format("{0} : {1}", str, exc));
}
/// <inheritdoc/>
public void Log(LogLevel level, string message, Exception? exception = null)
else
{
var msLogLevel = level switch
{
Telegrator.Logging.LogLevel.Trace => Microsoft.Extensions.Logging.LogLevel.Trace,
Telegrator.Logging.LogLevel.Debug => Microsoft.Extensions.Logging.LogLevel.Debug,
Telegrator.Logging.LogLevel.Information => Microsoft.Extensions.Logging.LogLevel.Information,
Telegrator.Logging.LogLevel.Warning => Microsoft.Extensions.Logging.LogLevel.Warning,
Telegrator.Logging.LogLevel.Error => Microsoft.Extensions.Logging.LogLevel.Error,
_ => Microsoft.Extensions.Logging.LogLevel.Information
};
if (exception != null)
{
_logger.Log(msLogLevel, default, message, exception, (str, exc) => string.Format("{0} : {1}", str, exc));
}
else
{
_logger.Log(msLogLevel, default, message, null, (str, _) => str);
}
_logger.Log(msLogLevel, default, message, null, (str, _) => str);
}
}
}
@@ -1,13 +0,0 @@
using Microsoft.Extensions.Options;
using Telegrator.Core;
using Telegrator.Mediation;
namespace Telegrator.Polling
{
/// <inheritdoc/>
public class HostUpdateHandlersPool(IUpdateRouter router, IOptions<TelegratorOptions> options)
: UpdateHandlersPool(router, options.Value, options.Value.GlobalCancellationToken)
{
}
}
@@ -4,56 +4,56 @@ using Telegram.Bot;
using Telegram.Bot.Polling;
using Telegram.Bot.Types;
using Telegrator.Core;
using Telegrator.Core.States;
using Telegrator.Mediation;
namespace Telegrator.Polling
namespace Telegrator.Polling;
/// <inheritdoc/>
public class HostUpdateRouter : UpdateRouter
{
/// <summary>
/// <see cref="ILogger"/> of this router
/// </summary>
protected readonly ILogger<HostUpdateRouter> Logger;
/// <inheritdoc/>
public class HostUpdateRouter : UpdateRouter
public HostUpdateRouter(
IHandlersProvider handlersProvider,
IAwaitingProvider awaitingProvider,
IStateStorage stateStorage,
IOptions<TelegratorOptions> options,
ITelegramBotInfo botInfo,
ILogger<HostUpdateRouter> logger) : base(handlersProvider, awaitingProvider, stateStorage, options.Value, botInfo)
{
/// <summary>
/// <see cref="ILogger"/> of this router
/// </summary>
protected readonly ILogger<HostUpdateRouter> Logger;
Logger = logger;
ExceptionHandler = new DefaultRouterExceptionHandler(HandleException);
}
/// <inheritdoc/>
public HostUpdateRouter(
IHandlersProvider handlersProvider,
IAwaitingProvider awaitingProvider,
IOptions<TelegratorOptions> options,
IUpdateHandlersPool handlersPool,
ITelegramBotInfo botInfo,
ILogger<HostUpdateRouter> logger) : base(handlersProvider, awaitingProvider, options.Value, handlersPool, botInfo)
/// <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 = logger;
ExceptionHandler = new DefaultRouterExceptionHandler(HandleException);
Logger.LogError("\"{handler}\" handler's execution was faulted :\n{exception}",
handlerFaultedException.HandlerInfo.ToString(),
handlerFaultedException.InnerException?.ToString() ?? "No inner exception");
return;
}
/// <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());
}
Logger.LogError("Exception was thrown during update routing faulted :\n{exception}", exception.ToString());
}
}
@@ -6,27 +6,26 @@ using Telegram.Bot.Polling;
using Telegrator.Core;
using Telegrator.Mediation;
namespace Telegrator.Polling
{
/// <summary>
/// Service for receiving updates for Hosted telegram bots
/// </summary>
/// <param name="botClient"></param>
/// <param name="updateRouter"></param>
/// <param name="options"></param>
/// <param name="logger"></param>
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;
namespace Telegrator.Polling;
/// <inheritdoc/>
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
logger.LogInformation("Starting receiving updates via long-polling");
_receiverOptions.AllowedUpdates = _updateRouter.HandlersProvider.AllowedTypes.ToArray();
DefaultUpdateReceiver updateReceiver = new DefaultUpdateReceiver(botClient, _receiverOptions);
await updateReceiver.ReceiveAsync(_updateRouter, stoppingToken).ConfigureAwait(false);
}
/// <summary>
/// Service for receiving updates for Hosted telegram bots
/// </summary>
/// <param name="botClient"></param>
/// <param name="updateRouter"></param>
/// <param name="options"></param>
/// <param name="logger"></param>
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;
/// <inheritdoc/>
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
logger.LogInformation("Starting receiving updates via long-polling");
_receiverOptions.AllowedUpdates = _updateRouter.HandlersProvider.AllowedTypes.ToArray();
DefaultUpdateReceiver updateReceiver = new DefaultUpdateReceiver(botClient, _receiverOptions);
await updateReceiver.ReceiveAsync(_updateRouter, stoppingToken).ConfigureAwait(false);
}
}
@@ -1,11 +1,9 @@
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.Extensions.Options;
namespace Telegrator.Providers
namespace Telegrator.Providers;
/// <inheritdoc/>
public class HostAwaitingProvider(IOptions<TelegratorOptions> options) : AwaitingProvider(options.Value)
{
/// <inheritdoc/>
public class HostAwaitingProvider(IOptions<TelegratorOptions> options, ILogger<HostAwaitingProvider> logger) : AwaitingProvider(options.Value)
{
private readonly ILogger<HostAwaitingProvider> _logger = logger;
}
}
@@ -2,61 +2,60 @@
using Telegrator.Core;
using Telegrator.Core.Descriptors;
namespace Telegrator.Providers
namespace Telegrator.Providers;
/// <inheritdoc/>
public class HostHandlersCollection(IServiceCollection hostServiceColletion, TelegratorOptions options) : HandlersCollection(options)
{
private readonly IServiceCollection Services = hostServiceColletion;
/// <inheritdoc/>
public class HostHandlersCollection(IServiceCollection hostServiceColletion, TelegratorOptions options) : HandlersCollection(options)
protected override bool MustHaveParameterlessCtor => false;
/// <inheritdoc/>
public override IHandlersCollection AddDescriptor(HandlerDescriptor descriptor)
{
private readonly IServiceCollection Services = hostServiceColletion;
/// <inheritdoc/>
protected override bool MustHaveParameterlessCtor => false;
/// <inheritdoc/>
public override IHandlersCollection AddDescriptor(HandlerDescriptor descriptor)
switch (descriptor.Type)
{
switch (descriptor.Type)
{
case DescriptorType.General:
{
if (descriptor.InstanceFactory != null)
Services.AddScoped(descriptor.HandlerType, _ => descriptor.InstanceFactory.Invoke());
else
Services.AddScoped(descriptor.HandlerType);
case DescriptorType.General:
{
if (descriptor.InstanceFactory != null)
Services.AddScoped(descriptor.HandlerType, _ => descriptor.InstanceFactory.Invoke());
else
Services.AddScoped(descriptor.HandlerType);
break;
}
break;
}
case DescriptorType.Keyed:
{
if (descriptor.InstanceFactory != null)
Services.AddKeyedScoped(descriptor.HandlerType, descriptor.ServiceKey, (_, _) => descriptor.InstanceFactory.Invoke());
else
Services.AddKeyedScoped(descriptor.HandlerType, descriptor.ServiceKey);
case DescriptorType.Keyed:
{
if (descriptor.InstanceFactory != null)
Services.AddKeyedScoped(descriptor.HandlerType, descriptor.ServiceKey, (_, _) => descriptor.InstanceFactory.Invoke());
else
Services.AddKeyedScoped(descriptor.HandlerType, descriptor.ServiceKey);
break;
}
break;
}
case DescriptorType.Singleton:
{
Services.AddSingleton(descriptor.HandlerType, descriptor.SingletonInstance ?? (descriptor.InstanceFactory != null
? descriptor.InstanceFactory.Invoke()
: throw new Exception()));
case DescriptorType.Singleton:
{
Services.AddSingleton(descriptor.HandlerType, descriptor.SingletonInstance ?? (descriptor.InstanceFactory != null
? descriptor.InstanceFactory.Invoke()
: throw new Exception()));
break;
}
break;
}
case DescriptorType.Implicit:
{
Services.AddKeyedSingleton(descriptor.HandlerType, descriptor.ServiceKey, descriptor.SingletonInstance ?? (descriptor.InstanceFactory != null
? descriptor.InstanceFactory.Invoke()
: throw new Exception()));
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);
break;
}
}
return base.AddDescriptor(descriptor);
}
}
@@ -1,45 +1,40 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Telegrator.Core;
using Telegrator.Core.Descriptors;
using Telegrator.Core.Handlers;
namespace Telegrator.Providers
namespace Telegrator.Providers;
/// <inheritdoc/>
public class HostHandlersProvider : HandlersProvider
{
private readonly IServiceProvider Services;
/// <inheritdoc/>
public class HostHandlersProvider : HandlersProvider
public HostHandlersProvider(
IHandlersCollection handlers,
IOptions<TelegratorOptions> options,
IServiceProvider serviceProvider) : base(handlers, options.Value)
{
private readonly IServiceProvider Services;
private readonly ILogger<HostHandlersProvider> Logger;
Services = serviceProvider;
}
/// <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();
/// <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);
object handlerInstance = descriptor.ServiceKey == null
? scope.ServiceProvider.GetRequiredService(descriptor.HandlerType)
: scope.ServiceProvider.GetRequiredKeyedService(descriptor.HandlerType, descriptor.ServiceKey);
if (handlerInstance is not UpdateHandlerBase updateHandler)
throw new InvalidOperationException("Failed to resolve " + descriptor.HandlerType + " as UpdateHandlerBase");
if (handlerInstance is not UpdateHandlerBase updateHandler)
throw new InvalidOperationException("Failed to resolve " + descriptor.HandlerType + " as UpdateHandlerBase");
descriptor.LazyInitialization?.Invoke(updateHandler);
updateHandler.LifetimeToken.OnLifetimeEnded += _ => scope.Dispose();
return updateHandler;
}
descriptor.LazyInitialization?.Invoke(updateHandler);
updateHandler.LifetimeToken.OnLifetimeEnded += _ => scope.Dispose();
return updateHandler;
}
}
@@ -15,7 +15,7 @@
<EnforceCodeStyleInBuild>True</EnforceCodeStyleInBuild>
<Title>Telegrator.Hosting</Title>
<Version>1.16.1</Version>
<Version>1.16.8</Version>
<Authors>Rikitav Tim4ik</Authors>
<Company>Rikitav Tim4ik</Company>
<RepositoryUrl>https://github.com/Rikitav/Telegrator</RepositoryUrl>
@@ -31,8 +31,8 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Hosting" Version="10.0.3" />
<PackageReference Include="Microsoft.Extensions.Http" Version="10.0.3" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="10.0.5" />
<PackageReference Include="Microsoft.Extensions.Http" Version="10.0.5" />
</ItemGroup>
<ItemGroup>
+61 -16
View File
@@ -4,6 +4,7 @@ using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using System.Diagnostics.CodeAnalysis;
using System.Text;
using Telegram.Bot;
using Telegram.Bot.Polling;
@@ -11,10 +12,12 @@ using Telegram.Bot.Types;
using Telegram.Bot.Types.Enums;
using Telegrator.Core;
using Telegrator.Core.Descriptors;
using Telegrator.Core.States;
using Telegrator.Hosting;
using Telegrator.Logging;
using Telegrator.Polling;
using Telegrator.Providers;
using Telegrator.States;
namespace Telegrator;
@@ -28,7 +31,7 @@ public static class HostBuilderExtensions
/// </summary>
public const string HandlersCollectionPropertyKey = nameof(IHandlersCollection);
extension (IHostApplicationBuilder builder)
extension (HostApplicationBuilder builder)
{
/// <summary>
/// Gets the <see cref="IHandlersCollection"/> from the builder properties.
@@ -37,22 +40,57 @@ public static class HostBuilderExtensions
{
get
{
if (builder is TelegramBotHostBuilder botHostBuilder)
return botHostBuilder.Handlers;
return (IHandlersCollection)builder.Properties[HandlersCollectionPropertyKey];
return (IHandlersCollection)((IHostApplicationBuilder)builder).Properties[HandlersCollectionPropertyKey];
}
}
}
/// <summary>
/// Replaces TelegramBotWebHostBuilder. Configures DI, options, and handlers.
/// Replaces TelegramBotHostBuilder. Configures DI, options, and handlers.
/// </summary>
public static ITelegramBotHostBuilder AddTelegrator(this ITelegramBotHostBuilder builder, TelegratorOptions? options = null, IHandlersCollection? handlers = null, Action<ITelegramBotHostBuilder>? action = null)
{
AddTelegratorInternal(builder.Services, builder.Configuration, builder.Properties, ref handlers, options);
if (builder is TelegramBotHostBuilder telegramBotHostBuilder)
telegramBotHostBuilder._handlers = handlers;
action?.Invoke(builder);
return builder;
}
/// <summary>
/// Replaces TelegramBotHostBuilder. Configures DI, options, and handlers.
/// </summary>
public static IHostApplicationBuilder AddTelegrator(this HostApplicationBuilder builder, TelegratorOptions? options = null, IHandlersCollection? handlers = null, Action<ITelegramBotHostBuilder>? action = null)
{
AddTelegratorInternal(builder.Services, builder.Configuration, ((IHostApplicationBuilder)builder).Properties, ref handlers, options);
action?.Invoke(new TelegramBotHostBuilder(builder, handlers));
return builder;
}
/// <summary>
/// Replaces TelegramBotHostBuilder. Configures DI, options, and handlers.
/// </summary>
public static IHostApplicationBuilder AddTelegrator(this IHostApplicationBuilder builder, TelegratorOptions? options = null, IHandlersCollection? handlers = null)
{
IServiceCollection services = builder.Services;
IConfigurationManager configuration = builder.Configuration;
AddTelegratorInternal(builder.Services, builder.Configuration, builder.Properties, ref handlers, options);
return builder;
}
/// <summary>
/// Replaces TelegramBotHostBuilder. Configures DI, options, and handlers.
/// </summary>
public static IHostBuilder AddTelegrator(this IHostBuilder builder, TelegratorOptions? options = null, IHandlersCollection? handlers = null)
{
builder.ConfigureServices((ctx, sp) => AddTelegratorInternal(sp, ctx.Configuration, builder.Properties, ref handlers, options));
return builder;
}
/// <summary>
/// Replaces TelegramBotHostBuilder. Configures DI, options, and handlers.
/// </summary>
internal static void AddTelegratorInternal(IServiceCollection services, IConfiguration configuration, IDictionary<object, object> properties, [NotNull] ref IHandlersCollection? handlers, TelegratorOptions? options = null)
{
if (options == null)
{
options = configuration.GetSection(nameof(TelegratorOptions)).Get<TelegratorOptions>();
@@ -77,10 +115,7 @@ public static class HostBuilderExtensions
handlers ??= new HostHandlersCollection(services, options);
services.AddSingleton(handlers);
builder.Properties.Add(HandlersCollectionPropertyKey, handlers);
if (builder is TelegramBotHostBuilder botHostBuilder)
botHostBuilder._handlers = handlers;
properties.Add(HandlersCollectionPropertyKey, handlers);
if (!services.Any(srvc => srvc.ImplementationType == typeof(IOptions<ReceiverOptions>)))
{
@@ -100,9 +135,8 @@ public static class HostBuilderExtensions
}));
}
services.AddTelegramReceiver();
services.AddTelegramBotHostDefaults();
return builder;
services.AddTelegramReceiver();
}
}
@@ -112,6 +146,18 @@ public static class HostBuilderExtensions
/// </summary>
public static class ServicesCollectionExtensions
{
/// <summary>
/// Registers <see cref="IStateStorage"/> service
/// </summary>
/// <typeparam name="TStorage"></typeparam>
/// <param name="services"></param>
/// <returns></returns>
public static IServiceCollection AddStateStorage<TStorage>(this IServiceCollection services) where TStorage : IStateStorage
{
services.Replace(new ServiceDescriptor(typeof(IStateStorage), typeof(TStorage), ServiceLifetime.Singleton));
return services;
}
/// <summary>
/// Registers <see cref="TelegramBotHost"/> default services
/// </summary>
@@ -119,12 +165,11 @@ public static class ServicesCollectionExtensions
/// <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>();
services.AddSingleton<IStateStorage, DefaultStateStorage>();
return services;
}
@@ -4,14 +4,13 @@ using System.Threading.Tasks;
using Telegram.Bot.Types;
using Telegrator.Handlers;
namespace Telegrator.Localized
namespace Telegrator.Localized;
public static class LocalizedMessageHandlerExtensions
{
public static class LocalizedMessageHandlerExtensions
public static async Task<Message> ResponseLocalized(this ILocalizedHandler<Message> localizedHandler, string localizedReplyIdentifier, params IEnumerable<string> formatArgs)
{
public static async Task<Message> ResponseLocalized(this ILocalizedHandler<Message> localizedHandler, string localizedReplyIdentifier, params IEnumerable<string> formatArgs)
{
LocalizedString localizedString = localizedHandler.LocalizationProvider[localizedReplyIdentifier, formatArgs];
return await localizedHandler.Container.Responce(localizedString.Value);
}
LocalizedString localizedString = localizedHandler.LocalizationProvider[localizedReplyIdentifier, formatArgs];
return await localizedHandler.Container.Responce(localizedString.Value);
}
}
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<TargetFramework>netstandard2.1</TargetFramework>
<LangVersion>latest</LangVersion>
</PropertyGroup>
@@ -4,41 +4,40 @@ using Telegrator.Attributes;
using Telegrator.Core.Filters;
using Telegrator.Filters;
namespace Telegrator.Annotations
namespace Telegrator.Annotations;
/// <summary>
/// Abstract base attribute for filtering callback-based updates.
/// Supports various message types including regular messages, edited messages, channel posts, and business messages.
/// </summary>
/// <param name="filters">The filters to apply to messages</param>
public abstract class CallbackQueryAttribute(params IFilter<CallbackQuery>[] filters) : UpdateFilterAttribute<CallbackQuery>(filters)
{
/// <summary>
/// Abstract base attribute for filtering callback-based updates.
/// Supports various message types including regular messages, edited messages, channel posts, and business messages.
/// Gets the allowed update types that this filter can process.
/// </summary>
/// <param name="filters">The filters to apply to messages</param>
public abstract class CallbackQueryAttribute(params IFilter<CallbackQuery>[] filters) : UpdateFilterAttribute<CallbackQuery>(filters)
{
/// <summary>
/// Gets the allowed update types that this filter can process.
/// </summary>
public override UpdateType[] AllowedTypes => [UpdateType.CallbackQuery];
/// <summary>
/// Extracts the message from various types of updates.
/// </summary>
/// <param name="update">The Telegram update</param>
/// <returns>The message from the update, or null if not present</returns>
public override CallbackQuery? GetFilterringTarget(Update update)
=> update.CallbackQuery;
}
public override UpdateType[] AllowedTypes => [UpdateType.CallbackQuery];
/// <summary>
/// Attribute for filtering <see cref="CallbackQuery"/>'s data
/// Extracts the message from various types of updates.
/// </summary>
/// <param name="data"></param>
public class CallbackDataAttribute(string data)
: CallbackQueryAttribute(new CallbackDataFilter(data))
{ }
/// <summary>
/// Attribute to check if <see cref="CallbackQuery"/> belongs to a specific message by its ID
/// </summary>
public class CallbackInlineIdAttribute(string inlineMessageId)
: CallbackQueryAttribute(new CallbackInlineIdFilter(inlineMessageId))
{ }
/// <param name="update">The Telegram update</param>
/// <returns>The message from the update, or null if not present</returns>
public override CallbackQuery? GetFilterringTarget(Update update)
=> update.CallbackQuery;
}
/// <summary>
/// Attribute for filtering <see cref="CallbackQuery"/>'s data
/// </summary>
/// <param name="data"></param>
public class CallbackDataAttribute(string data)
: CallbackQueryAttribute(new CallbackDataFilter(data))
{ }
/// <summary>
/// Attribute to check if <see cref="CallbackQuery"/> belongs to a specific message by its ID
/// </summary>
public class CallbackInlineIdAttribute(string inlineMessageId)
: CallbackQueryAttribute(new CallbackInlineIdFilter(inlineMessageId))
{ }
@@ -3,58 +3,57 @@ using Telegram.Bot.Types.Enums;
using Telegrator.Filters;
using Telegrator.Attributes;
namespace Telegrator.Annotations
namespace Telegrator.Annotations;
/// <summary>
/// Attribute for filtering messages based on command aliases.
/// Allows handlers to respond to multiple command variations using a single attribute.
/// </summary>
public class CommandAlliasAttribute : UpdateFilterAttribute<Message>
{
/// <summary>
/// Attribute for filtering messages based on command aliases.
/// Allows handlers to respond to multiple command variations using a single attribute.
/// Gets the allowed update types for this filter.
/// </summary>
public class CommandAlliasAttribute : UpdateFilterAttribute<Message>
public override UpdateType[] AllowedTypes => [UpdateType.Message];
/// <summary>
/// The description of the command (defaults to "no description provided").
/// </summary>
private string _description = "no description provided";
/// <summary>
/// Gets the array of command aliases that this filter will match.
/// </summary>
public string[] Alliases
{
/// <summary>
/// Gets the allowed update types for this filter.
/// </summary>
public override UpdateType[] AllowedTypes => [UpdateType.Message];
/// <summary>
/// The description of the command (defaults to "no description provided").
/// </summary>
private string _description = "no description provided";
/// <summary>
/// Gets the array of command aliases that this filter will match.
/// </summary>
public string[] Alliases
{
get;
private set;
}
/// <summary>
/// Gets or sets the description of the command.
/// Must be between 0 and 256 characters in length.
/// </summary>
/// <exception cref="ArgumentOutOfRangeException">Thrown when the description length is outside the allowed range.</exception>
public string Description
{
get => _description;
set => _description = value is { Length: <= 256 and >= 0 }
? value : throw new ArgumentOutOfRangeException(nameof(value));
}
/// <summary>
/// Initializes a new instance of the CommandAlliasAttribute with the specified command aliases.
/// </summary>
/// <param name="alliases">The command aliases to match against.</param>
public CommandAlliasAttribute(params string[] alliases)
: base(new CommandAlliasFilter(alliases.Select(c => c.TrimStart('/')).ToArray()))
=> Alliases = alliases.Select(c => c.TrimStart('/')).ToArray();
/// <summary>
/// Gets the filtering target (Message) from the update.
/// </summary>
/// <param name="update">The Telegram update.</param>
/// <returns>The message from the update, or null if not present.</returns>
public override Message? GetFilterringTarget(Update update) => update.Message;
get;
private set;
}
/// <summary>
/// Gets or sets the description of the command.
/// Must be between 0 and 256 characters in length.
/// </summary>
/// <exception cref="ArgumentOutOfRangeException">Thrown when the description length is outside the allowed range.</exception>
public string Description
{
get => _description;
set => _description = value is { Length: <= 256 and >= 0 }
? value : throw new ArgumentOutOfRangeException(nameof(value));
}
/// <summary>
/// Initializes a new instance of the CommandAlliasAttribute with the specified command aliases.
/// </summary>
/// <param name="alliases">The command aliases to match against.</param>
public CommandAlliasAttribute(params string[] alliases)
: base(new CommandAlliasFilter(alliases.Select(c => c.TrimStart('/')).ToArray()))
=> Alliases = alliases.Select(c => c.TrimStart('/')).ToArray();
/// <summary>
/// Gets the filtering target (Message) from the update.
/// </summary>
/// <param name="update">The Telegram update.</param>
/// <returns>The message from the update, or null if not present.</returns>
public override Message? GetFilterringTarget(Update update) => update.Message;
}
@@ -1,63 +1,62 @@
using System.Text.RegularExpressions;
using Telegrator.Filters;
namespace Telegrator.Annotations
{
/// <summary>
/// Attribute for filtering messages where a command has arguments count >= <paramref name="count"/>.
/// </summary>
/// <param name="count"></param>
public class ArgumentCountAttribute(int count)
: MessageFilterAttribute(new ArgumentCountFilter(count))
{ }
namespace Telegrator.Annotations;
/// <summary>
/// Attribute for filtering messages where a command argument starts with the specified content.
/// </summary>
/// <param name="content">The content that the command argument should start with.</param>
/// <param name="comparison">The string comparison type to use for the check.</param>
/// <param name="index">The index of the argument to check (0-based).</param>
public class ArgumentStartsWithAttribute(string content, StringComparison comparison = StringComparison.InvariantCulture, int index = 0)
: MessageFilterAttribute(new ArgumentStartsWithFilter(content, comparison, index))
{ }
/// <summary>
/// Attribute for filtering messages where a command has arguments count >= <paramref name="count"/>.
/// </summary>
/// <param name="count"></param>
public class ArgumentCountAttribute(int count)
: MessageFilterAttribute(new ArgumentCountFilter(count))
{ }
/// <summary>
/// Attribute for filtering messages where a command argument ends with the specified content.
/// </summary>
/// <param name="content">The content that the command argument should end with.</param>
/// <param name="comparison">The string comparison type to use for the check.</param>
/// <param name="index">The index of the argument to check (0-based).</param>
public class ArgumentEndsWithAttribute(string content, StringComparison comparison = StringComparison.InvariantCulture, int index = 0)
: MessageFilterAttribute(new ArgumentEndsWithFilter(content, comparison, index))
{ }
/// <summary>
/// Attribute for filtering messages where a command argument starts with the specified content.
/// </summary>
/// <param name="content">The content that the command argument should start with.</param>
/// <param name="comparison">The string comparison type to use for the check.</param>
/// <param name="index">The index of the argument to check (0-based).</param>
public class ArgumentStartsWithAttribute(string content, StringComparison comparison = StringComparison.InvariantCulture, int index = 0)
: MessageFilterAttribute(new ArgumentStartsWithFilter(content, comparison, index))
{ }
/// <summary>
/// Attribute for filtering messages where a command argument contains the specified content.
/// </summary>
/// <param name="content">The content that the command argument should contain.</param>
/// <param name="comparison">The string comparison type to use for the check.</param>
/// <param name="index">The index of the argument to check (0-based).</param>
public class ArgumentContainsAttribute(string content, StringComparison comparison = StringComparison.InvariantCulture, int index = 0)
: MessageFilterAttribute(new ArgumentContainsFilter(content, comparison, index))
{ }
/// <summary>
/// Attribute for filtering messages where a command argument ends with the specified content.
/// </summary>
/// <param name="content">The content that the command argument should end with.</param>
/// <param name="comparison">The string comparison type to use for the check.</param>
/// <param name="index">The index of the argument to check (0-based).</param>
public class ArgumentEndsWithAttribute(string content, StringComparison comparison = StringComparison.InvariantCulture, int index = 0)
: MessageFilterAttribute(new ArgumentEndsWithFilter(content, comparison, index))
{ }
/// <summary>
/// Attribute for filtering messages where a command argument equals the specified content.
/// </summary>
/// <param name="content">The content that the command argument should equal.</param>
/// <param name="comparison">The string comparison type to use for the check.</param>
/// <param name="index">The index of the argument to check (0-based).</param>
public class ArgumentEqualsAttribute(string content, StringComparison comparison = StringComparison.InvariantCulture, int index = 0)
: MessageFilterAttribute(new ArgumentEqualsFilter(content, comparison, index))
{ }
/// <summary>
/// Attribute for filtering messages where a command argument contains the specified content.
/// </summary>
/// <param name="content">The content that the command argument should contain.</param>
/// <param name="comparison">The string comparison type to use for the check.</param>
/// <param name="index">The index of the argument to check (0-based).</param>
public class ArgumentContainsAttribute(string content, StringComparison comparison = StringComparison.InvariantCulture, int index = 0)
: MessageFilterAttribute(new ArgumentContainsFilter(content, comparison, index))
{ }
/// <summary>
/// Attribute for filtering messages where a command argument matches a regular expression pattern.
/// </summary>
/// <param name="pattern">The regular expression pattern to match against the command argument.</param>
/// <param name="options">The regex options to use for the pattern matching.</param>
/// <param name="index">The index of the argument to check (0-based).</param>
public class ArgumentRegexAttribute(string pattern, RegexOptions options = RegexOptions.None, int index = 0)
: MessageFilterAttribute(new ArgumentRegexFilter(pattern, options, index: index))
{ }
}
/// <summary>
/// Attribute for filtering messages where a command argument equals the specified content.
/// </summary>
/// <param name="content">The content that the command argument should equal.</param>
/// <param name="comparison">The string comparison type to use for the check.</param>
/// <param name="index">The index of the argument to check (0-based).</param>
public class ArgumentEqualsAttribute(string content, StringComparison comparison = StringComparison.InvariantCulture, int index = 0)
: MessageFilterAttribute(new ArgumentEqualsFilter(content, comparison, index))
{ }
/// <summary>
/// Attribute for filtering messages where a command argument matches a regular expression pattern.
/// </summary>
/// <param name="pattern">The regular expression pattern to match against the command argument.</param>
/// <param name="options">The regex options to use for the pattern matching.</param>
/// <param name="index">The index of the argument to check (0-based).</param>
public class ArgumentRegexAttribute(string pattern, RegexOptions options = RegexOptions.None, int index = 0)
: MessageFilterAttribute(new ArgumentRegexFilter(pattern, options, index: index))
{ }
@@ -1,12 +1,11 @@
namespace Telegrator.Annotations
{
/// <summary>
/// Attribute that prevents a class from being automatically collected by the handler collection system.
/// When applied to a class, it will be excluded from domain-wide handler collection operations.
/// </summary>
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)]
public class DontCollectAttribute : Attribute
{
namespace Telegrator.Annotations;
/// <summary>
/// Attribute that prevents a class from being automatically collected by the handler collection system.
/// When applied to a class, it will be excluded from domain-wide handler collection operations.
/// </summary>
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)]
public class DontCollectAttribute : Attribute
{
}
}
@@ -4,82 +4,81 @@ using Telegrator.Filters;
using Telegrator.Attributes;
using Telegrator.Core.Filters;
namespace Telegrator.Annotations
namespace Telegrator.Annotations;
/// <summary>
/// Abstract base attribute for filtering updates based on environment conditions.
/// Can process all types of updates and provides environment-specific filtering logic.
/// </summary>
/// <param name="filters">The environment filters to apply</param>
public abstract class EnvironmentFilterAttribute(params IFilter<Update>[] filters) : UpdateFilterAttribute<Update>(filters)
{
/// <summary>
/// Abstract base attribute for filtering updates based on environment conditions.
/// Can process all types of updates and provides environment-specific filtering logic.
/// Gets the allowed update types that this filter can process.
/// Environment filters can process all update types.
/// </summary>
/// <param name="filters">The environment filters to apply</param>
public abstract class EnvironmentFilterAttribute(params IFilter<Update>[] filters) : UpdateFilterAttribute<Update>(filters)
{
/// <summary>
/// Gets the allowed update types that this filter can process.
/// Environment filters can process all update types.
/// </summary>
public override UpdateType[] AllowedTypes => Update.AllTypes;
/// <summary>
/// Gets the update as the filtering target.
/// Environment filters work with the entire update object.
/// </summary>
/// <param name="update">The Telegram update</param>
/// <returns>The update object itself</returns>
public override Update? GetFilterringTarget(Update update)
=> update;
}
public override UpdateType[] AllowedTypes => Update.AllTypes;
/// <summary>
/// Attribute for filtering updates that occur in debug environment.
/// Only allows updates when the application is running in debug mode.
/// Gets the update as the filtering target.
/// Environment filters work with the entire update object.
/// </summary>
public class IsDebugEnvironmentAttribute()
: EnvironmentFilterAttribute(new IsDebugEnvironmentFilter())
{ }
/// <summary>
/// Attribute for filtering updates that occur in release environment.
/// Only allows updates when the application is running in release mode.
/// </summary>
public class IsReleaseEnvironmentAttribute()
: EnvironmentFilterAttribute(new IsReleaseEnvironmentFilter())
{ }
/// <summary>
/// Attribute for filtering updates based on environment variable values.
/// </summary>
public class EnvironmentVariableAttribute : EnvironmentFilterAttribute
{
/// <summary>
/// Initializes the attribute to filter based on an environment variable with a specific value and comparison method.
/// </summary>
/// <param name="variable">The name of the environment variable</param>
/// <param name="value">The expected value of the environment variable</param>
/// <param name="comparison">The string comparison method</param>
public EnvironmentVariableAttribute(string variable, string? value, StringComparison comparison)
: base(new EnvironmentVariableFilter(variable, value, comparison)) { }
/// <summary>
/// Initializes the attribute to filter based on an environment variable with a specific value.
/// </summary>
/// <param name="variable">The name of the environment variable</param>
/// <param name="value">The expected value of the environment variable</param>
public EnvironmentVariableAttribute(string variable, string? value)
: base(new EnvironmentVariableFilter(variable, value)) { }
/// <summary>
/// Initializes the attribute to filter based on the existence of an environment variable.
/// </summary>
/// <param name="variable">The name of the environment variable</param>
public EnvironmentVariableAttribute(string variable)
: base(new EnvironmentVariableFilter(variable)) { }
/// <summary>
/// Initializes the attribute to filter based on an environment variable with a specific comparison method.
/// </summary>
/// <param name="variable">The name of the environment variable</param>
/// <param name="comparison">The string comparison method</param>
public EnvironmentVariableAttribute(string variable, StringComparison comparison)
: base(new EnvironmentVariableFilter(variable, comparison)) { }
}
/// <param name="update">The Telegram update</param>
/// <returns>The update object itself</returns>
public override Update? GetFilterringTarget(Update update)
=> update;
}
/// <summary>
/// Attribute for filtering updates that occur in debug environment.
/// Only allows updates when the application is running in debug mode.
/// </summary>
public class IsDebugEnvironmentAttribute()
: EnvironmentFilterAttribute(new IsDebugEnvironmentFilter())
{ }
/// <summary>
/// Attribute for filtering updates that occur in release environment.
/// Only allows updates when the application is running in release mode.
/// </summary>
public class IsReleaseEnvironmentAttribute()
: EnvironmentFilterAttribute(new IsReleaseEnvironmentFilter())
{ }
/// <summary>
/// Attribute for filtering updates based on environment variable values.
/// </summary>
public class EnvironmentVariableAttribute : EnvironmentFilterAttribute
{
/// <summary>
/// Initializes the attribute to filter based on an environment variable with a specific value and comparison method.
/// </summary>
/// <param name="variable">The name of the environment variable</param>
/// <param name="value">The expected value of the environment variable</param>
/// <param name="comparison">The string comparison method</param>
public EnvironmentVariableAttribute(string variable, string? value, StringComparison comparison)
: base(new EnvironmentVariableFilter(variable, value, comparison)) { }
/// <summary>
/// Initializes the attribute to filter based on an environment variable with a specific value.
/// </summary>
/// <param name="variable">The name of the environment variable</param>
/// <param name="value">The expected value of the environment variable</param>
public EnvironmentVariableAttribute(string variable, string? value)
: base(new EnvironmentVariableFilter(variable, value)) { }
/// <summary>
/// Initializes the attribute to filter based on the existence of an environment variable.
/// </summary>
/// <param name="variable">The name of the environment variable</param>
public EnvironmentVariableAttribute(string variable)
: base(new EnvironmentVariableFilter(variable)) { }
/// <summary>
/// Initializes the attribute to filter based on an environment variable with a specific comparison method.
/// </summary>
/// <param name="variable">The name of the environment variable</param>
/// <param name="comparison">The string comparison method</param>
public EnvironmentVariableAttribute(string variable, StringComparison comparison)
: base(new EnvironmentVariableFilter(variable, comparison)) { }
}
@@ -1,40 +1,39 @@
using Telegram.Bot.Types.Enums;
using Telegrator.Filters;
namespace Telegrator.Annotations
namespace Telegrator.Annotations;
/// <summary>
/// Attribute for filtering messages that contain mentions.
/// Allows handlers to respond only to messages that mention the bot or specific users.
/// </summary>
public class MentionedAttribute : MessageFilterAttribute
{
/// <summary>
/// Attribute for filtering messages that contain mentions.
/// Allows handlers to respond only to messages that mention the bot or specific users.
/// Initializes a new instance of the MentionedAttribute that matches any mention.
/// </summary>
public class MentionedAttribute : MessageFilterAttribute
{
/// <summary>
/// Initializes a new instance of the MentionedAttribute that matches any mention.
/// </summary>
public MentionedAttribute()
: base(new MessageHasEntityFilter(MessageEntityType.Mention, null, null), new MentionedFilter()) { }
public MentionedAttribute()
: base(new MessageHasEntityFilter(MessageEntityType.Mention, null, null), new MentionedFilter()) { }
/// <summary>
/// Initializes a new instance of the MentionedAttribute that matches mentions at a specific offset.
/// </summary>
/// <param name="offset">The offset position where the mention should occur.</param>
public MentionedAttribute(int offset)
: base(new MessageHasEntityFilter(MessageEntityType.Mention, offset, null), new MentionedFilter()) { }
/// <summary>
/// Initializes a new instance of the MentionedAttribute that matches mentions at a specific offset.
/// </summary>
/// <param name="offset">The offset position where the mention should occur.</param>
public MentionedAttribute(int offset)
: base(new MessageHasEntityFilter(MessageEntityType.Mention, offset, null), new MentionedFilter()) { }
/// <summary>
/// Initializes a new instance of the MentionedAttribute that matches a specific mention.
/// </summary>
/// <param name="mention">The specific mention text to match.</param>
public MentionedAttribute(string mention)
: base(new MessageHasEntityFilter(MessageEntityType.Mention), new MentionedFilter(mention)) { }
/// <summary>
/// Initializes a new instance of the MentionedAttribute that matches a specific mention.
/// </summary>
/// <param name="mention">The specific mention text to match.</param>
public MentionedAttribute(string mention)
: base(new MessageHasEntityFilter(MessageEntityType.Mention), new MentionedFilter(mention)) { }
/// <summary>
/// Initializes a new instance of the MentionedAttribute that matches a specific mention at a specific offset.
/// </summary>
/// <param name="mention">The specific mention text to match.</param>
/// <param name="offset">The offset position where the mention should occur.</param>
public MentionedAttribute(string mention, int offset)
: base(new MessageHasEntityFilter(MessageEntityType.Mention, offset, null), new MentionedFilter(mention)) { }
}
/// <summary>
/// Initializes a new instance of the MentionedAttribute that matches a specific mention at a specific offset.
/// </summary>
/// <param name="mention">The specific mention text to match.</param>
/// <param name="offset">The offset position where the mention should occur.</param>
public MentionedAttribute(string mention, int offset)
: base(new MessageHasEntityFilter(MessageEntityType.Mention, offset, null), new MentionedFilter(mention)) { }
}
@@ -1,105 +1,104 @@
using Telegram.Bot.Types.Enums;
using Telegrator.Filters;
namespace Telegrator.Annotations
namespace Telegrator.Annotations;
/// <summary>
/// Attribute for filtering messages sent in forum chats.
/// </summary>
public class ChatIsForumAttribute()
: MessageFilterAttribute(new MessageChatIsForumFilter())
{ }
/// <summary>
/// Attribute for filtering messages sent in a specific chat by ID.
/// </summary>
/// <param name="id">The chat ID to match</param>
public class ChatIdAttribute(long id)
: MessageFilterAttribute(new MessageChatIdFilter(id))
{ }
/// <summary>
/// Attribute for filtering messages sent in chats of a specific type.
/// </summary>
public class ChatTypeAttribute : MessageFilterAttribute
{
/// <summary>
/// Attribute for filtering messages sent in forum chats.
/// Initialize new instance of <see cref="ChatTypeAttribute"/> to filter messages from chat from specific chats
/// </summary>
public class ChatIsForumAttribute()
: MessageFilterAttribute(new MessageChatIsForumFilter())
{ }
/// <param name="type"></param>
public ChatTypeAttribute(ChatType type)
: base(new MessageChatTypeFilter(type)) { }
/// <summary>
/// Attribute for filtering messages sent in a specific chat by ID.
/// Initialize new instance of <see cref="ChatTypeAttribute"/> to filter messages from chat from specific chats (with flags)
/// </summary>
/// <param name="id">The chat ID to match</param>
public class ChatIdAttribute(long id)
: MessageFilterAttribute(new MessageChatIdFilter(id))
{ }
/// <summary>
/// Attribute for filtering messages sent in chats of a specific type.
/// </summary>
public class ChatTypeAttribute : MessageFilterAttribute
{
/// <summary>
/// Initialize new instance of <see cref="ChatTypeAttribute"/> to filter messages from chat from specific chats
/// </summary>
/// <param name="type"></param>
public ChatTypeAttribute(ChatType type)
: base(new MessageChatTypeFilter(type)) { }
/// <summary>
/// Initialize new instance of <see cref="ChatTypeAttribute"/> to filter messages from chat from specific chats (with flags)
/// </summary>
/// <param name="flags"></param>
public ChatTypeAttribute(ChatTypeFlags flags)
: base(new MessageChatTypeFilter(flags)) { }
}
/// <summary>
/// Attribute for filtering messages based on the chat title.
/// </summary>
public class ChatTitleAttribute : MessageFilterAttribute
{
/// <summary>
/// Initializes the attribute to filter messages from chats with a specific title and comparison method.
/// </summary>
/// <param name="title">The chat title to match</param>
/// <param name="comparison">The string comparison method</param>
public ChatTitleAttribute(string? title, StringComparison comparison)
: base(new MessageChatTitleFilter(title, comparison)) { }
/// <summary>
/// Initializes the attribute to filter messages from chats with a specific title.
/// </summary>
/// <param name="title">The chat title to match</param>
public ChatTitleAttribute(string? title)
: base(new MessageChatTitleFilter(title)) { }
}
/// <summary>
/// Attribute for filtering messages based on the chat username.
/// </summary>
public class ChatUsernameAttribute : MessageFilterAttribute
{
/// <summary>
/// Initializes the attribute to filter messages from chats with a specific username and comparison method.
/// </summary>
/// <param name="userName">The chat username to match</param>
/// <param name="comparison">The string comparison method</param>
public ChatUsernameAttribute(string? userName, StringComparison comparison)
: base(new MessageChatUsernameFilter(userName, comparison)) { }
/// <summary>
/// Initializes the attribute to filter messages from chats with a specific username.
/// </summary>
/// <param name="userName">The chat username to match</param>
public ChatUsernameAttribute(string? userName)
: base(new MessageChatUsernameFilter(userName, StringComparison.InvariantCulture)) { }
}
/// <summary>
/// Attribute for filtering messages based on the chat name (first name and optionally last name).
/// </summary>
public class ChatNameAttribute : MessageFilterAttribute
{
/// <summary>
/// Initializes the attribute to filter messages from chats with specific first and last names.
/// </summary>
/// <param name="firstName">The first name to match</param>
/// <param name="lastName">The last name to match (optional)</param>
/// <param name="comparison">The string comparison method</param>
public ChatNameAttribute(string? firstName, string? lastName, StringComparison comparison)
: base(new MessageChatNameFilter(firstName, lastName, comparison)) { }
/// <summary>
/// Initializes the attribute to filter messages from chats with specific first and last names.
/// </summary>
/// <param name="firstName">The first name to match</param>
/// <param name="lastName">The last name to match (optional)</param>
public ChatNameAttribute(string? firstName, string? lastName)
: base(new MessageChatNameFilter(firstName, lastName)) { }
}
/// <param name="flags"></param>
public ChatTypeAttribute(ChatTypeFlags flags)
: base(new MessageChatTypeFilter(flags)) { }
}
/// <summary>
/// Attribute for filtering messages based on the chat title.
/// </summary>
public class ChatTitleAttribute : MessageFilterAttribute
{
/// <summary>
/// Initializes the attribute to filter messages from chats with a specific title and comparison method.
/// </summary>
/// <param name="title">The chat title to match</param>
/// <param name="comparison">The string comparison method</param>
public ChatTitleAttribute(string? title, StringComparison comparison)
: base(new MessageChatTitleFilter(title, comparison)) { }
/// <summary>
/// Initializes the attribute to filter messages from chats with a specific title.
/// </summary>
/// <param name="title">The chat title to match</param>
public ChatTitleAttribute(string? title)
: base(new MessageChatTitleFilter(title)) { }
}
/// <summary>
/// Attribute for filtering messages based on the chat username.
/// </summary>
public class ChatUsernameAttribute : MessageFilterAttribute
{
/// <summary>
/// Initializes the attribute to filter messages from chats with a specific username and comparison method.
/// </summary>
/// <param name="userName">The chat username to match</param>
/// <param name="comparison">The string comparison method</param>
public ChatUsernameAttribute(string? userName, StringComparison comparison)
: base(new MessageChatUsernameFilter(userName, comparison)) { }
/// <summary>
/// Initializes the attribute to filter messages from chats with a specific username.
/// </summary>
/// <param name="userName">The chat username to match</param>
public ChatUsernameAttribute(string? userName)
: base(new MessageChatUsernameFilter(userName, StringComparison.InvariantCulture)) { }
}
/// <summary>
/// Attribute for filtering messages based on the chat name (first name and optionally last name).
/// </summary>
public class ChatNameAttribute : MessageFilterAttribute
{
/// <summary>
/// Initializes the attribute to filter messages from chats with specific first and last names.
/// </summary>
/// <param name="firstName">The first name to match</param>
/// <param name="lastName">The last name to match (optional)</param>
/// <param name="comparison">The string comparison method</param>
public ChatNameAttribute(string? firstName, string? lastName, StringComparison comparison)
: base(new MessageChatNameFilter(firstName, lastName, comparison)) { }
/// <summary>
/// Initializes the attribute to filter messages from chats with specific first and last names.
/// </summary>
/// <param name="firstName">The first name to match</param>
/// <param name="lastName">The last name to match (optional)</param>
public ChatNameAttribute(string? firstName, string? lastName)
: base(new MessageChatNameFilter(firstName, lastName)) { }
}
@@ -5,157 +5,156 @@ using Telegrator.Filters;
using Telegrator.Attributes;
using Telegrator.Core.Filters;
namespace Telegrator.Annotations
namespace Telegrator.Annotations;
/// <summary>
/// Abstract base attribute for filtering message-based updates.
/// Supports various message types including regular messages, edited messages, channel posts, and business messages.
/// </summary>
/// <param name="filters">The filters to apply to messages</param>
public abstract class MessageFilterAttribute(params IFilter<Message>[] filters) : UpdateFilterAttribute<Message>(filters)
{
/// <summary>
/// Abstract base attribute for filtering message-based updates.
/// Supports various message types including regular messages, edited messages, channel posts, and business messages.
/// Gets the allowed update types that this filter can process.
/// </summary>
/// <param name="filters">The filters to apply to messages</param>
public abstract class MessageFilterAttribute(params IFilter<Message>[] filters) : UpdateFilterAttribute<Message>(filters)
{
/// <summary>
/// Gets the allowed update types that this filter can process.
/// </summary>
public override UpdateType[] AllowedTypes =>
[
UpdateType.Message,
UpdateType.EditedMessage,
UpdateType.ChannelPost,
UpdateType.EditedChannelPost,
UpdateType.BusinessMessage,
UpdateType.EditedBusinessMessage
];
public override UpdateType[] AllowedTypes =>
[
UpdateType.Message,
UpdateType.EditedMessage,
UpdateType.ChannelPost,
UpdateType.EditedChannelPost,
UpdateType.BusinessMessage,
UpdateType.EditedBusinessMessage
];
/// <summary>
/// Extracts the message from various types of updates.
/// </summary>
/// <param name="update">The Telegram update</param>
/// <returns>The message from the update, or null if not present</returns>
public override Message? GetFilterringTarget(Update update)
/// <summary>
/// Extracts the message from various types of updates.
/// </summary>
/// <param name="update">The Telegram update</param>
/// <returns>The message from the update, or null if not present</returns>
public override Message? GetFilterringTarget(Update update)
{
return update switch
{
return update switch
{
{ Message: { } message } => message,
{ EditedMessage: { } message } => message,
{ ChannelPost: { } message } => message,
{ EditedChannelPost: { } message } => message,
{ BusinessMessage: { } message } => message,
{ EditedBusinessMessage: { } message } => message,
_ => null
};
}
}
/// <summary>
/// Attribute for filtering messages based on regular expression patterns.
/// </summary>
public class MessageRegexAttribute : MessageFilterAttribute
{
/// <summary>
/// Initializes the attribute with a regex pattern and options.
/// </summary>
/// <param name="pattern">The regular expression pattern to match</param>
/// <param name="regexOptions">The regex options for matching</param>
public MessageRegexAttribute(string pattern, RegexOptions regexOptions = default)
: base(new MessageRegexFilter(pattern, regexOptions)) { }
/// <summary>
/// Initializes the attribute with a precompiled regex.
/// </summary>
/// <param name="regex">The precompiled regular expression</param>
public MessageRegexAttribute(Regex regex)
: base(new MessageRegexFilter(regex)) { }
}
/// <summary>
/// Attribute for filtering messages that contain dice throws with specific values.
/// </summary>
public class DiceThrowedAttribute : MessageFilterAttribute
{
/// <summary>
/// Initializes the attribute to filter dice throws with a specific value.
/// </summary>
/// <param name="value">The dice value to match</param>
public DiceThrowedAttribute(int value)
: base(new DiceThrowedFilter(value)) { }
/// <summary>
/// Initializes the attribute to filter dice throws with a specific type and value.
/// </summary>
/// <param name="diceType">The type of dice</param>
/// <param name="value">The dice value to match</param>
public DiceThrowedAttribute(DiceType diceType, int value)
: base(new DiceThrowedFilter(diceType, value)) { }
}
/// <summary>
/// Attribute for filtering messages that are automatically forwarded.
/// </summary>
public class IsAutomaticFormwardMessageAttribute()
: MessageFilterAttribute(new IsAutomaticFormwardMessageFilter())
{ }
/// <summary>
/// Attribute for filtering messages sent while the user was offline.
/// </summary>
public class IsFromOfflineMessageAttribute()
: MessageFilterAttribute(new IsFromOfflineMessageFilter())
{ }
/// <summary>
/// Attribute for filtering service messages (e.g., user joined, left, etc.).
/// </summary>
public class IsServiceMessageMessageAttribute()
: MessageFilterAttribute(new IsServiceMessageMessageFilter())
{ }
/// <summary>
/// Attribute for filtering topic messages in forum chats.
/// </summary>
public class IsTopicMessageMessageAttribute()
: MessageFilterAttribute(new IsServiceMessageMessageFilter())
{ }
/// <summary>
/// Attribute for filtering messages based on their entities (mentions, links, etc.).
/// </summary>
public class MessageHasEntityAttribute : MessageFilterAttribute
{
/// <summary>
/// Initializes the attribute to filter messages with a specific entity type.
/// </summary>
/// <param name="type">The entity type to match</param>
public MessageHasEntityAttribute(MessageEntityType type)
: base(new MessageHasEntityFilter(type)) { }
/// <summary>
/// Initializes the attribute to filter messages with a specific entity type at a specific position.
/// </summary>
/// <param name="type">The entity type to match</param>
/// <param name="offset">The starting position of the entity</param>
/// <param name="length">The length of the entity (optional)</param>
public MessageHasEntityAttribute(MessageEntityType type, int offset, int? length)
: base(new MessageHasEntityFilter(type, offset, length)) { }
/// <summary>
/// Initializes the attribute to filter messages with a specific entity type and content.
/// </summary>
/// <param name="type">The entity type to match</param>
/// <param name="content">The content that the entity should contain</param>
/// <param name="stringComparison">The string comparison method</param>
public MessageHasEntityAttribute(MessageEntityType type, string content, StringComparison stringComparison = StringComparison.CurrentCulture)
: base(new MessageHasEntityFilter(type, content, stringComparison)) { }
/// <summary>
/// Initializes the attribute to filter messages with a specific entity type, position, and content.
/// </summary>
/// <param name="type">The entity type to match</param>
/// <param name="offset">The starting position of the entity</param>
/// <param name="length">The length of the entity (optional)</param>
/// <param name="content">The content that the entity should contain</param>
/// <param name="stringComparison">The string comparison method</param>
public MessageHasEntityAttribute(MessageEntityType type, int offset, int? length, string content, StringComparison stringComparison = StringComparison.CurrentCulture)
: base(new MessageHasEntityFilter(type, offset, length, content, stringComparison)) { }
{ Message: { } message } => message,
{ EditedMessage: { } message } => message,
{ ChannelPost: { } message } => message,
{ EditedChannelPost: { } message } => message,
{ BusinessMessage: { } message } => message,
{ EditedBusinessMessage: { } message } => message,
_ => null
};
}
}
/// <summary>
/// Attribute for filtering messages based on regular expression patterns.
/// </summary>
public class MessageRegexAttribute : MessageFilterAttribute
{
/// <summary>
/// Initializes the attribute with a regex pattern and options.
/// </summary>
/// <param name="pattern">The regular expression pattern to match</param>
/// <param name="regexOptions">The regex options for matching</param>
public MessageRegexAttribute(string pattern, RegexOptions regexOptions = default)
: base(new MessageRegexFilter(pattern, regexOptions)) { }
/// <summary>
/// Initializes the attribute with a precompiled regex.
/// </summary>
/// <param name="regex">The precompiled regular expression</param>
public MessageRegexAttribute(Regex regex)
: base(new MessageRegexFilter(regex)) { }
}
/// <summary>
/// Attribute for filtering messages that contain dice throws with specific values.
/// </summary>
public class DiceThrowedAttribute : MessageFilterAttribute
{
/// <summary>
/// Initializes the attribute to filter dice throws with a specific value.
/// </summary>
/// <param name="value">The dice value to match</param>
public DiceThrowedAttribute(int value)
: base(new DiceThrowedFilter(value)) { }
/// <summary>
/// Initializes the attribute to filter dice throws with a specific type and value.
/// </summary>
/// <param name="diceType">The type of dice</param>
/// <param name="value">The dice value to match</param>
public DiceThrowedAttribute(DiceType diceType, int value)
: base(new DiceThrowedFilter(diceType, value)) { }
}
/// <summary>
/// Attribute for filtering messages that are automatically forwarded.
/// </summary>
public class IsAutomaticFormwardMessageAttribute()
: MessageFilterAttribute(new IsAutomaticFormwardMessageFilter())
{ }
/// <summary>
/// Attribute for filtering messages sent while the user was offline.
/// </summary>
public class IsFromOfflineMessageAttribute()
: MessageFilterAttribute(new IsFromOfflineMessageFilter())
{ }
/// <summary>
/// Attribute for filtering service messages (e.g., user joined, left, etc.).
/// </summary>
public class IsServiceMessageMessageAttribute()
: MessageFilterAttribute(new IsServiceMessageMessageFilter())
{ }
/// <summary>
/// Attribute for filtering topic messages in forum chats.
/// </summary>
public class IsTopicMessageMessageAttribute()
: MessageFilterAttribute(new IsServiceMessageMessageFilter())
{ }
/// <summary>
/// Attribute for filtering messages based on their entities (mentions, links, etc.).
/// </summary>
public class MessageHasEntityAttribute : MessageFilterAttribute
{
/// <summary>
/// Initializes the attribute to filter messages with a specific entity type.
/// </summary>
/// <param name="type">The entity type to match</param>
public MessageHasEntityAttribute(MessageEntityType type)
: base(new MessageHasEntityFilter(type)) { }
/// <summary>
/// Initializes the attribute to filter messages with a specific entity type at a specific position.
/// </summary>
/// <param name="type">The entity type to match</param>
/// <param name="offset">The starting position of the entity</param>
/// <param name="length">The length of the entity (optional)</param>
public MessageHasEntityAttribute(MessageEntityType type, int offset, int? length)
: base(new MessageHasEntityFilter(type, offset, length)) { }
/// <summary>
/// Initializes the attribute to filter messages with a specific entity type and content.
/// </summary>
/// <param name="type">The entity type to match</param>
/// <param name="content">The content that the entity should contain</param>
/// <param name="stringComparison">The string comparison method</param>
public MessageHasEntityAttribute(MessageEntityType type, string content, StringComparison stringComparison = StringComparison.CurrentCulture)
: base(new MessageHasEntityFilter(type, content, stringComparison)) { }
/// <summary>
/// Initializes the attribute to filter messages with a specific entity type, position, and content.
/// </summary>
/// <param name="type">The entity type to match</param>
/// <param name="offset">The starting position of the entity</param>
/// <param name="length">The length of the entity (optional)</param>
/// <param name="content">The content that the entity should contain</param>
/// <param name="stringComparison">The string comparison method</param>
public MessageHasEntityAttribute(MessageEntityType type, int offset, int? length, string content, StringComparison stringComparison = StringComparison.CurrentCulture)
: base(new MessageHasEntityFilter(type, offset, length, content, stringComparison)) { }
}
@@ -1,27 +1,26 @@
using Telegrator.Filters;
namespace Telegrator.Annotations
{
/// <summary>
/// Attribute for filtering messages with reply to messages of this bot.
/// </summary>
public class MeRepliedAttribute()
: MessageFilterAttribute(new MeRepliedFilter())
{ }
namespace Telegrator.Annotations;
/// <summary>
/// Attribute for checking message's reply chain.
/// </summary>
public class HasReplyAttribute(int replyDepth = 1)
: MessageFilterAttribute(new MessageHasReplyFilter(replyDepth))
{ }
/// <summary>
/// Attribute for filtering messages with reply to messages of this bot.
/// </summary>
public class MeRepliedAttribute()
: MessageFilterAttribute(new MeRepliedFilter())
{ }
/// <summary>
/// Helper filter class for filters that operate on replied messages.
/// Provides functionality to traverse reply chains and access replied message content.
/// </summary>
/// <param name="replyDepth"></param>
public class FromReplyChainAttribute(int replyDepth = 1)
: MessageFilterAttribute(new FromReplyChainFilter(replyDepth))
{ }
}
/// <summary>
/// Attribute for checking message's reply chain.
/// </summary>
public class HasReplyAttribute(int replyDepth = 1)
: MessageFilterAttribute(new MessageHasReplyFilter(replyDepth))
{ }
/// <summary>
/// Helper filter class for filters that operate on replied messages.
/// Provides functionality to traverse reply chains and access replied message content.
/// </summary>
/// <param name="replyDepth"></param>
public class FromReplyChainAttribute(int replyDepth = 1)
: MessageFilterAttribute(new FromReplyChainFilter(replyDepth))
{ }
@@ -1,92 +1,91 @@
using Telegrator.Filters;
namespace Telegrator.Annotations
namespace Telegrator.Annotations;
/// <summary>
/// Attribute for filtering messages based on the sender's username.
/// </summary>
public class FromUsernameAttribute : MessageFilterAttribute
{
/// <summary>
/// Attribute for filtering messages based on the sender's username.
/// Initializes the attribute to filter messages from a specific username.
/// </summary>
public class FromUsernameAttribute : MessageFilterAttribute
{
/// <summary>
/// Initializes the attribute to filter messages from a specific username.
/// </summary>
/// <param name="username">The username to match</param>
public FromUsernameAttribute(string username)
: base(new FromUsernameFilter(username)) { }
/// <summary>
/// Initializes the attribute to filter messages from a specific username with custom comparison.
/// </summary>
/// <param name="username">The username to match</param>
/// <param name="comparison">The string comparison method</param>
public FromUsernameAttribute(string username, StringComparison comparison)
: base(new FromUsernameFilter(username, comparison)) { }
}
/// <param name="username">The username to match</param>
public FromUsernameAttribute(string username)
: base(new FromUsernameFilter(username)) { }
/// <summary>
/// Attribute for filtering messages based on the sender's name (first name and optionally last name).
/// Initializes the attribute to filter messages from a specific username with custom comparison.
/// </summary>
public class FromUserAttribute : MessageFilterAttribute
{
/// <summary>
/// Initializes the attribute to filter messages from a user with specific first and last names.
/// </summary>
/// <param name="firstName">The first name to match</param>
/// <param name="lastName">The last name to match (optional)</param>
/// <param name="comparison">The string comparison method</param>
public FromUserAttribute(string firstName, string? lastName, StringComparison comparison)
: base(new FromUserFilter(firstName, lastName, comparison)) { }
/// <summary>
/// Initializes the attribute to filter messages from a user with specific first and last names.
/// </summary>
/// <param name="firstName">The first name to match</param>
/// <param name="lastName">The last name to match</param>
public FromUserAttribute(string firstName, string? lastName)
: base(new FromUserFilter(firstName, lastName, StringComparison.InvariantCulture)) { }
/// <summary>
/// Initializes the attribute to filter messages from a user with a specific first name.
/// </summary>
/// <param name="firstName">The first name to match</param>
public FromUserAttribute(string firstName)
: base(new FromUserFilter(firstName, null, StringComparison.InvariantCulture)) { }
/// <summary>
/// Initializes the attribute to filter messages from a user with a specific first name and custom comparison.
/// </summary>
/// <param name="firstName">The first name to match</param>
/// <param name="comparison">The string comparison method</param>
public FromUserAttribute(string firstName, StringComparison comparison)
: base(new FromUserFilter(firstName, null, comparison)) { }
}
/// <summary>
/// Attribute for filtering messages from a specific user ID.
/// </summary>
/// <param name="userId">The user ID to match</param>
public class FromUserIdAttribute(long userId)
: MessageFilterAttribute(new FromUserIdFilter(userId))
{ }
/// <summary>
/// Attribute for filtering messages sent by not bots (users).
/// </summary>
public class NotFromBotAttribute()
: MessageFilterAttribute(new FromBotFilter().Not())
{ }
/// <summary>
/// Attribute for filtering messages sent by bots.
/// </summary>
public class FromBotAttribute()
: MessageFilterAttribute(new FromBotFilter())
{ }
/// <summary>
/// Attribute for filtering messages sent by premium users.
/// </summary>
public class FromPremiumUserAttribute()
: MessageFilterAttribute(new FromPremiumUserFilter())
{ }
/// <param name="username">The username to match</param>
/// <param name="comparison">The string comparison method</param>
public FromUsernameAttribute(string username, StringComparison comparison)
: base(new FromUsernameFilter(username, comparison)) { }
}
/// <summary>
/// Attribute for filtering messages based on the sender's name (first name and optionally last name).
/// </summary>
public class FromUserAttribute : MessageFilterAttribute
{
/// <summary>
/// Initializes the attribute to filter messages from a user with specific first and last names.
/// </summary>
/// <param name="firstName">The first name to match</param>
/// <param name="lastName">The last name to match (optional)</param>
/// <param name="comparison">The string comparison method</param>
public FromUserAttribute(string firstName, string? lastName, StringComparison comparison)
: base(new FromUserFilter(firstName, lastName, comparison)) { }
/// <summary>
/// Initializes the attribute to filter messages from a user with specific first and last names.
/// </summary>
/// <param name="firstName">The first name to match</param>
/// <param name="lastName">The last name to match</param>
public FromUserAttribute(string firstName, string? lastName)
: base(new FromUserFilter(firstName, lastName, StringComparison.InvariantCulture)) { }
/// <summary>
/// Initializes the attribute to filter messages from a user with a specific first name.
/// </summary>
/// <param name="firstName">The first name to match</param>
public FromUserAttribute(string firstName)
: base(new FromUserFilter(firstName, null, StringComparison.InvariantCulture)) { }
/// <summary>
/// Initializes the attribute to filter messages from a user with a specific first name and custom comparison.
/// </summary>
/// <param name="firstName">The first name to match</param>
/// <param name="comparison">The string comparison method</param>
public FromUserAttribute(string firstName, StringComparison comparison)
: base(new FromUserFilter(firstName, null, comparison)) { }
}
/// <summary>
/// Attribute for filtering messages from a specific user ID.
/// </summary>
/// <param name="userId">The user ID to match</param>
public class FromUserIdAttribute(long userId)
: MessageFilterAttribute(new FromUserIdFilter(userId))
{ }
/// <summary>
/// Attribute for filtering messages sent by not bots (users).
/// </summary>
public class NotFromBotAttribute()
: MessageFilterAttribute(new FromBotFilter().Not())
{ }
/// <summary>
/// Attribute for filtering messages sent by bots.
/// </summary>
public class FromBotAttribute()
: MessageFilterAttribute(new FromBotFilter())
{ }
/// <summary>
/// Attribute for filtering messages sent by premium users.
/// </summary>
public class FromPremiumUserAttribute()
: MessageFilterAttribute(new FromPremiumUserFilter())
{ }
@@ -1,58 +1,57 @@
using Telegrator.Filters;
namespace Telegrator.Annotations
{
/// <summary>
/// Attribute for filtering messages where the text starts with the specified content.
/// </summary>
/// <param name="content">The string that the message text should start with</param>
/// <param name="comparison">The string comparison type</param>
public class TextStartsWithAttribute(string content, StringComparison comparison = StringComparison.InvariantCulture)
: MessageFilterAttribute(new TextStartsWithFilter(content, comparison))
{ }
namespace Telegrator.Annotations;
/// <summary>
/// Attribute for filtering messages where the text ends with the specified content.
/// </summary>
/// <param name="content">The string that the message text should end with</param>
/// <param name="comparison">The string comparison type</param>
public class TextEndsWithAttribute(string content, StringComparison comparison = StringComparison.InvariantCulture)
: MessageFilterAttribute(new TextEndsWithFilter(content, comparison))
{ }
/// <summary>
/// Attribute for filtering messages where the text starts with the specified content.
/// </summary>
/// <param name="content">The string that the message text should start with</param>
/// <param name="comparison">The string comparison type</param>
public class TextStartsWithAttribute(string content, StringComparison comparison = StringComparison.InvariantCulture)
: MessageFilterAttribute(new TextStartsWithFilter(content, comparison))
{ }
/// <summary>
/// Attribute for filtering messages where the text contains the specified content.
/// </summary>
/// <param name="content">The string that the message text should contain</param>
/// <param name="comparison">The string comparison type</param>
public class TextContainsAttribute(string content, StringComparison comparison = StringComparison.InvariantCulture)
: MessageFilterAttribute(new TextContainsFilter(content, comparison))
{ }
/// <summary>
/// Attribute for filtering messages where the text ends with the specified content.
/// </summary>
/// <param name="content">The string that the message text should end with</param>
/// <param name="comparison">The string comparison type</param>
public class TextEndsWithAttribute(string content, StringComparison comparison = StringComparison.InvariantCulture)
: MessageFilterAttribute(new TextEndsWithFilter(content, comparison))
{ }
/// <summary>
/// Attribute for filtering messages where the text equals the specified content.
/// </summary>
/// <param name="content">The string that the message text should equal</param>
/// <param name="comparison">The string comparison type</param>
public class TextEqualsAttribute(string content, StringComparison comparison = StringComparison.InvariantCulture)
: MessageFilterAttribute(new TextEqualsFilter(content, comparison))
{ }
/// <summary>
/// Attribute for filtering messages where the text contains the specified content.
/// </summary>
/// <param name="content">The string that the message text should contain</param>
/// <param name="comparison">The string comparison type</param>
public class TextContainsAttribute(string content, StringComparison comparison = StringComparison.InvariantCulture)
: MessageFilterAttribute(new TextContainsFilter(content, comparison))
{ }
/// <summary>
/// Attribute for filtering messages that contain any non-empty text.
/// </summary>
public class HasTextAttribute()
: MessageFilterAttribute(new TextNotNullOrEmptyFilter())
{ }
/// <summary>
/// Attribute for filtering messages where the text equals the specified content.
/// </summary>
/// <param name="content">The string that the message text should equal</param>
/// <param name="comparison">The string comparison type</param>
public class TextEqualsAttribute(string content, StringComparison comparison = StringComparison.InvariantCulture)
: MessageFilterAttribute(new TextEqualsFilter(content, comparison))
{ }
/// <summary>
/// Attribute for filtering messages where the text contains a 'word'.
/// 'Word' must be a separate member of the text, and not have any alphabetic characters next to it.
/// </summary>
/// <param name="word"></param>
/// <param name="comparison"></param>
/// <param name="startIndex"></param>
public class TextContainsWordAttribute(string word, StringComparison comparison = StringComparison.InvariantCulture, int startIndex = 0)
: MessageFilterAttribute(new TextContainsWordFilter(word, comparison, startIndex))
{ }
}
/// <summary>
/// Attribute for filtering messages that contain any non-empty text.
/// </summary>
public class HasTextAttribute()
: MessageFilterAttribute(new TextNotNullOrEmptyFilter())
{ }
/// <summary>
/// Attribute for filtering messages where the text contains a 'word'.
/// 'Word' must be a separate member of the text, and not have any alphabetic characters next to it.
/// </summary>
/// <param name="word"></param>
/// <param name="comparison"></param>
/// <param name="startIndex"></param>
public class TextContainsWordAttribute(string word, StringComparison comparison = StringComparison.InvariantCulture, int startIndex = 0)
: MessageFilterAttribute(new TextContainsWordFilter(word, comparison, startIndex))
{ }
@@ -1,27 +1,26 @@
using Telegram.Bot.Types.Enums;
namespace Telegrator.Annotations
namespace Telegrator.Annotations;
/// <summary>
/// Attribute that says if this handler can await some of await types, that is not listed by its handler base.
/// Used for automatic collecting allowed to receiving <see cref="UpdateType"/>'s.
/// If you don't use it, you won't be able to await the updates inside handler.
/// </summary>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public class MightAwaitAttribute : Attribute
{
private readonly UpdateType[] _updateTypes;
/// <summary>
/// Attribute that says if this handler can await some of await types, that is not listed by its handler base.
/// Used for automatic collecting allowed to receiving <see cref="UpdateType"/>'s.
/// If you don't use it, you won't be able to await the updates inside handler.
/// Update types that may be awaited
/// </summary>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public class MightAwaitAttribute : Attribute
{
private readonly UpdateType[] _updateTypes;
public UpdateType[] UpdateTypes => _updateTypes;
/// <summary>
/// Update types that may be awaited
/// </summary>
public UpdateType[] UpdateTypes => _updateTypes;
/// <summary>
/// main ctor of <see cref="MightAwaitAttribute"/>
/// </summary>
/// <param name="updateTypes"></param>
public MightAwaitAttribute(params UpdateType[] updateTypes)
=> _updateTypes = updateTypes;
}
/// <summary>
/// main ctor of <see cref="MightAwaitAttribute"/>
/// </summary>
/// <param name="updateTypes"></param>
public MightAwaitAttribute(params UpdateType[] updateTypes)
=> _updateTypes = updateTypes;
}
@@ -0,0 +1,32 @@
using Telegram.Bot.Types;
using Telegram.Bot.Types.Enums;
using Telegrator.Attributes;
using Telegrator.Core.States;
using Telegrator.Filters;
namespace Telegrator.Annotations;
/// <summary>
/// Attribute for filtering updates where resolved state matches target value.
/// </summary>
/// <typeparam name="TKey"></typeparam>
/// <typeparam name="TValue"></typeparam>
/// <param name="value"></param>
public class StateAttribute<TKey, TValue>(TValue? value) : UpdateFilterAttribute<Update>(new StateKeyFilter<TKey, TValue>(value))
where TKey : IStateKeyResolver, new()
where TValue : IEquatable<TValue>
{
/// <summary>
/// The targetting state value.
/// </summary>
public TValue? Value => value;
/// <inheritdoc/>
public override UpdateType[] AllowedTypes => Update.AllTypes;
/// <inheritdoc/>
public override Update? GetFilterringTarget(Update update)
{
return update;
}
}
@@ -1,44 +0,0 @@
using Telegrator.StateKeeping;
using Telegrator.Attributes;
using Telegrator.Core.StateKeeping;
namespace Telegrator.Annotations.StateKeeping
{
/// <summary>
/// Attribute for managing enum-based states in Telegram bot handlers.
/// Provides a convenient way to associate enum values with state management functionality.
/// </summary>
/// <typeparam name="TEnum">The enum type to be used for state management.</typeparam>
public class EnumStateAttribute<TEnum> : StateKeeperAttribute<long, TEnum, EnumStateKeeper<TEnum>> where TEnum : Enum
{
/// <summary>
/// Initializes a new instance of the EnumStateAttribute with a special state and custom key resolver.
/// </summary>
/// <param name="specialState">The special state to be managed.</param>
/// <param name="keyResolver">The resolver for extracting keys from updates.</param>
public EnumStateAttribute(SpecialState specialState, IStateKeyResolver<long> keyResolver)
: base(specialState, keyResolver) { }
/// <summary>
/// Initializes a new instance of the EnumStateAttribute with a specific enum state and custom key resolver.
/// </summary>
/// <param name="myState">The specific enum state to be managed.</param>
/// <param name="keyResolver">The resolver for extracting keys from updates.</param>
public EnumStateAttribute(TEnum myState, IStateKeyResolver<long> keyResolver)
: base(myState, keyResolver) { }
/// <summary>
/// Initializes a new instance of the EnumStateAttribute with a special state and default sender ID resolver.
/// </summary>
/// <param name="specialState">The special state to be managed.</param>
public EnumStateAttribute(SpecialState specialState)
: base(specialState, new SenderIdResolver()) { }
/// <summary>
/// Initializes a new instance of the EnumStateAttribute with a specific enum state and default sender ID resolver.
/// </summary>
/// <param name="myState">The specific enum state to be managed.</param>
public EnumStateAttribute(TEnum myState)
: this(myState, new SenderIdResolver()) { }
}
}
@@ -1,43 +0,0 @@
using Telegrator.StateKeeping;
using Telegrator.Attributes;
using Telegrator.Core.StateKeeping;
namespace Telegrator.Annotations.StateKeeping
{
/// <summary>
/// Attribute for associating a handler or method with a numeric (integer) state keeper.
/// Provides constructors for flexible state and key resolver configuration.
/// </summary>
public class NumericStateAttribute : StateKeeperAttribute<long, int, NumericStateKeeper>
{
/// <summary>
/// Initializes the attribute with a special state and a custom key resolver.
/// </summary>
/// <param name="specialState">The special state to associate</param>
/// <param name="keyResolver">The key resolver for state keeping</param>
public NumericStateAttribute(SpecialState specialState, IStateKeyResolver<long> keyResolver)
: base(specialState, keyResolver) { }
/// <summary>
/// Initializes the attribute with a specific numeric state and a custom key resolver.
/// </summary>
/// <param name="myState">The integer state to associate</param>
/// <param name="keyResolver">The key resolver for state keeping</param>
public NumericStateAttribute(int myState, IStateKeyResolver<long> keyResolver)
: base(myState, keyResolver) { }
/// <summary>
/// Initializes the attribute with a special state and the default sender ID resolver.
/// </summary>
/// <param name="specialState">The special state to associate</param>
public NumericStateAttribute(SpecialState specialState)
: base(specialState, new SenderIdResolver()) { }
/// <summary>
/// Initializes the attribute with a specific numeric state and the default sender ID resolver.
/// </summary>
/// <param name="myState">The integer state to associate</param>
public NumericStateAttribute(int myState)
: this(myState, new SenderIdResolver()) { }
}
}
@@ -1,21 +0,0 @@
namespace Telegrator.Annotations.StateKeeping
{
/// <summary>
/// Represents special states for state keeping logic.
/// </summary>
public enum SpecialState
{
/// <summary>
/// No special state.
/// </summary>
None,
/// <summary>
/// Indicates that no state is present.
/// </summary>
NoState,
/// <summary>
/// Indicates that any state is acceptable.
/// </summary>
AnyState
}
}
@@ -1,43 +0,0 @@
using Telegrator.StateKeeping;
using Telegrator.Attributes;
using Telegrator.Core.StateKeeping;
namespace Telegrator.Annotations.StateKeeping
{
/// <summary>
/// Attribute for associating a handler or method with a string-based state keeper.
/// Provides various constructors for flexible state and key resolver configuration.
/// </summary>
public class StringStateAttribute : StateKeeperAttribute<long, string, StringStateKeeper>
{
/// <summary>
/// Initializes the attribute with a special state and a custom key resolver.
/// </summary>
/// <param name="specialState">The special state to associate</param>
/// <param name="keyResolver">The key resolver for state keeping</param>
public StringStateAttribute(SpecialState specialState, IStateKeyResolver<long> keyResolver)
: base(specialState, keyResolver) { }
/// <summary>
/// Initializes the attribute with a specific state and a custom key resolver.
/// </summary>
/// <param name="myState">The string state to associate</param>
/// <param name="keyResolver">The key resolver for state keeping</param>
public StringStateAttribute(string myState, IStateKeyResolver<long> keyResolver)
: base(myState, keyResolver) { }
/// <summary>
/// Initializes the attribute with a special state and the default sender ID resolver.
/// </summary>
/// <param name="specialState">The special state to associate</param>
public StringStateAttribute(SpecialState specialState)
: base(specialState, new SenderIdResolver()) { }
/// <summary>
/// Initializes the attribute with a specific state and the default sender ID resolver.
/// </summary>
/// <param name="myState">The string state to associate</param>
public StringStateAttribute(string myState)
: base(myState, new SenderIdResolver()) { }
}
}
@@ -1,23 +0,0 @@
using Telegram.Bot.Types;
using Telegram.Bot.Types.Enums;
using Telegrator.Filters;
namespace Telegrator.Annotations.Targetted
{
/// <summary>
/// Attribute for filtering message with command "start" in bot's private chats.
/// Allows handlers to respond to "welcome" bot commands.
/// </summary>
public class WelcomeAttribute : MessageFilterAttribute
{
/// <summary>
/// Creates new instance of <see cref="WelcomeAttribute"/>
/// </summary>
/// <param name="onlyFirst"></param>
public WelcomeAttribute(bool onlyFirst = false) : base(
new MessageChatTypeFilter(ChatType.Private),
new CommandAlliasFilter("start"),
Filter<Message>.If(ctx => !onlyFirst || ctx.Input.Id == 0))
{ }
}
}
@@ -0,0 +1,22 @@
using Telegram.Bot.Types;
using Telegram.Bot.Types.Enums;
using Telegrator.Filters;
namespace Telegrator.Annotations;
/// <summary>
/// Attribute for filtering message with command "start" in bot's private chats.
/// Allows handlers to respond to "welcome" bot commands.
/// </summary>
public class WelcomeAttribute : MessageFilterAttribute
{
/// <summary>
/// Creates new instance of <see cref="WelcomeAttribute"/>
/// </summary>
/// <param name="onlyFirst"></param>
public WelcomeAttribute(bool onlyFirst = false) : base(
new MessageChatTypeFilter(ChatType.Private),
new CommandAlliasFilter("start"),
Filter<Message>.If(ctx => !onlyFirst || ctx.Input.Id == 0))
{ }
}
@@ -1,16 +1,15 @@
namespace Telegrator.Aspects
namespace Telegrator.Aspects;
/// <summary>
/// Attribute that specifies a post-execution processor to be executed after the handler.
/// The processor type must implement <see cref="IPostProcessor"/> interface.
/// </summary>
/// <typeparam name="T">The type of the post-processor that implements <see cref="IPostProcessor"/>.</typeparam>
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
public class AfterExecutionAttribute<T> : Attribute where T : IPostProcessor
{
/// <summary>
/// Attribute that specifies a post-execution processor to be executed after the handler.
/// The processor type must implement <see cref="IPostProcessor"/> interface.
/// Gets the type of the post-processor.
/// </summary>
/// <typeparam name="T">The type of the post-processor that implements <see cref="IPostProcessor"/>.</typeparam>
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
public class AfterExecutionAttribute<T> : Attribute where T : IPostProcessor
{
/// <summary>
/// Gets the type of the post-processor.
/// </summary>
public Type ProcessorType => typeof(T);
}
public Type ProcessorType => typeof(T);
}
@@ -1,16 +1,15 @@
namespace Telegrator.Aspects
namespace Telegrator.Aspects;
/// <summary>
/// Attribute that specifies a pre-execution processor to be executed before the handler.
/// The processor type must implement <see cref="IPreProcessor"/> interface.
/// </summary>
/// <typeparam name="T">The type of the pre-processor that implements <see cref="IPreProcessor"/>.</typeparam>
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
public class BeforeExecutionAttribute<T> : Attribute where T : IPreProcessor
{
/// <summary>
/// Attribute that specifies a pre-execution processor to be executed before the handler.
/// The processor type must implement <see cref="IPreProcessor"/> interface.
/// Gets the type of the pre-processor.
/// </summary>
/// <typeparam name="T">The type of the pre-processor that implements <see cref="IPreProcessor"/>.</typeparam>
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
public class BeforeExecutionAttribute<T> : Attribute where T : IPreProcessor
{
/// <summary>
/// Gets the type of the pre-processor.
/// </summary>
public Type ProcessorType => typeof(T);
}
public Type ProcessorType => typeof(T);
}
+12 -13
View File
@@ -1,19 +1,18 @@
using Telegrator.Core.Handlers;
namespace Telegrator.Aspects
namespace Telegrator.Aspects;
/// <summary>
/// Interface for post-execution processors that are executed after handler execution.
/// Implement this interface to add cross-cutting concerns like logging, cleanup, or metrics collection.
/// </summary>
public interface IPostProcessor
{
/// <summary>
/// Interface for post-execution processors that are executed after handler execution.
/// Implement this interface to add cross-cutting concerns like logging, cleanup, or metrics collection.
/// Executes after the handler's main execution logic.
/// </summary>
public interface IPostProcessor
{
/// <summary>
/// Executes after the handler's main execution logic.
/// </summary>
/// <param name="container">The handler container containing the current update and context.</param>
/// <param name="cancellationToken"></param>
/// <returns>A <see cref="Result"/> indicating the final execution result.</returns>
public Task<Result> AfterExecution(IHandlerContainer container, CancellationToken cancellationToken);
}
/// <param name="container">The handler container containing the current update and context.</param>
/// <param name="cancellationToken"></param>
/// <returns>A <see cref="Result"/> indicating the final execution result.</returns>
public Task<Result> AfterExecution(IHandlerContainer container, CancellationToken cancellationToken);
}
+12 -13
View File
@@ -1,19 +1,18 @@
using Telegrator.Core.Handlers;
namespace Telegrator.Aspects
namespace Telegrator.Aspects;
/// <summary>
/// Interface for pre-execution processors that are executed before handler execution.
/// Implement this interface to add cross-cutting concerns like validation, logging, or authorization.
/// </summary>
public interface IPreProcessor
{
/// <summary>
/// Interface for pre-execution processors that are executed before handler execution.
/// Implement this interface to add cross-cutting concerns like validation, logging, or authorization.
/// Executes before the handler's main execution logic.
/// </summary>
public interface IPreProcessor
{
/// <summary>
/// Executes before the handler's main execution logic.
/// </summary>
/// <param name="container">The handler container containing the current update and context.</param>
/// <param name="cancellationToken"></param>
/// <returns>A <see cref="Result"/> indicating whether execution should continue or be stopped.</returns>
public Task<Result> BeforeExecution(IHandlerContainer container, CancellationToken cancellationToken = default);
}
/// <param name="container">The handler container containing the current update and context.</param>
/// <param name="cancellationToken"></param>
/// <returns>A <see cref="Result"/> indicating whether execution should continue or be stopped.</returns>
public Task<Result> BeforeExecution(IHandlerContainer container, CancellationToken cancellationToken = default);
}
+27 -28
View File
@@ -3,37 +3,36 @@ using Telegram.Bot.Types.Enums;
using Telegrator.Core.Filters;
using Telegrator.Filters;
namespace Telegrator.Attributes
namespace Telegrator.Attributes;
/// <summary>
/// Reactive way to implement a new <see cref="UpdateFilterAttribute{T}"/> of type <typeparamref name="T"/>
/// </summary>
/// <typeparam name="T"></typeparam>
public abstract class FilterAnnotation<T> : UpdateFilterAttribute<T>, IFilter<T>, INamedFilter where T : class
{
/// <inheritdoc/>
public virtual bool IsCollectible { get; } = false;
/// <inheritdoc/>
public override UpdateType[] AllowedTypes { get; } = typeof(T).GetAllowedUpdateTypes();
/// <inheritdoc/>
public string Name => GetType().Name;
/// <summary>
/// Reactive way to implement a new <see cref="UpdateFilterAttribute{T}"/> of type <typeparamref name="T"/>
/// Initializes new instance of <see cref="FilterAnnotation{T}"/>
/// </summary>
/// <typeparam name="T"></typeparam>
public abstract class FilterAnnotation<T> : UpdateFilterAttribute<T>, IFilter<T>, INamedFilter where T : class
public FilterAnnotation() : base()
{
/// <inheritdoc/>
public virtual bool IsCollectible { get; } = false;
/// <inheritdoc/>
public override UpdateType[] AllowedTypes { get; } = typeof(T).GetAllowedUpdateTypes();
/// <inheritdoc/>
public string Name => GetType().Name;
/// <summary>
/// Initializes new instance of <see cref="FilterAnnotation{T}"/>
/// </summary>
public FilterAnnotation() : base()
{
UpdateFilter = Filter<T>.If(CanPass);
AnonymousFilter = AnonymousTypeFilter.Compile(UpdateFilter, GetFilterringTarget);
}
/// <inheritdoc/>
public override T? GetFilterringTarget(Update update)
=> update.GetActualUpdateObject<T>();
/// <inheritdoc/>
public abstract bool CanPass(FilterExecutionContext<T> context);
UpdateFilter = Filter<T>.If(CanPass);
AnonymousFilter = AnonymousTypeFilter.Compile(UpdateFilter, GetFilterringTarget);
}
/// <inheritdoc/>
public override T? GetFilterringTarget(Update update)
=> update.GetActualUpdateObject<T>();
/// <inheritdoc/>
public abstract bool CanPass(FilterExecutionContext<T> context);
}
+18 -19
View File
@@ -1,25 +1,24 @@
namespace Telegrator.Attributes
namespace Telegrator.Attributes;
/// <summary>
/// Enumeration of filter modifiers that can be applied to update filters.
/// Defines how filters should be combined and applied in filter chains.
/// </summary>
[Flags]
public enum FilterModifier
{
/// <summary>
/// Enumeration of filter modifiers that can be applied to update filters.
/// Defines how filters should be combined and applied in filter chains.
/// No modifier applied. Filter is applied as-is.
/// </summary>
[Flags]
public enum FilterModifier
{
/// <summary>
/// No modifier applied. Filter is applied as-is.
/// </summary>
None = 1,
None = 1,
/// <summary>
/// OR modifier. This filter or the next filter in the chain should match.
/// </summary>
OrNext = 2,
/// <summary>
/// OR modifier. This filter or the next filter in the chain should match.
/// </summary>
OrNext = 2,
/// <summary>
/// NOT modifier. The inverse of this filter should match.
/// </summary>
Not = 4,
}
/// <summary>
/// NOT modifier. The inverse of this filter should match.
/// </summary>
Not = 4,
}
@@ -1,86 +0,0 @@
using Telegram.Bot.Types;
using Telegrator.Annotations.StateKeeping;
using Telegrator.Core.Attributes;
using Telegrator.Core.Filters;
using Telegrator.Core.StateKeeping;
namespace Telegrator.Attributes
{
/// <summary>
/// Abstract attribute for associating a handler or method with a state keeper.
/// Provides logic for state-based filtering and state management.
/// </summary>
/// <typeparam name="TKey">The type of the key used for state keeping (e.g., chat ID).</typeparam>
/// <typeparam name="TState">The type of the state value (e.g., string, int).</typeparam>
/// <typeparam name="TKeeper">The type of the state keeper implementation.</typeparam>
public abstract class StateKeeperAttribute<TKey, TState, TKeeper> : StateKeeperAttributeBase where TKey : notnull where TState : notnull where TKeeper : StateKeeperBase<TKey, TState>, new()
{
/*
private static readonly TKeeper _shared = new TKeeper();
private static readonly Dictionary<TKey, TKeeper> _keyed = [];
*/
/// <summary>
/// Gets or sets the singleton instance of the state keeper for this attribute type.
/// </summary>
public static TKeeper Shared { get; } = new TKeeper();
/// <summary>
/// Gets the default state value of this statekeeper.
/// </summary>
public static TState DefaultState => Shared.DefaultState;
/// <summary>
/// Gets the state value associated with this attribute instance.
/// </summary>
public TState MyState { get; private set; }
/// <summary>
/// Gets the special state mode for this attribute instance.
/// </summary>
public SpecialState SpecialState { get; private set; }
/// <summary>
/// Initializes the attribute with a specific state and a custom key resolver.
/// </summary>
/// <param name="myState">The state value to associate</param>
/// <param name="keyResolver">The key resolver for state keeping</param>
protected StateKeeperAttribute(TState myState, IStateKeyResolver<TKey> keyResolver) : base(typeof(TKeeper))
{
Shared.KeyResolver = keyResolver;
MyState = myState;
SpecialState = SpecialState.None;
}
/// <summary>
/// Initializes the attribute with a special state and a custom key resolver.
/// </summary>
/// <param name="specialState">The special state mode</param>
/// <param name="keyResolver">The key resolver for state keeping</param>
protected StateKeeperAttribute(SpecialState specialState, IStateKeyResolver<TKey> keyResolver) : base(typeof(TKeeper))
{
Shared.KeyResolver = keyResolver;
MyState = Shared.DefaultState;
SpecialState = specialState;
}
/// <summary>
/// Determines whether the current update context passes the state filter.
/// </summary>
/// <param name="context">The filter execution context</param>
/// <returns>True if the state matches the filter; otherwise, false.</returns>
public override bool CanPass(FilterExecutionContext<Update> context)
{
if (SpecialState == SpecialState.AnyState)
return true;
if (!Shared.TryGetState(context.Input, out TState? state))
return SpecialState == SpecialState.NoState;
if (state == null)
return false;
return MyState.Equals(state);
}
}
}
@@ -3,83 +3,82 @@ using Telegrator.Core.Attributes;
using Telegrator.Core.Filters;
using Telegrator.Filters;
namespace Telegrator.Attributes
namespace Telegrator.Attributes;
/// <summary>
/// Abstract base attribute for defining update filters for a specific type of update target.
/// Provides logic for filter composition, modifier processing, and target extraction.
/// </summary>
/// <typeparam name="T">The type of the update target to filter (e.g., Message, Update).</typeparam>
public abstract class UpdateFilterAttribute<T> : UpdateFilterAttributeBase where T : class
{
/// <summary>
/// Abstract base attribute for defining update filters for a specific type of update target.
/// Provides logic for filter composition, modifier processing, and target extraction.
/// Gets the compiled anonymous filter for this attribute.
/// </summary>
/// <typeparam name="T">The type of the update target to filter (e.g., Message, Update).</typeparam>
public abstract class UpdateFilterAttribute<T> : UpdateFilterAttributeBase where T : class
public override IFilter<Update> AnonymousFilter { get; protected set; }
/// <summary>
/// Gets the compiled filter logic for the update target.
/// </summary>
public IFilter<T> UpdateFilter { get; protected set; }
/// <summary>
/// Empty constructor for internal using
/// </summary>
internal UpdateFilterAttribute()
{
/// <summary>
/// Gets the compiled anonymous filter for this attribute.
/// </summary>
public override IFilter<Update> AnonymousFilter { get; protected set; }
/// <summary>
/// Gets the compiled filter logic for the update target.
/// </summary>
public IFilter<T> UpdateFilter { get; protected set; }
/// <summary>
/// Empty constructor for internal using
/// </summary>
internal UpdateFilterAttribute()
{
AnonymousFilter = null!;
UpdateFilter = null!;
_ = 0xBAD + 0xC0DE;
}
/// <summary>
/// Initializes the attribute with one or more filters for the update target.
/// </summary>
/// <param name="filters">The filters to compose</param>
protected UpdateFilterAttribute(params IFilter<T>[] filters)
{
string name = GetType().Name;
UpdateFilter = new CompiledFilter<T>(name, filters);
AnonymousFilter = AnonymousTypeFilter.Compile(name, UpdateFilter, GetFilterringTarget);
}
/// <summary>
/// Initializes the attribute with a precompiled filter for the update target.
/// </summary>
/// <param name="updateFilter">The compiled filter</param>
protected UpdateFilterAttribute(IFilter<T> updateFilter)
{
string name = GetType().Name;
UpdateFilter = updateFilter;
AnonymousFilter = AnonymousTypeFilter.Compile(name, UpdateFilter, GetFilterringTarget);
}
/// <summary>
/// Processes filter modifiers and combines this filter with the previous one if needed.
/// </summary>
/// <param name="previous">The previous filter attribute in the chain</param>
/// <returns>True if the OrNext modifier is set; otherwise, false.</returns>
public override sealed bool ProcessModifiers(UpdateFilterAttributeBase? previous)
{
if (Modifiers.HasFlag(FilterModifier.Not))
AnonymousFilter = Filter<T>.Not(AnonymousFilter);
if (previous is not null)
{
if (previous.Modifiers.HasFlag(FilterModifier.OrNext))
{
AnonymousFilter = Filter<Update>.Or(previous.AnonymousFilter, AnonymousFilter);
}
}
return Modifiers.HasFlag(FilterModifier.OrNext);
}
/// <summary>
/// Extracts the filtering target of type <typeparamref name="T"/> from the given update.
/// </summary>
/// <param name="update">The Telegram update</param>
/// <returns>The target object to filter, or null if not applicable</returns>
public abstract T? GetFilterringTarget(Update update);
AnonymousFilter = null!;
UpdateFilter = null!;
_ = 0xBAD + 0xC0DE;
}
/// <summary>
/// Initializes the attribute with one or more filters for the update target.
/// </summary>
/// <param name="filters">The filters to compose</param>
protected UpdateFilterAttribute(params IFilter<T>[] filters)
{
string name = GetType().Name;
UpdateFilter = new CompiledFilter<T>(name, filters);
AnonymousFilter = AnonymousTypeFilter.Compile(name, UpdateFilter, GetFilterringTarget);
}
/// <summary>
/// Initializes the attribute with a precompiled filter for the update target.
/// </summary>
/// <param name="updateFilter">The compiled filter</param>
protected UpdateFilterAttribute(IFilter<T> updateFilter)
{
string name = GetType().Name;
UpdateFilter = updateFilter;
AnonymousFilter = AnonymousTypeFilter.Compile(name, UpdateFilter, GetFilterringTarget);
}
/// <summary>
/// Processes filter modifiers and combines this filter with the previous one if needed.
/// </summary>
/// <param name="previous">The previous filter attribute in the chain</param>
/// <returns>True if the OrNext modifier is set; otherwise, false.</returns>
public override sealed bool ProcessModifiers(UpdateFilterAttributeBase? previous)
{
if (Modifiers.HasFlag(FilterModifier.Not))
AnonymousFilter = Filter<T>.Not(AnonymousFilter);
if (previous is not null)
{
if (previous.Modifiers.HasFlag(FilterModifier.OrNext))
{
AnonymousFilter = Filter<Update>.Or(previous.AnonymousFilter, AnonymousFilter);
}
}
return Modifiers.HasFlag(FilterModifier.OrNext);
}
/// <summary>
/// Extracts the filtering target of type <typeparamref name="T"/> from the given update.
/// </summary>
/// <param name="update">The Telegram update</param>
/// <returns>The target object to filter, or null if not applicable</returns>
public abstract T? GetFilterringTarget(Update update);
}
@@ -2,45 +2,44 @@
using Telegrator.Core.Attributes;
using Telegrator.Core.Handlers;
namespace Telegrator.Attributes
namespace Telegrator.Attributes;
/// <summary>
/// Abstract base attribute for marking update handler classes.
/// Provides a type-safe way to associate handler types with specific update types and importance settings.
/// </summary>
/// <typeparam name="T">The type of the update handler that this attribute is applied to.</typeparam>
public abstract class UpdateHandlerAttribute<T> : UpdateHandlerAttributeBase where T : UpdateHandlerBase
{
/// <summary>
/// Abstract base attribute for marking update handler classes.
/// Provides a type-safe way to associate handler types with specific update types and importance settings.
/// Initializes new instance of <see cref="UpdateHandlerAttribute{T}"/>
/// </summary>
/// <typeparam name="T">The type of the update handler that this attribute is applied to.</typeparam>
public abstract class UpdateHandlerAttribute<T> : UpdateHandlerAttributeBase where T : UpdateHandlerBase
{
/// <summary>
/// Initializes new instance of <see cref="UpdateHandlerAttribute{T}"/>
/// </summary>
/// <param name="updateType">The type of update that this handler can process.</param>
protected UpdateHandlerAttribute(UpdateType updateType)
: base([typeof(T)], updateType, 0) { }
/// <param name="updateType">The type of update that this handler can process.</param>
protected UpdateHandlerAttribute(UpdateType updateType)
: base([typeof(T)], updateType, 0) { }
/// <summary>
/// Initializes new instance of <see cref="UpdateHandlerAttribute{T}"/>
/// </summary>
/// <param name="updateType">The type of update that this handler can process.</param>
/// <param name="importance">The importance level for this handler</param>
protected UpdateHandlerAttribute(UpdateType updateType, int importance)
: base([typeof(T)], updateType, importance) { }
/// <summary>
/// Initializes new instance of <see cref="UpdateHandlerAttribute{T}"/>
/// </summary>
/// <param name="updateType">The type of update that this handler can process.</param>
/// <param name="importance">The importance level for this handler</param>
protected UpdateHandlerAttribute(UpdateType updateType, int importance)
: base([typeof(T)], updateType, importance) { }
/// <summary>
/// Initializes new instance of <see cref="UpdateHandlerAttribute{T}"/>
/// </summary>
/// <param name="types">Additional suported types.</param>
/// <param name="updateType">The type of update that this handler can process.</param>
protected UpdateHandlerAttribute(Type[] types, UpdateType updateType)
: base([..types, typeof(T)], updateType, 0) { }
/// <summary>
/// Initializes new instance of <see cref="UpdateHandlerAttribute{T}"/>
/// </summary>
/// <param name="types">Additional suported types.</param>
/// <param name="updateType">The type of update that this handler can process.</param>
protected UpdateHandlerAttribute(Type[] types, UpdateType updateType)
: base([..types, typeof(T)], updateType, 0) { }
/// <summary>
/// Initializes new instance of <see cref="UpdateHandlerAttribute{T}"/>
/// </summary>
/// <param name="types">Additional suported types.</param>
/// <param name="updateType">The type of update that this handler can process.</param>
/// <param name="importance">The importance level for this handler</param>
protected UpdateHandlerAttribute(Type[] types, UpdateType updateType, int importance)
: base([.. types, typeof(T)], updateType, importance) { }
}
/// <summary>
/// Initializes new instance of <see cref="UpdateHandlerAttribute{T}"/>
/// </summary>
/// <param name="types">Additional suported types.</param>
/// <param name="updateType">The type of update that this handler can process.</param>
/// <param name="importance">The importance level for this handler</param>
protected UpdateHandlerAttribute(Type[] types, UpdateType updateType, int importance)
: base([.. types, typeof(T)], updateType, importance) { }
}
@@ -1,35 +0,0 @@
using Telegram.Bot.Types;
using Telegrator.Core.Filters;
using Telegrator.Core.Handlers;
using Telegrator.Core.StateKeeping;
namespace Telegrator.Core.Attributes
{
/// <summary>
/// Sets the state in which the <see cref="UpdateHandlerBase"/> can be executed
/// </summary>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public abstract class StateKeeperAttributeBase : Attribute, IFilter<Update>
{
/// <inheritdoc/>
public bool IsCollectible => GetType().HasPublicProperties();
/// <summary>
/// Creates a new instance <see cref="StateKeeperBase{TKey, TState}"/>
/// </summary>
/// <param name="stateKeeperType"></param>
/// <exception cref="ArgumentException"></exception>
protected StateKeeperAttributeBase(Type stateKeeperType)
{
if (!stateKeeperType.IsAssignableToGenericType(typeof(StateKeeperBase<,>)))
throw new ArgumentException(stateKeeperType + " is not a StateKeeperBase", nameof(stateKeeperType));
}
/// <summary>
/// Realizes a <see cref="IFilter{T}"/> for validation of the current <see cref="StateKeeperBase{TKey, TState}"/> in the polling routing
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
public abstract bool CanPass(FilterExecutionContext<Update> context);
}
}
@@ -5,43 +5,42 @@ using Telegrator.Core.Filters;
using Telegrator.Core.Handlers;
using Telegrator.Filters;
namespace Telegrator.Core.Attributes
namespace Telegrator.Core.Attributes;
/// <summary>
/// Defines the <see cref="IFilter{T}"/> to <see cref="Update"/> validation for entry into execution of the <see cref="UpdateHandlerBase"/>
/// </summary>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)]
public abstract class UpdateFilterAttributeBase : Attribute
{
/// <summary>
/// Defines the <see cref="IFilter{T}"/> to <see cref="Update"/> validation for entry into execution of the <see cref="UpdateHandlerBase"/>
/// Gets the <see cref="UpdateType"/>'s that <see cref="UpdateHandlerBase"/> processing
/// </summary>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)]
public abstract class UpdateFilterAttributeBase : Attribute
public abstract UpdateType[] AllowedTypes { get; }
/// <summary>
/// Gets the <see cref="IFilter{T}"/> that <see cref="UpdateHandlerBase"/> processing
/// </summary>
public abstract IFilter<Update> AnonymousFilter { get; protected set; }
/// <summary>
/// Gets or sets the filter modifiers that affect how this filter is combined with others.
/// </summary>
public FilterModifier Modifiers { get; set; }
/// <summary>
/// Creates a new instance of <see cref="UpdateHandlerAttributeBase"/>
/// </summary>
/// <exception cref="ArgumentException"></exception>
protected internal UpdateFilterAttributeBase()
{
/// <summary>
/// Gets the <see cref="UpdateType"/>'s that <see cref="UpdateHandlerBase"/> processing
/// </summary>
public abstract UpdateType[] AllowedTypes { get; }
/// <summary>
/// Gets the <see cref="IFilter{T}"/> that <see cref="UpdateHandlerBase"/> processing
/// </summary>
public abstract IFilter<Update> AnonymousFilter { get; protected set; }
/// <summary>
/// Gets or sets the filter modifiers that affect how this filter is combined with others.
/// </summary>
public FilterModifier Modifiers { get; set; }
/// <summary>
/// Creates a new instance of <see cref="UpdateHandlerAttributeBase"/>
/// </summary>
/// <exception cref="ArgumentException"></exception>
protected internal UpdateFilterAttributeBase()
{
if (AllowedTypes.Length == 0)
throw new ArgumentException();
}
/// <summary>
/// Determines the logic of filter modifiers. Exceptionally internal implementation</summary>
/// <param name="previous"></param>
/// <returns></returns>
public abstract bool ProcessModifiers(UpdateFilterAttributeBase? previous);
if (AllowedTypes.Length == 0)
throw new ArgumentException();
}
/// <summary>
/// Determines the logic of filter modifiers. Exceptionally internal implementation</summary>
/// <param name="previous"></param>
/// <returns></returns>
public abstract bool ProcessModifiers(UpdateFilterAttributeBase? previous);
}
@@ -4,79 +4,78 @@ using Telegrator.Core.Descriptors;
using Telegrator.Core.Filters;
using Telegrator.Core.Handlers;
namespace Telegrator.Core.Attributes
namespace Telegrator.Core.Attributes;
/// <summary>
/// Defines the <see cref="UpdateType"/>'s and validator (<see cref="IFilter{T}"/>) of the <see cref="Update"/> that <see cref="UpdateHandlerBase"/> will process
/// </summary>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public abstract class UpdateHandlerAttributeBase : Attribute, IFilter<Update>
{
/// <inheritdoc/>
public bool IsCollectible => GetType().HasPublicProperties();
/// <summary>
/// Defines the <see cref="UpdateType"/>'s and validator (<see cref="IFilter{T}"/>) of the <see cref="Update"/> that <see cref="UpdateHandlerBase"/> will process
/// Gets an array of <see cref="UpdateHandlerBase"/> that this attribute can be attached to
/// </summary>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public abstract class UpdateHandlerAttributeBase : Attribute, IFilter<Update>
public Type[] ExpectingHandlerType { get; private set; }
/// <summary>
/// Gets an <see cref="UpdateType"/> that handlers processes
/// </summary>
public UpdateType Type { get; private set; }
/// <summary>
/// Gets or sets importance of this <see cref="UpdateHandlerBase"/> in same <see cref="UpdateType"/> pool
/// </summary>
public int Importance { get; set; }
/// <summary>
/// Gets or sets priority of this <see cref="UpdateHandlerBase"/> in same type handlers pool
/// </summary>
public int Priority { get; set; }
/// <summary>
/// Gets or sets a value indicating whether to form a fallback report.
/// </summary>
public bool FormReport { get; set; }
/// <summary>
/// Creates a new instance of <see cref="UpdateHandlerAttributeBase"/>
/// </summary>
/// <param name="expectingHandlerType">The types of handlers that this attribute can be applied to.</param>
/// <param name="updateType">The type of update that this handler processes.</param>
/// <param name="importance">The importance level of this handler (default: 0).</param>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="expectingHandlerType"/> is null.</exception>
/// <exception cref="ArgumentException">Thrown when one of the handler types is not a valid handler type.</exception>
/// <exception cref="Exception">Thrown when <paramref name="updateType"/> is <see cref="UpdateType.Unknown"/>.</exception>
protected internal UpdateHandlerAttributeBase(Type[] expectingHandlerType, UpdateType updateType, int importance = 0)
{
/// <inheritdoc/>
public bool IsCollectible => GetType().HasPublicProperties();
if (expectingHandlerType == null)
throw new ArgumentNullException(nameof(expectingHandlerType));
/// <summary>
/// Gets an array of <see cref="UpdateHandlerBase"/> that this attribute can be attached to
/// </summary>
public Type[] ExpectingHandlerType { get; private set; }
if (expectingHandlerType.Any(type => !type.IsHandlerAbstract()))
throw new ArgumentException("One of expectingHandlerType is not a handler type", nameof(expectingHandlerType));
/// <summary>
/// Gets an <see cref="UpdateType"/> that handlers processes
/// </summary>
public UpdateType Type { get; private set; }
if (updateType == UpdateType.Unknown)
throw new Exception();
/// <summary>
/// Gets or sets importance of this <see cref="UpdateHandlerBase"/> in same <see cref="UpdateType"/> pool
/// </summary>
public int Importance { get; set; }
/// <summary>
/// Gets or sets priority of this <see cref="UpdateHandlerBase"/> in same type handlers pool
/// </summary>
public int Priority { get; set; }
/// <summary>
/// Gets or sets a value indicating whether to form a fallback report.
/// </summary>
public bool FormReport { get; set; }
/// <summary>
/// Creates a new instance of <see cref="UpdateHandlerAttributeBase"/>
/// </summary>
/// <param name="expectingHandlerType">The types of handlers that this attribute can be applied to.</param>
/// <param name="updateType">The type of update that this handler processes.</param>
/// <param name="importance">The importance level of this handler (default: 0).</param>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="expectingHandlerType"/> is null.</exception>
/// <exception cref="ArgumentException">Thrown when one of the handler types is not a valid handler type.</exception>
/// <exception cref="Exception">Thrown when <paramref name="updateType"/> is <see cref="UpdateType.Unknown"/>.</exception>
protected internal UpdateHandlerAttributeBase(Type[] expectingHandlerType, UpdateType updateType, int importance = 0)
{
if (expectingHandlerType == null)
throw new ArgumentNullException(nameof(expectingHandlerType));
if (expectingHandlerType.Any(type => !type.IsHandlerAbstract()))
throw new ArgumentException("One of expectingHandlerType is not a handler type", nameof(expectingHandlerType));
if (updateType == UpdateType.Unknown)
throw new Exception();
ExpectingHandlerType = expectingHandlerType;
Type = updateType;
Importance = importance;
}
/// <summary>
/// Gets an <see cref="DescriptorIndexer"/> of this <see cref="UpdateHandlerAttributeBase"/> from <see cref="Importance"/> and <see cref="Priority"/>
/// </summary>
/// <returns>A descriptor indexer for this handler attribute.</returns>
public DescriptorIndexer GetIndexer()
=> new DescriptorIndexer(0, this);
/// <summary>
/// Validator (<see cref="IFilter{T}"/>) of the <see cref="Update"/> that <see cref="UpdateHandlerBase"/> will process
/// </summary>
/// <param name="context">The filter execution context containing the update to validate.</param>
/// <returns>True if the update passes validation; otherwise, false.</returns>
public abstract bool CanPass(FilterExecutionContext<Update> context);
ExpectingHandlerType = expectingHandlerType;
Type = updateType;
Importance = importance;
}
/// <summary>
/// Gets an <see cref="DescriptorIndexer"/> of this <see cref="UpdateHandlerAttributeBase"/> from <see cref="Importance"/> and <see cref="Priority"/>
/// </summary>
/// <returns>A descriptor indexer for this handler attribute.</returns>
public DescriptorIndexer GetIndexer()
=> new DescriptorIndexer(0, this);
/// <summary>
/// Validator (<see cref="IFilter{T}"/>) of the <see cref="Update"/> that <see cref="UpdateHandlerBase"/> will process
/// </summary>
/// <param name="context">The filter execution context containing the update to validate.</param>
/// <returns>True if the update passes validation; otherwise, false.</returns>
public abstract bool CanPass(FilterExecutionContext<Update> context);
}
@@ -7,63 +7,62 @@ using Telegrator.Core.Handlers;
using Telegrator.Handlers;
using Telegrator.Handlers.Building;
namespace Telegrator.Core.Descriptors
namespace Telegrator.Core.Descriptors;
/// <summary>
/// Descriptor for creating handlers from methods
/// </summary>
/// <typeparam name="TUpdate"></typeparam>
public class MethodHandlerDescriptor<TUpdate> : HandlerDescriptor where TUpdate : class
{
private readonly MethodInfo Method;
/// <summary>
/// Descriptor for creating handlers from methods
/// Initializes new instance of <see cref="MethodHandlerDescriptor{TUpdate}"/>
/// </summary>
/// <typeparam name="TUpdate"></typeparam>
public class MethodHandlerDescriptor<TUpdate> : HandlerDescriptor where TUpdate : class
/// <param name="action"></param>
public MethodHandlerDescriptor(AbstractHandlerAction<TUpdate> action) : base(DescriptorType.General, typeof(MethodHandler), true)
{
private readonly MethodInfo Method;
UpdateHandlerAttributeBase handlerAttribute = HandlerInspector.GetHandlerAttribute(action.Method);
IFilter<Update>? stateKeeperAttribute = HandlerInspector.GetStateKeeperAttribute(action.Method);
IFilter<Update>[] filters = HandlerInspector.GetFilterAttributes(action.Method, handlerAttribute.Type).ToArray();
/// <summary>
/// Initializes new instance of <see cref="MethodHandlerDescriptor{TUpdate}"/>
/// </summary>
/// <param name="action"></param>
public MethodHandlerDescriptor(AbstractHandlerAction<TUpdate> action) : base(DescriptorType.General, typeof(MethodHandler), true)
UpdateType = handlerAttribute.Type;
Indexer = handlerAttribute.GetIndexer();
Filters = new DescriptorFiltersSet(handlerAttribute, stateKeeperAttribute, filters);
DisplayString = HandlerInspector.GetDisplayName(action.Method) ?? action.Method.Name;
Method = action.Method;
InstanceFactory = () => new MethodHandler(UpdateType);
LazyInitialization = handler =>
{
UpdateHandlerAttributeBase handlerAttribute = HandlerInspector.GetHandlerAttribute(action.Method);
StateKeeperAttributeBase? stateKeeperAttribute = HandlerInspector.GetStateKeeperAttribute(action.Method);
IFilter<Update>[] filters = HandlerInspector.GetFilterAttributes(action.Method, handlerAttribute.Type).ToArray();
if (handler is not MethodHandler methodHandler)
throw new InvalidDataException();
UpdateType = handlerAttribute.Type;
Indexer = handlerAttribute.GetIndexer();
Filters = new DescriptorFiltersSet(handlerAttribute, stateKeeperAttribute, filters);
DisplayString = HandlerInspector.GetDisplayName(action.Method) ?? action.Method.Name;
Method = action.Method;
InstanceFactory = () => new MethodHandler(UpdateType);
LazyInitialization = handler =>
{
if (handler is not MethodHandler methodHandler)
throw new InvalidDataException();
methodHandler.Method = Method;
};
}
methodHandler.Method = Method;
};
}
private class MethodHandler(UpdateType updateType) : AbstractUpdateHandler<TUpdate>(updateType)
{
internal MethodInfo Method = null!;
private class MethodHandler(UpdateType updateType) : AbstractUpdateHandler<TUpdate>(updateType)
public override async Task<Result> Execute(IHandlerContainer<TUpdate> container, CancellationToken cancellation)
{
internal MethodInfo Method = null!;
if (Method is null)
throw new Exception();
public override async Task<Result> Execute(IHandlerContainer<TUpdate> container, CancellationToken cancellation)
if (Method.ReturnType == typeof(void))
{
if (Method is null)
throw new Exception();
Method.Invoke(this, [container, cancellation]);
return Result.Ok();
}
else
{
object branchReturn = Method.Invoke(this, [container, cancellation]);
if (branchReturn is not Task<Result> branchTask)
throw new InvalidOperationException();
if (Method.ReturnType == typeof(void))
{
Method.Invoke(this, [container, cancellation]);
return Result.Ok();
}
else
{
object branchReturn = Method.Invoke(this, [container, cancellation]);
if (branchReturn is not Task<Result> branchTask)
throw new InvalidOperationException();
return await branchTask;
}
return await branchTask;
}
}
}
@@ -2,127 +2,135 @@
using Telegram.Bot.Types;
using Telegrator.Core.Filters;
using Telegrator.Core.Handlers;
using Telegrator.Core.States;
namespace Telegrator.Core.Descriptors
namespace Telegrator.Core.Descriptors;
/// <summary>
/// Contains information about a described handler, including its context, client, and execution logic.
/// </summary>
public class DescribedHandlerDescriptor
{
private readonly ManualResetEventSlim ResetEvent = new ManualResetEventSlim(false);
/// <summary>
/// Contains information about a described handler, including its context, client, and execution logic.
/// Descriptor from that handler was described from.
/// </summary>
public class DescribedHandlerDescriptor
public HandlerDescriptor From { get; }
/// <summary>
/// The update router associated with this handler.
/// </summary>
public IUpdateRouter UpdateRouter { get; }
/// <summary>
/// The awaiting provider to fetch new updates inside handler
/// </summary>
public IAwaitingProvider AwaitingProvider { get; }
/// <summary>
/// The state storage to handling state machines
/// </summary>
public IStateStorage StateStorage { get; }
/// <summary>
/// The Telegram bot client used for this handler.
/// </summary>
public ITelegramBotClient Client { get; }
/// <summary>
/// The handler instance being described.
/// </summary>
public UpdateHandlerBase HandlerInstance { get; }
/// <summary>
/// Extra data associated with the handler execution.
/// </summary>
public Dictionary<string, object> ExtraData { get; }
/// <summary>
/// List of completed filters for this handler.
/// </summary>
public CompletedFiltersList CompletedFilters { get; }
/// <summary>
/// The update being handled.
/// </summary>
public Update HandlingUpdate { get; }
/// <summary>
/// Lifetime token for the handler instance.
/// </summary>
public HandlerLifetimeToken HandlerLifetime => HandlerInstance.LifetimeToken;
/// <summary>
/// Display string for the handler (for debugging or logging).
/// </summary>
public string DisplayString { get; set; }
/// <summary>
/// The final execution result.
/// </summary>
public Result? Result { get; private set; }
/// <summary>
/// Initializes a new instance of the <see cref="DescribedHandlerDescriptor"/> class.
/// </summary>
/// <param name="fromDescriptor">The descriptor from which this handler was described.</param>
/// <param name="updateRouter">The update router.</param>
/// <param name="awaitingProvider">The awaiting provider.</param>
/// <param name="stateStorage">The state storage.</param>
/// <param name="client">The Telegram bot client.</param>
/// <param name="handlerInstance">The handler instance.</param>
/// <param name="filterContext">The filter execution context.</param>
/// <param name="displayString">Optional display string.</param>
public DescribedHandlerDescriptor(
HandlerDescriptor fromDescriptor,
IUpdateRouter updateRouter,
IAwaitingProvider awaitingProvider,
IStateStorage stateStorage,
ITelegramBotClient client,
UpdateHandlerBase handlerInstance,
FilterExecutionContext<Update> filterContext,
string? displayString)
{
private readonly ManualResetEventSlim ResetEvent = new ManualResetEventSlim(false);
/// <summary>
/// Descriptor from that handler was described from.
/// </summary>
public HandlerDescriptor From { get; }
/// <summary>
/// The update router associated with this handler.
/// </summary>
public IUpdateRouter UpdateRouter { get; }
/// <summary>
/// The awaiting provider to fetch new updates inside handler
/// </summary>
public IAwaitingProvider AwaitingProvider { get; }
/// <summary>
/// The Telegram bot client used for this handler.
/// </summary>
public ITelegramBotClient Client { get; }
/// <summary>
/// The handler instance being described.
/// </summary>
public UpdateHandlerBase HandlerInstance { get; }
/// <summary>
/// Extra data associated with the handler execution.
/// </summary>
public Dictionary<string, object> ExtraData { get; }
/// <summary>
/// List of completed filters for this handler.
/// </summary>
public CompletedFiltersList CompletedFilters { get; }
/// <summary>
/// The update being handled.
/// </summary>
public Update HandlingUpdate { get; }
/// <summary>
/// Lifetime token for the handler instance.
/// </summary>
public HandlerLifetimeToken HandlerLifetime => HandlerInstance.LifetimeToken;
/// <summary>
/// Display string for the handler (for debugging or logging).
/// </summary>
public string DisplayString { get; set; }
/// <summary>
/// The final execution result.
/// </summary>
public Result? Result { get; private set; }
/// <summary>
/// Initializes a new instance of the <see cref="DescribedHandlerDescriptor"/> class.
/// </summary>
/// <param name="fromDescriptor">The descriptor from which this handler was described.</param>
/// <param name="updateRouter">The update router.</param>
/// <param name="awaitingProvider">The awaiting provider.</param>
/// <param name="client">The Telegram bot client.</param>
/// <param name="handlerInstance">The handler instance.</param>
/// <param name="filterContext">The filter execution context.</param>
/// <param name="displayString">Optional display string.</param>
public DescribedHandlerDescriptor(
HandlerDescriptor fromDescriptor,
IUpdateRouter updateRouter,
IAwaitingProvider awaitingProvider,
ITelegramBotClient client,
UpdateHandlerBase handlerInstance,
FilterExecutionContext<Update> filterContext,
string? displayString)
{
From = fromDescriptor;
UpdateRouter = updateRouter;
AwaitingProvider = awaitingProvider;
Client = client;
HandlerInstance = handlerInstance;
ExtraData = filterContext.Data;
CompletedFilters = filterContext.CompletedFilters;
HandlingUpdate = filterContext.Update;
DisplayString = displayString ?? fromDescriptor.HandlerType.Name;
}
/// <summary>
/// Waits for the handler execution result.
/// </summary>
/// <param name="cancellationToken">The cancellation token.</param>
public async Task AwaitResult(CancellationToken cancellationToken)
{
await Task.Yield();
ResetEvent.Reset();
ResetEvent.Wait(cancellationToken);
}
/// <summary>
/// Reports the execution result and signals completion.
/// </summary>
/// <param name="result">The execution result.</param>
public void ReportResult(Result? result)
{
if (result != null)
throw new InvalidOperationException("Result already reported");
Result = result;
ResetEvent.Set();
}
/// <inheritdoc/>
public override string ToString()
=> DisplayString ?? From.HandlerType.Name;
From = fromDescriptor;
UpdateRouter = updateRouter;
AwaitingProvider = awaitingProvider;
StateStorage = stateStorage;
Client = client;
HandlerInstance = handlerInstance;
ExtraData = filterContext.Data;
CompletedFilters = filterContext.CompletedFilters;
HandlingUpdate = filterContext.Update;
DisplayString = displayString ?? fromDescriptor.HandlerType.Name;
}
/// <summary>
/// Waits for the handler execution result.
/// </summary>
/// <param name="cancellationToken">The cancellation token.</param>
public async Task AwaitResult(CancellationToken cancellationToken)
{
await Task.Yield();
ResetEvent.Reset();
ResetEvent.Wait(cancellationToken);
}
/// <summary>
/// Reports the execution result and signals completion.
/// </summary>
/// <param name="result">The execution result.</param>
public void ReportResult(Result? result)
{
if (Result != null)
throw new InvalidOperationException("Result already reported");
Result = result;
ResetEvent.Set();
}
/// <inheritdoc/>
public override string ToString()
=> DisplayString ?? From.HandlerType.Name;
}
@@ -1,80 +1,79 @@
using Telegrator.Aspects;
using Telegrator.Core.Handlers;
namespace Telegrator.Core.Descriptors
namespace Telegrator.Core.Descriptors;
/// <summary>
/// Manages the execution of pre and post-execution aspects for a handler.
/// This class coordinates between self-processing (handler implements interfaces)
/// and typed processing (external processor classes).
/// </summary>
public sealed class DescriptorAspectsSet
{
/// <summary>
/// Manages the execution of pre and post-execution aspects for a handler.
/// This class coordinates between self-processing (handler implements interfaces)
/// and typed processing (external processor classes).
/// Gets the type of the external pre-processor, if specified via <see cref="BeforeExecutionAttribute{T}"/>.
/// </summary>
public sealed class DescriptorAspectsSet
public Type? TypedPre { get; private set; }
/// <summary>
/// Gets the type of the external post-processor, if specified via <see cref="AfterExecutionAttribute{T}"/>.
/// </summary>
public Type? TypedPost { get; private set; }
/// <summary>
/// Initializes a new instance of the <see cref="DescriptorAspectsSet"/> class.
/// </summary>
/// <param name="typedPre">The type of external pre-processor, if any.</param>
/// <param name="typedPost">The type of external post-processor, if any.</param>
public DescriptorAspectsSet(Type? typedPre, Type? typedPost)
{
/// <summary>
/// Gets the type of the external pre-processor, if specified via <see cref="BeforeExecutionAttribute{T}"/>.
/// </summary>
public Type? TypedPre { get; private set; }
TypedPre = typedPre;
TypedPost = typedPost;
}
/// <summary>
/// Gets the type of the external post-processor, if specified via <see cref="AfterExecutionAttribute{T}"/>.
/// </summary>
public Type? TypedPost { get; private set; }
/// <summary>
/// Initializes a new instance of the <see cref="DescriptorAspectsSet"/> class.
/// </summary>
/// <param name="typedPre">The type of external pre-processor, if any.</param>
/// <param name="typedPost">The type of external post-processor, if any.</param>
public DescriptorAspectsSet(Type? typedPre, Type? typedPost)
/// <summary>
/// Executes the pre-execution aspect for the handler.
/// </summary>
/// <param name="handler">The handler instance.</param>
/// <param name="container">The handler container with update context.</param>
/// <param name="cancellationToken"></param>
/// <returns>A <see cref="Result"/> indicating whether execution should continue.</returns>
/// <exception cref="InvalidOperationException">Thrown when handler claims to implement <see cref="IPreProcessor"/> but doesn't.</exception>
public async Task<Result> ExecutePre(UpdateHandlerBase handler, IHandlerContainer container, CancellationToken cancellationToken)
{
if (handler is IPreProcessor preProcessor)
{
TypedPre = typedPre;
TypedPost = typedPost;
return await preProcessor.BeforeExecution(container, cancellationToken).ConfigureAwait(false);
}
else if (TypedPre != null)
{
preProcessor = (IPreProcessor)Activator.CreateInstance(TypedPre);
return await preProcessor.BeforeExecution(container, cancellationToken).ConfigureAwait(false);
}
/// <summary>
/// Executes the pre-execution aspect for the handler.
/// </summary>
/// <param name="handler">The handler instance.</param>
/// <param name="container">The handler container with update context.</param>
/// <param name="cancellationToken"></param>
/// <returns>A <see cref="Result"/> indicating whether execution should continue.</returns>
/// <exception cref="InvalidOperationException">Thrown when handler claims to implement <see cref="IPreProcessor"/> but doesn't.</exception>
public async Task<Result> ExecutePre(UpdateHandlerBase handler, IHandlerContainer container, CancellationToken cancellationToken)
{
if (handler is IPreProcessor preProcessor)
{
return await preProcessor.BeforeExecution(container, cancellationToken).ConfigureAwait(false);
}
else if (TypedPre != null)
{
preProcessor = (IPreProcessor)Activator.CreateInstance(TypedPre);
return await preProcessor.BeforeExecution(container, cancellationToken).ConfigureAwait(false);
}
return Result.Ok();
}
return Result.Ok();
/// <summary>
/// Executes the post-execution aspect for the handler.
/// </summary>
/// <param name="handler">The handler instance.</param>
/// <param name="container">The handler container with update context.</param>
/// <param name="cancellationToken"></param>
/// <returns>A <see cref="Result"/> indicating the final execution result.</returns>
/// <exception cref="InvalidOperationException">Thrown when handler claims to implement <see cref="IPostProcessor"/> but doesn't.</exception>
public async Task<Result> ExecutePost(UpdateHandlerBase handler, IHandlerContainer container, CancellationToken cancellationToken)
{
if (handler is IPostProcessor postProcessor)
{
return await postProcessor.AfterExecution(container, cancellationToken).ConfigureAwait(false);
}
else if (TypedPost != null)
{
postProcessor = (IPostProcessor)Activator.CreateInstance(TypedPost);
return await postProcessor.AfterExecution(container, cancellationToken).ConfigureAwait(false);
}
/// <summary>
/// Executes the post-execution aspect for the handler.
/// </summary>
/// <param name="handler">The handler instance.</param>
/// <param name="container">The handler container with update context.</param>
/// <param name="cancellationToken"></param>
/// <returns>A <see cref="Result"/> indicating the final execution result.</returns>
/// <exception cref="InvalidOperationException">Thrown when handler claims to implement <see cref="IPostProcessor"/> but doesn't.</exception>
public async Task<Result> ExecutePost(UpdateHandlerBase handler, IHandlerContainer container, CancellationToken cancellationToken)
{
if (handler is IPostProcessor postProcessor)
{
return await postProcessor.AfterExecution(container, cancellationToken).ConfigureAwait(false);
}
else if (TypedPost != null)
{
postProcessor = (IPostProcessor)Activator.CreateInstance(TypedPost);
return await postProcessor.AfterExecution(container, cancellationToken).ConfigureAwait(false);
}
return Result.Ok();
}
return Result.Ok();
}
}
@@ -3,143 +3,142 @@ using Telegrator.Core.Filters;
using Telegrator.Handlers.Diagnostics;
using Telegrator.Logging;
namespace Telegrator.Core.Descriptors
namespace Telegrator.Core.Descriptors;
/// <summary>
/// Represents a set of filters for a handler descriptor, including update and state keeper validators.
/// </summary>
public sealed class DescriptorFiltersSet
{
/// <summary>
/// Represents a set of filters for a handler descriptor, including update and state keeper validators.
/// Validator for the update object.
/// </summary>
public sealed class DescriptorFiltersSet
public IFilter<Update>? UpdateValidator { get; set; }
/// <summary>
/// Validator for the state keeper.
/// </summary>
public IFilter<Update>? StateKeeperValidator { get; set; }
/// <summary>
/// Array of update filters.
/// </summary>
public IFilter<Update>[]? UpdateFilters { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="DescriptorFiltersSet"/> class.
/// </summary>
/// <param name="updateValidator">Validator for the update object.</param>
/// <param name="stateKeeperValidator">Validator for the state keeper.</param>
/// <param name="updateFilters">Array of update filters.</param>
public DescriptorFiltersSet(IFilter<Update>? updateValidator, IFilter<Update>? stateKeeperValidator, IFilter<Update>[]? updateFilters)
{
/// <summary>
/// Validator for the update object.
/// </summary>
public IFilter<Update>? UpdateValidator { get; set; }
UpdateValidator = updateValidator;
StateKeeperValidator = stateKeeperValidator;
UpdateFilters = updateFilters;
}
/// <summary>
/// Validator for the state keeper.
/// </summary>
public IFilter<Update>? StateKeeperValidator { get; set; }
/// <summary>
/// Validates the filter context using all filters in the set.
/// </summary>
/// <param name="filterContext">The filter execution context.</param>
/// <param name="formReport"></param>
/// <param name="report"></param>
/// <returns>True if all filters pass; otherwise, false.</returns>
public Result Validate(FilterExecutionContext<Update> filterContext, bool formReport, ref FiltersFallbackReport report)
{
bool anyErrors = false;
/// <summary>
/// Array of update filters.
/// </summary>
public IFilter<Update>[]? UpdateFilters { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="DescriptorFiltersSet"/> class.
/// </summary>
/// <param name="updateValidator">Validator for the update object.</param>
/// <param name="stateKeeperValidator">Validator for the state keeper.</param>
/// <param name="updateFilters">Array of update filters.</param>
public DescriptorFiltersSet(IFilter<Update>? updateValidator, IFilter<Update>? stateKeeperValidator, IFilter<Update>[]? updateFilters)
if (UpdateValidator != null)
{
UpdateValidator = updateValidator;
StateKeeperValidator = stateKeeperValidator;
UpdateFilters = updateFilters;
bool result = ExecuteFilter(UpdateValidator, filterContext, out Exception? exc);
if (formReport)
{
report.UpdateValidator = new FilterFallbackInfo("Validator", UpdateValidator, !result, exc);
}
if (!result)
{
anyErrors = true;
TelegratorLogging.LogTrace("(E) UpdateValidator filter of '{0}' for Update ({1}) didnt pass!", filterContext.Data["handler_name"], filterContext.Update.Id);
if (!formReport)
return Result.Fault();
}
else
{
filterContext.CompletedFilters.Add(UpdateValidator);
}
}
/// <summary>
/// Validates the filter context using all filters in the set.
/// </summary>
/// <param name="filterContext">The filter execution context.</param>
/// <param name="formReport"></param>
/// <param name="report"></param>
/// <returns>True if all filters pass; otherwise, false.</returns>
public Result Validate(FilterExecutionContext<Update> filterContext, bool formReport, ref FiltersFallbackReport report)
if (StateKeeperValidator != null)
{
bool anyErrors = false;
bool result = ExecuteFilter(StateKeeperValidator, filterContext, out Exception? exc);
if (UpdateValidator != null)
if (formReport)
{
bool result = ExecuteFilter(UpdateValidator, filterContext, out Exception? exc);
report.StateKeeperValidator = new FilterFallbackInfo("State", StateKeeperValidator, !result, exc);
}
if (!result)
{
anyErrors = true;
TelegratorLogging.LogTrace("(E) StateKeeperValidator filter of '{0}' for Update ({1}) didnt pass!", filterContext.Data["handler_name"], filterContext.Update.Id);
if (!formReport)
return Result.Fault();
}
else
{
filterContext.CompletedFilters.Add(StateKeeperValidator);
}
}
if (UpdateFilters != null)
{
foreach (IFilter<Update> filter in UpdateFilters)
{
bool result = ExecuteFilter(filter, filterContext, out Exception? exc);
string filterName = filter is INamedFilter named ? named.Name : filter.GetType().Name;
if (formReport)
{
report.UpdateValidator = new FilterFallbackInfo("Validator", UpdateValidator, !result, exc);
report.UpdateFilters.Add(new FilterFallbackInfo(filterName, filter, !result, exc));
}
if (!result)
{
anyErrors = true;
TelegratorLogging.LogTrace("(E) UpdateValidator filter of '{0}' for Update ({1}) didnt pass!", filterContext.Data["handler_name"], filterContext.Update.Id);
TelegratorLogging.LogTrace("(E) '{0}' filter of '{1}' for Update ({2}) didnt pass!", filterName, filterContext.Data["handler_name"], filterContext.Update.Id);
if (!formReport)
return Result.Fault();
}
else
{
filterContext.CompletedFilters.Add(UpdateValidator);
filterContext.CompletedFilters.Add(filter);
}
}
if (StateKeeperValidator != null)
{
bool result = ExecuteFilter(StateKeeperValidator, filterContext, out Exception? exc);
if (formReport)
{
report.StateKeeperValidator = new FilterFallbackInfo("State", StateKeeperValidator, !result, exc);
}
if (!result)
{
anyErrors = true;
TelegratorLogging.LogTrace("(E) StateKeeperValidator filter of '{0}' for Update ({1}) didnt pass!", filterContext.Data["handler_name"], filterContext.Update.Id);
if (!formReport)
return Result.Fault();
}
else
{
filterContext.CompletedFilters.Add(StateKeeperValidator);
}
}
if (UpdateFilters != null)
{
foreach (IFilter<Update> filter in UpdateFilters)
{
bool result = ExecuteFilter(filter, filterContext, out Exception? exc);
string filterName = filter is INamedFilter named ? named.Name : filter.GetType().Name;
if (formReport)
{
report.UpdateFilters.Add(new FilterFallbackInfo(filterName, filter, !result, exc));
}
if (!result)
{
anyErrors = true;
TelegratorLogging.LogTrace("(E) '{0}' filter of '{1}' for Update ({2}) didnt pass!", filterName, filterContext.Data["handler_name"], filterContext.Update.Id);
if (!formReport)
return Result.Fault();
}
else
{
filterContext.CompletedFilters.Add(filter);
}
}
}
if (!anyErrors)
return Result.Ok();
return formReport ? Result.Next() : Result.Fault();
}
private static bool ExecuteFilter<T>(IFilter<T> filter, FilterExecutionContext<T> context, out Exception? exception) where T : class
if (!anyErrors)
return Result.Ok();
return formReport ? Result.Next() : Result.Fault();
}
private static bool ExecuteFilter<T>(IFilter<T> filter, FilterExecutionContext<T> context, out Exception? exception) where T : class
{
try
{
try
{
exception = null;
return filter.CanPass(context);
}
catch (Exception ex)
{
exception = ex;
return false;
}
exception = null;
return filter.CanPass(context);
}
catch (Exception ex)
{
exception = ex;
return false;
}
}
}
@@ -1,88 +1,87 @@
using Telegrator.Core.Attributes;
namespace Telegrator.Core.Descriptors
namespace Telegrator.Core.Descriptors;
/// <summary>
/// Represents an indexer for handler descriptors, containing importance and priority information.
/// </summary>
public readonly struct DescriptorIndexer(int routerIndex, int importance, int priority) : IComparable<DescriptorIndexer>
{
/// <summary>
/// Represents an indexer for handler descriptors, containing importance and priority information.
/// Index of this descriptor when it was added to router
/// </summary>
public readonly struct DescriptorIndexer(int routerIndex, int importance, int priority) : IComparable<DescriptorIndexer>
public readonly int RouterIndex = routerIndex;
/// <summary>
/// Of this handlert type
/// </summary>
public readonly int Importance = importance;
/// <summary>
/// The priority of the handler.
/// </summary>
public readonly int Priority = priority;
/// <summary>
/// Initializes a new instance of the <see cref="DescriptorIndexer"/> struct from a handler attribute.
/// </summary>
/// <param name="routerIndex"></param>
/// <param name="pollingHandler">The handler attribute.</param>
public DescriptorIndexer(int routerIndex, UpdateHandlerAttributeBase pollingHandler)
: this(routerIndex, pollingHandler.Importance, pollingHandler.Priority) { }
/// <summary>
/// Returns a new <see cref="DescriptorIndexer"/> with updated priority.
/// </summary>
/// <param name="priority">The new priority value.</param>
/// <returns>A new <see cref="DescriptorIndexer"/> instance.</returns>
public DescriptorIndexer UpdatePriority(int priority)
=> new DescriptorIndexer(RouterIndex, Importance, priority);
/// <summary>
/// Returns a new <see cref="DescriptorIndexer"/> with updated importance.
/// </summary>
/// <param name="importance">The new importance value.</param>
/// <returns>A new <see cref="DescriptorIndexer"/> instance.</returns>
public DescriptorIndexer UpdateImportance(int importance)
=> new DescriptorIndexer(RouterIndex, importance, Priority);
/// <summary>
/// Returns a new <see cref="DescriptorIndexer"/> with updated RouterIndex.
/// </summary>
/// <param name="routerIndex"></param>
/// <returns>A new <see cref="DescriptorIndexer"/> instance.</returns>
public DescriptorIndexer UpdateIndex(int routerIndex)
=> new DescriptorIndexer(routerIndex, Importance, Priority);
/// <summary>
/// Compares this instance to another <see cref="DescriptorIndexer"/>.
/// </summary>
/// <param name="other">The other indexer to compare to.</param>
/// <returns>An integer indicating the relative order.</returns>
public int CompareTo(DescriptorIndexer other)
{
/// <summary>
/// Index of this descriptor when it was added to router
/// </summary>
public readonly int RouterIndex = routerIndex;
int importanceCmp = Importance.CompareTo(other.Importance);
if (importanceCmp != 0)
return importanceCmp;
/// <summary>
/// Of this handlert type
/// </summary>
public readonly int Importance = importance;
int priorityCmp = Priority.CompareTo(other.Priority);
if (priorityCmp != 0)
return priorityCmp;
/// <summary>
/// The priority of the handler.
/// </summary>
public readonly int Priority = priority;
int routerIndexCmp = RouterIndex.CompareTo(other.RouterIndex);
if (routerIndexCmp != 0)
return routerIndexCmp;
/// <summary>
/// Initializes a new instance of the <see cref="DescriptorIndexer"/> struct from a handler attribute.
/// </summary>
/// <param name="routerIndex"></param>
/// <param name="pollingHandler">The handler attribute.</param>
public DescriptorIndexer(int routerIndex, UpdateHandlerAttributeBase pollingHandler)
: this(routerIndex, pollingHandler.Importance, pollingHandler.Priority) { }
return 0;
}
/// <summary>
/// Returns a new <see cref="DescriptorIndexer"/> with updated priority.
/// </summary>
/// <param name="priority">The new priority value.</param>
/// <returns>A new <see cref="DescriptorIndexer"/> instance.</returns>
public DescriptorIndexer UpdatePriority(int priority)
=> new DescriptorIndexer(RouterIndex, Importance, priority);
/// <summary>
/// Returns a new <see cref="DescriptorIndexer"/> with updated importance.
/// </summary>
/// <param name="importance">The new importance value.</param>
/// <returns>A new <see cref="DescriptorIndexer"/> instance.</returns>
public DescriptorIndexer UpdateImportance(int importance)
=> new DescriptorIndexer(RouterIndex, importance, Priority);
/// <summary>
/// Returns a new <see cref="DescriptorIndexer"/> with updated RouterIndex.
/// </summary>
/// <param name="routerIndex"></param>
/// <returns>A new <see cref="DescriptorIndexer"/> instance.</returns>
public DescriptorIndexer UpdateIndex(int routerIndex)
=> new DescriptorIndexer(routerIndex, Importance, Priority);
/// <summary>
/// Compares this instance to another <see cref="DescriptorIndexer"/>.
/// </summary>
/// <param name="other">The other indexer to compare to.</param>
/// <returns>An integer indicating the relative order.</returns>
public int CompareTo(DescriptorIndexer other)
{
int importanceCmp = Importance.CompareTo(other.Importance);
if (importanceCmp != 0)
return importanceCmp;
int priorityCmp = Priority.CompareTo(other.Priority);
if (priorityCmp != 0)
return priorityCmp;
int routerIndexCmp = RouterIndex.CompareTo(other.RouterIndex);
if (routerIndexCmp != 0)
return routerIndexCmp;
return 0;
}
/// <summary>
/// Returns a string representation of the indexer.
/// </summary>
/// <returns>A string in the format (C:importance, P:priority).</returns>
public override string ToString()
{
return string.Format("(Ix: {0,2}, Im: {1,2}, Pr: {2,2})", RouterIndex, Importance, Priority);
}
/// <summary>
/// Returns a string representation of the indexer.
/// </summary>
/// <returns>A string in the format (C:importance, P:priority).</returns>
public override string ToString()
{
return string.Format("(Ix: {0,2}, Im: {1,2}, Pr: {2,2})", RouterIndex, Importance, Priority);
}
}
@@ -4,472 +4,471 @@ using Telegrator.Core.Attributes;
using Telegrator.Core.Filters;
using Telegrator.Core.Handlers;
namespace Telegrator.Core.Descriptors
namespace Telegrator.Core.Descriptors;
/// <summary>
/// Specifies the type of handler descriptor.
/// </summary>
public enum DescriptorType
{
/// <summary>
/// Specifies the type of handler descriptor.
/// General handler descriptor.
/// </summary>
public enum DescriptorType
General,
/// <summary>
/// Keyed handler descriptor (uses a service key).
/// </summary>
Keyed,
/// <summary>
/// Implicit handler descriptor.
/// </summary>
Implicit,
/// <summary>
/// Singleton handler descriptor (single instance).
/// </summary>
Singleton
}
/// <summary>
/// Describes a handler, its type, filters, and instantiation logic.
/// </summary>
public class HandlerDescriptor
{
/// <summary>
/// The type of the descriptor.
/// </summary>
public DescriptorType Type
{
/// <summary>
/// General handler descriptor.
/// </summary>
General,
/// <summary>
/// Keyed handler descriptor (uses a service key).
/// </summary>
Keyed,
/// <summary>
/// Implicit handler descriptor.
/// </summary>
Implicit,
/// <summary>
/// Singleton handler descriptor (single instance).
/// </summary>
Singleton
get;
private set;
}
/// <summary>
/// Describes a handler, its type, filters, and instantiation logic.
/// The type of the handler.
/// </summary>
public class HandlerDescriptor
public Type HandlerType
{
/// <summary>
/// The type of the descriptor.
/// </summary>
public DescriptorType Type
{
get;
private set;
}
/// <summary>
/// The type of the handler.
/// </summary>
public Type HandlerType
{
get;
private set;
}
/// <summary>
/// The update type handled by this handler.
/// </summary>
public UpdateType UpdateType
{
get;
protected set;
}
/// <summary>
/// The indexer for handler concurrency and priority.
/// </summary>
public DescriptorIndexer Indexer
{
get;
set;
}
/// <summary>
/// Gets or sets a value indicating whether to form a fallback report.
/// </summary>
public bool FormReport
{
get;
set;
}
/// <summary>
/// The set of filters associated with this handler.
/// </summary>
public DescriptorFiltersSet? Filters
{
get;
protected set;
}
/// <summary>
/// Gets or sets the aspects configuration for this handler.
/// Contains pre and post-execution processors if the handler uses the aspect system.
/// </summary>
public DescriptorAspectsSet? Aspects
{
get;
protected set;
}
/// <summary>
/// The service key for keyed handlers.
/// </summary>
public object? ServiceKey
{
get;
protected set;
}
/// <summary>
/// Factory for creating handler instances.
/// </summary>
public Func<UpdateHandlerBase>? InstanceFactory
{
get;
protected set;
}
/// <summary>
/// Singleton instance of the handler, if applicable.
/// </summary>
public UpdateHandlerBase? SingletonInstance
{
get;
protected set;
}
/// <summary>
/// Display string for the handler (for debugging or logging).
/// </summary>
public string? DisplayString
{
get;
set;
}
/// <summary>
/// Gets or sets a function for 'lazy' handlers initialization
/// </summary>
public Action<UpdateHandlerBase>? LazyInitialization
{
get;
set;
}
/// <summary>
/// Initializes a new instance of the <see cref="HandlerDescriptor"/> class with the specified descriptor type and handler type.
/// Automatically inspects the handler type to extract attributes, filters, and configuration.
/// </summary>
/// <param name="descriptorType">The type of the descriptor</param>
/// <param name="handlerType">The type of the handler to describe</param>
/// <param name="dontInspect"></param>
/// <exception cref="ArgumentException">Thrown when the handler type is not compatible with the expected handler type</exception>
public HandlerDescriptor(DescriptorType descriptorType, Type handlerType, bool dontInspect = false)
{
Type = descriptorType;
HandlerType = handlerType;
Filters = new DescriptorFiltersSet(null, null, null);
if (dontInspect)
return;
UpdateHandlerAttributeBase handlerAttribute = HandlerInspector.GetHandlerAttribute(handlerType);
if (handlerAttribute.ExpectingHandlerType != null && !handlerAttribute.ExpectingHandlerType.Contains(handlerType.BaseType))
throw new ArgumentException(string.Format("This handler attribute cannot be attached to this class. Attribute can be attached on next handlers : {0}", string.Join(", ", handlerAttribute.ExpectingHandlerType.AsEnumerable())));
StateKeeperAttributeBase? stateKeeperAttribute = HandlerInspector.GetStateKeeperAttribute(handlerType);
IFilter<Update>[] filters = HandlerInspector.GetFilterAttributes(handlerType, handlerAttribute.Type).ToArray();
UpdateType = handlerAttribute.Type;
Indexer = handlerAttribute.GetIndexer();
FormReport = handlerAttribute.FormReport;
Filters = new DescriptorFiltersSet(handlerAttribute, stateKeeperAttribute, filters);
Aspects = HandlerInspector.GetAspects(handlerType);
DisplayString = HandlerInspector.GetDisplayName(handlerType);
}
/// <summary>
/// Initializes a new instance of the <see cref="HandlerDescriptor"/> class as a keyed handler with the specified service key.
/// </summary>
/// <param name="handlerType">The type of the handler to describe</param>
/// <param name="serviceKey">The service key for dependency injection</param>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="serviceKey"/> is null</exception>
public HandlerDescriptor(Type handlerType, object serviceKey) : this(DescriptorType.Keyed, handlerType)
{
ServiceKey = serviceKey ?? throw new ArgumentNullException(nameof(serviceKey));
}
/// <summary>
/// Initializes a new instance of the <see cref="HandlerDescriptor"/> class with all basic properties.
/// </summary>
/// <param name="type">The type of the descriptor</param>
/// <param name="handlerType">The type of the handler</param>
/// <param name="updateType">The type of update this handler processes</param>
/// <param name="indexer">The indexer for handler concurrency and priority</param>
/// <param name="filters">The set of filters associated with this handler</param>
public HandlerDescriptor(DescriptorType type, Type handlerType, UpdateType updateType, DescriptorIndexer indexer, DescriptorFiltersSet filters)
{
Type = type;
HandlerType = handlerType;
UpdateType = updateType;
Indexer = indexer;
Filters = filters;
}
/// <summary>
/// Initializes a new instance of the <see cref="HandlerDescriptor"/> class with singleton instance support.
/// </summary>
/// <param name="type">The type of the descriptor</param>
/// <param name="handlerType">The type of the handler</param>
/// <param name="updateType">The type of update this handler processes</param>
/// <param name="indexer">The indexer for handler concurrency and priority</param>
/// <param name="filters">The set of filters associated with this handler</param>
/// <param name="serviceKey">The service key for dependency injection</param>
/// <param name="singletonInstance">The singleton instance of the handler</param>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="serviceKey"/> or <paramref name="singletonInstance"/> is null</exception>
public HandlerDescriptor(DescriptorType type, Type handlerType, UpdateType updateType, DescriptorIndexer indexer, DescriptorFiltersSet filters, object serviceKey, UpdateHandlerBase singletonInstance)
{
Type = type;
HandlerType = handlerType;
UpdateType = updateType;
Indexer = indexer;
Filters = filters;
ServiceKey = serviceKey ?? throw new ArgumentNullException(nameof(serviceKey));
SingletonInstance = singletonInstance ?? throw new ArgumentNullException(nameof(singletonInstance));
}
/// <summary>
/// Initializes a new instance of the <see cref="HandlerDescriptor"/> class with instance factory support.
/// </summary>
/// <param name="type">The type of the descriptor</param>
/// <param name="handlerType">The type of the handler</param>
/// <param name="updateType">The type of update this handler processes</param>
/// <param name="indexer">The indexer for handler concurrency and priority</param>
/// <param name="filters">The set of filters associated with this handler</param>
/// <param name="instanceFactory">Factory for creating handler instances</param>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="instanceFactory"/> is null</exception>
public HandlerDescriptor(DescriptorType type, Type handlerType, UpdateType updateType, DescriptorIndexer indexer, DescriptorFiltersSet filters, Func<UpdateHandlerBase> instanceFactory)
{
Type = type;
HandlerType = handlerType;
UpdateType = updateType;
Indexer = indexer;
Filters = filters;
InstanceFactory = instanceFactory ?? throw new ArgumentNullException(nameof(instanceFactory));
}
/// <summary>
/// Initializes a new instance of the <see cref="HandlerDescriptor"/> class with service key and instance factory support.
/// </summary>
/// <param name="type">The type of the descriptor</param>
/// <param name="handlerType">The type of the handler</param>
/// <param name="updateType">The type of update this handler processes</param>
/// <param name="indexer">The indexer for handler concurrency and priority</param>
/// <param name="filters">The set of filters associated with this handler</param>
/// <param name="serviceKey">The service key for dependency injection</param>
/// <param name="instanceFactory">Factory for creating handler instances</param>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="serviceKey"/> or <paramref name="instanceFactory"/> is null</exception>
public HandlerDescriptor(DescriptorType type, Type handlerType, UpdateType updateType, DescriptorIndexer indexer, DescriptorFiltersSet filters, object serviceKey, Func<UpdateHandlerBase> instanceFactory)
{
Type = type;
HandlerType = handlerType;
UpdateType = updateType;
Indexer = indexer;
Filters = filters;
ServiceKey = serviceKey ?? throw new ArgumentNullException(nameof(serviceKey));
InstanceFactory = instanceFactory ?? throw new ArgumentNullException(nameof(instanceFactory));
}
/// <summary>
/// Initializes a new instance of the <see cref="HandlerDescriptor"/> class with polling handler attribute and filters.
/// </summary>
/// <param name="type">The type of the descriptor</param>
/// <param name="handlerType">The type of the handler</param>
/// <param name="pollingHandlerAttribute">The polling handler attribute containing configuration</param>
/// <param name="filters">Optional array of filters to apply</param>
/// <param name="stateKeepFilter">Optional state keeping filter</param>
public HandlerDescriptor(DescriptorType type, Type handlerType, UpdateHandlerAttributeBase pollingHandlerAttribute, IFilter<Update>[]? filters, IFilter<Update>? stateKeepFilter)
{
Type = type;
HandlerType = handlerType;
UpdateType = pollingHandlerAttribute.Type;
Indexer = pollingHandlerAttribute.GetIndexer();
Filters = new DescriptorFiltersSet(pollingHandlerAttribute, stateKeepFilter, filters);
}
/// <summary>
/// Initializes a new instance of the <see cref="HandlerDescriptor"/> class with polling handler attribute, filters, and singleton instance.
/// </summary>
/// <param name="type">The type of the descriptor</param>
/// <param name="handlerType">The type of the handler</param>
/// <param name="pollingHandlerAttribute">The polling handler attribute containing configuration</param>
/// <param name="filters">Optional array of filters to apply</param>
/// <param name="stateKeepFilter">Optional state keeping filter</param>
/// <param name="serviceKey">The service key for dependency injection</param>
/// <param name="singletonInstance">The singleton instance of the handler</param>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="serviceKey"/> or <paramref name="singletonInstance"/> is null</exception>
public HandlerDescriptor(DescriptorType type, Type handlerType, UpdateHandlerAttributeBase pollingHandlerAttribute, IFilter<Update>[]? filters, IFilter<Update>? stateKeepFilter, object serviceKey, UpdateHandlerBase singletonInstance)
{
Type = type;
HandlerType = handlerType;
UpdateType = pollingHandlerAttribute.Type;
Indexer = pollingHandlerAttribute.GetIndexer();
Filters = new DescriptorFiltersSet(pollingHandlerAttribute, stateKeepFilter, filters);
ServiceKey = serviceKey ?? throw new ArgumentNullException(nameof(serviceKey));
SingletonInstance = singletonInstance ?? throw new ArgumentNullException(nameof(singletonInstance));
}
/// <summary>
/// Initializes a new instance of the <see cref="HandlerDescriptor"/> class with polling handler attribute, filters, and instance factory.
/// </summary>
/// <param name="type">The type of the descriptor</param>
/// <param name="handlerType">The type of the handler</param>
/// <param name="pollingHandlerAttribute">The polling handler attribute containing configuration</param>
/// <param name="filters">Optional array of filters to apply</param>
/// <param name="stateKeepFilter">Optional state keeping filter</param>
/// <param name="instanceFactory">Factory for creating handler instances</param>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="instanceFactory"/> is null</exception>
public HandlerDescriptor(DescriptorType type, Type handlerType, UpdateHandlerAttributeBase pollingHandlerAttribute, IFilter<Update>[]? filters, IFilter<Update>? stateKeepFilter, Func<UpdateHandlerBase> instanceFactory)
{
Type = type;
HandlerType = handlerType;
UpdateType = pollingHandlerAttribute.Type;
Indexer = pollingHandlerAttribute.GetIndexer();
Filters = new DescriptorFiltersSet(pollingHandlerAttribute, stateKeepFilter, filters);
InstanceFactory = instanceFactory ?? throw new ArgumentNullException(nameof(instanceFactory));
}
/// <summary>
/// Initializes a new instance of the <see cref="HandlerDescriptor"/> class with polling handler attribute, filters, service key, and instance factory.
/// </summary>
/// <param name="type">The type of the descriptor</param>
/// <param name="handlerType">The type of the handler</param>
/// <param name="pollingHandlerAttribute">The polling handler attribute containing configuration</param>
/// <param name="filters">Optional array of filters to apply</param>
/// <param name="stateKeepFilter">Optional state keeping filter</param>
/// <param name="serviceKey">The service key for dependency injection</param>
/// <param name="instanceFactory">Factory for creating handler instances</param>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="serviceKey"/> or <paramref name="instanceFactory"/> is null</exception>
public HandlerDescriptor(DescriptorType type, Type handlerType, UpdateHandlerAttributeBase pollingHandlerAttribute, IFilter<Update>[]? filters, IFilter<Update>? stateKeepFilter, object serviceKey, Func<UpdateHandlerBase> instanceFactory)
{
Type = type;
HandlerType = handlerType;
UpdateType = pollingHandlerAttribute.Type;
Indexer = pollingHandlerAttribute.GetIndexer();
Filters = new DescriptorFiltersSet(pollingHandlerAttribute, stateKeepFilter, filters);
ServiceKey = serviceKey ?? throw new ArgumentNullException(nameof(serviceKey));
InstanceFactory = instanceFactory ?? throw new ArgumentNullException(nameof(instanceFactory));
}
/// <summary>
/// Initializes a new instance of the <see cref="HandlerDescriptor"/> class with validation filter support.
/// </summary>
/// <param name="type">The type of the descriptor</param>
/// <param name="handlerType">The type of the handler</param>
/// <param name="updateType">The type of update this handler processes</param>
/// <param name="indexer">The indexer for handler concurrency and priority</param>
/// <param name="validateFilter">Optional validation filter</param>
/// <param name="filters">Optional array of filters to apply</param>
/// <param name="stateKeepFilter">Optional state keeping filter</param>
public HandlerDescriptor(DescriptorType type, Type handlerType, UpdateType updateType, DescriptorIndexer indexer, IFilter<Update>? validateFilter, IFilter<Update>[]? filters, IFilter<Update>? stateKeepFilter)
{
Type = type;
HandlerType = handlerType;
UpdateType = updateType;
Indexer = indexer;
Filters = new DescriptorFiltersSet(validateFilter, stateKeepFilter, filters);
}
/// <summary>
/// Initializes a new instance of the <see cref="HandlerDescriptor"/> class with validation filter and singleton instance support.
/// </summary>
/// <param name="type">The type of the descriptor</param>
/// <param name="handlerType">The type of the handler</param>
/// <param name="updateType">The type of update this handler processes</param>
/// <param name="indexer">The indexer for handler concurrency and priority</param>
/// <param name="validateFilter">Optional validation filter</param>
/// <param name="filters">Optional array of filters to apply</param>
/// <param name="stateKeepFilter">Optional state keeping filter</param>
/// <param name="serviceKey">The service key for dependency injection</param>
/// <param name="singletonInstance">The singleton instance of the handler</param>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="serviceKey"/> or <paramref name="singletonInstance"/> is null</exception>
public HandlerDescriptor(DescriptorType type, Type handlerType, UpdateType updateType, DescriptorIndexer indexer, IFilter<Update>? validateFilter, IFilter<Update>[]? filters, IFilter<Update>? stateKeepFilter, object serviceKey, UpdateHandlerBase singletonInstance)
{
Type = type;
HandlerType = handlerType;
UpdateType = updateType;
Indexer = indexer;
Filters = new DescriptorFiltersSet(validateFilter, stateKeepFilter, filters);
ServiceKey = serviceKey ?? throw new ArgumentNullException(nameof(serviceKey));
SingletonInstance = singletonInstance ?? throw new ArgumentNullException(nameof(singletonInstance));
}
/// <summary>
/// Initializes a new instance of the <see cref="HandlerDescriptor"/> class with validation filter and instance factory support.
/// </summary>
/// <param name="type">The type of the descriptor</param>
/// <param name="handlerType">The type of the handler</param>
/// <param name="updateType">The type of update this handler processes</param>
/// <param name="indexer">The indexer for handler concurrency and priority</param>
/// <param name="validateFilter">Optional validation filter</param>
/// <param name="filters">Optional array of filters to apply</param>
/// <param name="stateKeepFilter">Optional state keeping filter</param>
/// <param name="instanceFactory">Factory for creating handler instances</param>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="instanceFactory"/> is null</exception>
public HandlerDescriptor(DescriptorType type, Type handlerType, UpdateType updateType, DescriptorIndexer indexer, IFilter<Update>? validateFilter, IFilter<Update>[]? filters, IFilter<Update>? stateKeepFilter, Func<UpdateHandlerBase> instanceFactory)
{
Type = type;
HandlerType = handlerType;
UpdateType = updateType;
Indexer = indexer;
Filters = new DescriptorFiltersSet(validateFilter, stateKeepFilter, filters);
InstanceFactory = instanceFactory ?? throw new ArgumentNullException(nameof(instanceFactory));
}
/// <summary>
/// Initializes a new instance of the <see cref="HandlerDescriptor"/> class with validation filter, service key, and instance factory support.
/// </summary>
/// <param name="type">The type of the descriptor</param>
/// <param name="handlerType">The type of the handler</param>
/// <param name="updateType">The type of update this handler processes</param>
/// <param name="indexer">The indexer for handler concurrency and priority</param>
/// <param name="validateFilter">Optional validation filter</param>
/// <param name="filters">Optional array of filters to apply</param>
/// <param name="stateKeepFilter">Optional state keeping filter</param>
/// <param name="serviceKey">The service key for dependency injection</param>
/// <param name="instanceFactory">Factory for creating handler instances</param>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="serviceKey"/> or <paramref name="instanceFactory"/> is null</exception>
public HandlerDescriptor(DescriptorType type, Type handlerType, UpdateType updateType, DescriptorIndexer indexer, IFilter<Update>? validateFilter, IFilter<Update>[]? filters, IFilter<Update>? stateKeepFilter, object serviceKey, Func<UpdateHandlerBase> instanceFactory)
{
Type = type;
HandlerType = handlerType;
UpdateType = updateType;
Indexer = indexer;
Filters = new DescriptorFiltersSet(validateFilter, stateKeepFilter, filters);
ServiceKey = serviceKey ?? throw new ArgumentNullException(nameof(serviceKey));
InstanceFactory = instanceFactory ?? throw new ArgumentNullException(nameof(instanceFactory));
}
/// <summary>
/// Sets singleton instance of this descriptor
/// Throws exception if instance already set
/// </summary>
/// <param name="instance"></param>
/// <exception cref="Exception"></exception>
public void SetInstance(UpdateHandlerBase instance)
{
if (SingletonInstance != null)
throw new Exception();
SingletonInstance = instance;
}
/// <summary>
/// Tries to set singleton instance of this descriptor
/// </summary>
/// <param name="instance"></param>
/// <returns></returns>
public bool TrySetInstance(UpdateHandlerBase instance)
{
if (SingletonInstance != null)
return false;
SingletonInstance = instance;
return true;
}
/// <inheritdoc/>
public override string ToString()
=> DisplayString ?? HandlerType.Name;
get;
private set;
}
/// <summary>
/// The update type handled by this handler.
/// </summary>
public UpdateType UpdateType
{
get;
protected set;
}
/// <summary>
/// The indexer for handler concurrency and priority.
/// </summary>
public DescriptorIndexer Indexer
{
get;
set;
}
/// <summary>
/// Gets or sets a value indicating whether to form a fallback report.
/// </summary>
public bool FormReport
{
get;
set;
}
/// <summary>
/// The set of filters associated with this handler.
/// </summary>
public DescriptorFiltersSet? Filters
{
get;
protected set;
}
/// <summary>
/// Gets or sets the aspects configuration for this handler.
/// Contains pre and post-execution processors if the handler uses the aspect system.
/// </summary>
public DescriptorAspectsSet? Aspects
{
get;
protected set;
}
/// <summary>
/// The service key for keyed handlers.
/// </summary>
public object? ServiceKey
{
get;
protected set;
}
/// <summary>
/// Factory for creating handler instances.
/// </summary>
public Func<UpdateHandlerBase>? InstanceFactory
{
get;
protected set;
}
/// <summary>
/// Singleton instance of the handler, if applicable.
/// </summary>
public UpdateHandlerBase? SingletonInstance
{
get;
protected set;
}
/// <summary>
/// Display string for the handler (for debugging or logging).
/// </summary>
public string? DisplayString
{
get;
set;
}
/// <summary>
/// Gets or sets a function for 'lazy' handlers initialization
/// </summary>
public Action<UpdateHandlerBase>? LazyInitialization
{
get;
set;
}
/// <summary>
/// Initializes a new instance of the <see cref="HandlerDescriptor"/> class with the specified descriptor type and handler type.
/// Automatically inspects the handler type to extract attributes, filters, and configuration.
/// </summary>
/// <param name="descriptorType">The type of the descriptor</param>
/// <param name="handlerType">The type of the handler to describe</param>
/// <param name="dontInspect"></param>
/// <exception cref="ArgumentException">Thrown when the handler type is not compatible with the expected handler type</exception>
public HandlerDescriptor(DescriptorType descriptorType, Type handlerType, bool dontInspect = false)
{
Type = descriptorType;
HandlerType = handlerType;
Filters = new DescriptorFiltersSet(null, null, null);
if (dontInspect)
return;
UpdateHandlerAttributeBase handlerAttribute = HandlerInspector.GetHandlerAttribute(handlerType);
if (handlerAttribute.ExpectingHandlerType != null && !handlerAttribute.ExpectingHandlerType.Contains(handlerType.BaseType))
throw new ArgumentException(string.Format("This handler attribute cannot be attached to this class. Attribute can be attached on next handlers : {0}", string.Join(", ", handlerAttribute.ExpectingHandlerType.AsEnumerable())));
IFilter<Update>? stateKeeperAttribute = HandlerInspector.GetStateKeeperAttribute(handlerType);
IFilter<Update>[] filters = HandlerInspector.GetFilterAttributes(handlerType, handlerAttribute.Type).ToArray();
UpdateType = handlerAttribute.Type;
Indexer = handlerAttribute.GetIndexer();
FormReport = handlerAttribute.FormReport;
Filters = new DescriptorFiltersSet(handlerAttribute, stateKeeperAttribute, filters);
Aspects = HandlerInspector.GetAspects(handlerType);
DisplayString = HandlerInspector.GetDisplayName(handlerType);
}
/// <summary>
/// Initializes a new instance of the <see cref="HandlerDescriptor"/> class as a keyed handler with the specified service key.
/// </summary>
/// <param name="handlerType">The type of the handler to describe</param>
/// <param name="serviceKey">The service key for dependency injection</param>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="serviceKey"/> is null</exception>
public HandlerDescriptor(Type handlerType, object serviceKey) : this(DescriptorType.Keyed, handlerType)
{
ServiceKey = serviceKey ?? throw new ArgumentNullException(nameof(serviceKey));
}
/// <summary>
/// Initializes a new instance of the <see cref="HandlerDescriptor"/> class with all basic properties.
/// </summary>
/// <param name="type">The type of the descriptor</param>
/// <param name="handlerType">The type of the handler</param>
/// <param name="updateType">The type of update this handler processes</param>
/// <param name="indexer">The indexer for handler concurrency and priority</param>
/// <param name="filters">The set of filters associated with this handler</param>
public HandlerDescriptor(DescriptorType type, Type handlerType, UpdateType updateType, DescriptorIndexer indexer, DescriptorFiltersSet filters)
{
Type = type;
HandlerType = handlerType;
UpdateType = updateType;
Indexer = indexer;
Filters = filters;
}
/// <summary>
/// Initializes a new instance of the <see cref="HandlerDescriptor"/> class with singleton instance support.
/// </summary>
/// <param name="type">The type of the descriptor</param>
/// <param name="handlerType">The type of the handler</param>
/// <param name="updateType">The type of update this handler processes</param>
/// <param name="indexer">The indexer for handler concurrency and priority</param>
/// <param name="filters">The set of filters associated with this handler</param>
/// <param name="serviceKey">The service key for dependency injection</param>
/// <param name="singletonInstance">The singleton instance of the handler</param>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="serviceKey"/> or <paramref name="singletonInstance"/> is null</exception>
public HandlerDescriptor(DescriptorType type, Type handlerType, UpdateType updateType, DescriptorIndexer indexer, DescriptorFiltersSet filters, object serviceKey, UpdateHandlerBase singletonInstance)
{
Type = type;
HandlerType = handlerType;
UpdateType = updateType;
Indexer = indexer;
Filters = filters;
ServiceKey = serviceKey ?? throw new ArgumentNullException(nameof(serviceKey));
SingletonInstance = singletonInstance ?? throw new ArgumentNullException(nameof(singletonInstance));
}
/// <summary>
/// Initializes a new instance of the <see cref="HandlerDescriptor"/> class with instance factory support.
/// </summary>
/// <param name="type">The type of the descriptor</param>
/// <param name="handlerType">The type of the handler</param>
/// <param name="updateType">The type of update this handler processes</param>
/// <param name="indexer">The indexer for handler concurrency and priority</param>
/// <param name="filters">The set of filters associated with this handler</param>
/// <param name="instanceFactory">Factory for creating handler instances</param>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="instanceFactory"/> is null</exception>
public HandlerDescriptor(DescriptorType type, Type handlerType, UpdateType updateType, DescriptorIndexer indexer, DescriptorFiltersSet filters, Func<UpdateHandlerBase> instanceFactory)
{
Type = type;
HandlerType = handlerType;
UpdateType = updateType;
Indexer = indexer;
Filters = filters;
InstanceFactory = instanceFactory ?? throw new ArgumentNullException(nameof(instanceFactory));
}
/// <summary>
/// Initializes a new instance of the <see cref="HandlerDescriptor"/> class with service key and instance factory support.
/// </summary>
/// <param name="type">The type of the descriptor</param>
/// <param name="handlerType">The type of the handler</param>
/// <param name="updateType">The type of update this handler processes</param>
/// <param name="indexer">The indexer for handler concurrency and priority</param>
/// <param name="filters">The set of filters associated with this handler</param>
/// <param name="serviceKey">The service key for dependency injection</param>
/// <param name="instanceFactory">Factory for creating handler instances</param>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="serviceKey"/> or <paramref name="instanceFactory"/> is null</exception>
public HandlerDescriptor(DescriptorType type, Type handlerType, UpdateType updateType, DescriptorIndexer indexer, DescriptorFiltersSet filters, object serviceKey, Func<UpdateHandlerBase> instanceFactory)
{
Type = type;
HandlerType = handlerType;
UpdateType = updateType;
Indexer = indexer;
Filters = filters;
ServiceKey = serviceKey ?? throw new ArgumentNullException(nameof(serviceKey));
InstanceFactory = instanceFactory ?? throw new ArgumentNullException(nameof(instanceFactory));
}
/// <summary>
/// Initializes a new instance of the <see cref="HandlerDescriptor"/> class with polling handler attribute and filters.
/// </summary>
/// <param name="type">The type of the descriptor</param>
/// <param name="handlerType">The type of the handler</param>
/// <param name="pollingHandlerAttribute">The polling handler attribute containing configuration</param>
/// <param name="filters">Optional array of filters to apply</param>
/// <param name="stateKeepFilter">Optional state keeping filter</param>
public HandlerDescriptor(DescriptorType type, Type handlerType, UpdateHandlerAttributeBase pollingHandlerAttribute, IFilter<Update>[]? filters, IFilter<Update>? stateKeepFilter)
{
Type = type;
HandlerType = handlerType;
UpdateType = pollingHandlerAttribute.Type;
Indexer = pollingHandlerAttribute.GetIndexer();
Filters = new DescriptorFiltersSet(pollingHandlerAttribute, stateKeepFilter, filters);
}
/// <summary>
/// Initializes a new instance of the <see cref="HandlerDescriptor"/> class with polling handler attribute, filters, and singleton instance.
/// </summary>
/// <param name="type">The type of the descriptor</param>
/// <param name="handlerType">The type of the handler</param>
/// <param name="pollingHandlerAttribute">The polling handler attribute containing configuration</param>
/// <param name="filters">Optional array of filters to apply</param>
/// <param name="stateKeepFilter">Optional state keeping filter</param>
/// <param name="serviceKey">The service key for dependency injection</param>
/// <param name="singletonInstance">The singleton instance of the handler</param>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="serviceKey"/> or <paramref name="singletonInstance"/> is null</exception>
public HandlerDescriptor(DescriptorType type, Type handlerType, UpdateHandlerAttributeBase pollingHandlerAttribute, IFilter<Update>[]? filters, IFilter<Update>? stateKeepFilter, object serviceKey, UpdateHandlerBase singletonInstance)
{
Type = type;
HandlerType = handlerType;
UpdateType = pollingHandlerAttribute.Type;
Indexer = pollingHandlerAttribute.GetIndexer();
Filters = new DescriptorFiltersSet(pollingHandlerAttribute, stateKeepFilter, filters);
ServiceKey = serviceKey ?? throw new ArgumentNullException(nameof(serviceKey));
SingletonInstance = singletonInstance ?? throw new ArgumentNullException(nameof(singletonInstance));
}
/// <summary>
/// Initializes a new instance of the <see cref="HandlerDescriptor"/> class with polling handler attribute, filters, and instance factory.
/// </summary>
/// <param name="type">The type of the descriptor</param>
/// <param name="handlerType">The type of the handler</param>
/// <param name="pollingHandlerAttribute">The polling handler attribute containing configuration</param>
/// <param name="filters">Optional array of filters to apply</param>
/// <param name="stateKeepFilter">Optional state keeping filter</param>
/// <param name="instanceFactory">Factory for creating handler instances</param>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="instanceFactory"/> is null</exception>
public HandlerDescriptor(DescriptorType type, Type handlerType, UpdateHandlerAttributeBase pollingHandlerAttribute, IFilter<Update>[]? filters, IFilter<Update>? stateKeepFilter, Func<UpdateHandlerBase> instanceFactory)
{
Type = type;
HandlerType = handlerType;
UpdateType = pollingHandlerAttribute.Type;
Indexer = pollingHandlerAttribute.GetIndexer();
Filters = new DescriptorFiltersSet(pollingHandlerAttribute, stateKeepFilter, filters);
InstanceFactory = instanceFactory ?? throw new ArgumentNullException(nameof(instanceFactory));
}
/// <summary>
/// Initializes a new instance of the <see cref="HandlerDescriptor"/> class with polling handler attribute, filters, service key, and instance factory.
/// </summary>
/// <param name="type">The type of the descriptor</param>
/// <param name="handlerType">The type of the handler</param>
/// <param name="pollingHandlerAttribute">The polling handler attribute containing configuration</param>
/// <param name="filters">Optional array of filters to apply</param>
/// <param name="stateKeepFilter">Optional state keeping filter</param>
/// <param name="serviceKey">The service key for dependency injection</param>
/// <param name="instanceFactory">Factory for creating handler instances</param>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="serviceKey"/> or <paramref name="instanceFactory"/> is null</exception>
public HandlerDescriptor(DescriptorType type, Type handlerType, UpdateHandlerAttributeBase pollingHandlerAttribute, IFilter<Update>[]? filters, IFilter<Update>? stateKeepFilter, object serviceKey, Func<UpdateHandlerBase> instanceFactory)
{
Type = type;
HandlerType = handlerType;
UpdateType = pollingHandlerAttribute.Type;
Indexer = pollingHandlerAttribute.GetIndexer();
Filters = new DescriptorFiltersSet(pollingHandlerAttribute, stateKeepFilter, filters);
ServiceKey = serviceKey ?? throw new ArgumentNullException(nameof(serviceKey));
InstanceFactory = instanceFactory ?? throw new ArgumentNullException(nameof(instanceFactory));
}
/// <summary>
/// Initializes a new instance of the <see cref="HandlerDescriptor"/> class with validation filter support.
/// </summary>
/// <param name="type">The type of the descriptor</param>
/// <param name="handlerType">The type of the handler</param>
/// <param name="updateType">The type of update this handler processes</param>
/// <param name="indexer">The indexer for handler concurrency and priority</param>
/// <param name="validateFilter">Optional validation filter</param>
/// <param name="filters">Optional array of filters to apply</param>
/// <param name="stateKeepFilter">Optional state keeping filter</param>
public HandlerDescriptor(DescriptorType type, Type handlerType, UpdateType updateType, DescriptorIndexer indexer, IFilter<Update>? validateFilter, IFilter<Update>[]? filters, IFilter<Update>? stateKeepFilter)
{
Type = type;
HandlerType = handlerType;
UpdateType = updateType;
Indexer = indexer;
Filters = new DescriptorFiltersSet(validateFilter, stateKeepFilter, filters);
}
/// <summary>
/// Initializes a new instance of the <see cref="HandlerDescriptor"/> class with validation filter and singleton instance support.
/// </summary>
/// <param name="type">The type of the descriptor</param>
/// <param name="handlerType">The type of the handler</param>
/// <param name="updateType">The type of update this handler processes</param>
/// <param name="indexer">The indexer for handler concurrency and priority</param>
/// <param name="validateFilter">Optional validation filter</param>
/// <param name="filters">Optional array of filters to apply</param>
/// <param name="stateKeepFilter">Optional state keeping filter</param>
/// <param name="serviceKey">The service key for dependency injection</param>
/// <param name="singletonInstance">The singleton instance of the handler</param>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="serviceKey"/> or <paramref name="singletonInstance"/> is null</exception>
public HandlerDescriptor(DescriptorType type, Type handlerType, UpdateType updateType, DescriptorIndexer indexer, IFilter<Update>? validateFilter, IFilter<Update>[]? filters, IFilter<Update>? stateKeepFilter, object serviceKey, UpdateHandlerBase singletonInstance)
{
Type = type;
HandlerType = handlerType;
UpdateType = updateType;
Indexer = indexer;
Filters = new DescriptorFiltersSet(validateFilter, stateKeepFilter, filters);
ServiceKey = serviceKey ?? throw new ArgumentNullException(nameof(serviceKey));
SingletonInstance = singletonInstance ?? throw new ArgumentNullException(nameof(singletonInstance));
}
/// <summary>
/// Initializes a new instance of the <see cref="HandlerDescriptor"/> class with validation filter and instance factory support.
/// </summary>
/// <param name="type">The type of the descriptor</param>
/// <param name="handlerType">The type of the handler</param>
/// <param name="updateType">The type of update this handler processes</param>
/// <param name="indexer">The indexer for handler concurrency and priority</param>
/// <param name="validateFilter">Optional validation filter</param>
/// <param name="filters">Optional array of filters to apply</param>
/// <param name="stateKeepFilter">Optional state keeping filter</param>
/// <param name="instanceFactory">Factory for creating handler instances</param>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="instanceFactory"/> is null</exception>
public HandlerDescriptor(DescriptorType type, Type handlerType, UpdateType updateType, DescriptorIndexer indexer, IFilter<Update>? validateFilter, IFilter<Update>[]? filters, IFilter<Update>? stateKeepFilter, Func<UpdateHandlerBase> instanceFactory)
{
Type = type;
HandlerType = handlerType;
UpdateType = updateType;
Indexer = indexer;
Filters = new DescriptorFiltersSet(validateFilter, stateKeepFilter, filters);
InstanceFactory = instanceFactory ?? throw new ArgumentNullException(nameof(instanceFactory));
}
/// <summary>
/// Initializes a new instance of the <see cref="HandlerDescriptor"/> class with validation filter, service key, and instance factory support.
/// </summary>
/// <param name="type">The type of the descriptor</param>
/// <param name="handlerType">The type of the handler</param>
/// <param name="updateType">The type of update this handler processes</param>
/// <param name="indexer">The indexer for handler concurrency and priority</param>
/// <param name="validateFilter">Optional validation filter</param>
/// <param name="filters">Optional array of filters to apply</param>
/// <param name="stateKeepFilter">Optional state keeping filter</param>
/// <param name="serviceKey">The service key for dependency injection</param>
/// <param name="instanceFactory">Factory for creating handler instances</param>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="serviceKey"/> or <paramref name="instanceFactory"/> is null</exception>
public HandlerDescriptor(DescriptorType type, Type handlerType, UpdateType updateType, DescriptorIndexer indexer, IFilter<Update>? validateFilter, IFilter<Update>[]? filters, IFilter<Update>? stateKeepFilter, object serviceKey, Func<UpdateHandlerBase> instanceFactory)
{
Type = type;
HandlerType = handlerType;
UpdateType = updateType;
Indexer = indexer;
Filters = new DescriptorFiltersSet(validateFilter, stateKeepFilter, filters);
ServiceKey = serviceKey ?? throw new ArgumentNullException(nameof(serviceKey));
InstanceFactory = instanceFactory ?? throw new ArgumentNullException(nameof(instanceFactory));
}
/// <summary>
/// Sets singleton instance of this descriptor
/// Throws exception if instance already set
/// </summary>
/// <param name="instance"></param>
/// <exception cref="Exception"></exception>
public void SetInstance(UpdateHandlerBase instance)
{
if (SingletonInstance != null)
throw new Exception();
SingletonInstance = instance;
}
/// <summary>
/// Tries to set singleton instance of this descriptor
/// </summary>
/// <param name="instance"></param>
/// <returns></returns>
public bool TrySetInstance(UpdateHandlerBase instance)
{
if (SingletonInstance != null)
return false;
SingletonInstance = instance;
return true;
}
/// <inheritdoc/>
public override string ToString()
=> DisplayString ?? HandlerType.Name;
}
@@ -2,158 +2,157 @@
using Telegram.Bot.Types;
using Telegram.Bot.Types.Enums;
namespace Telegrator.Core.Descriptors
namespace Telegrator.Core.Descriptors;
/// <summary>
/// The collection containing the <see cref="HandlerDescriptor"/>'s. Used to route <see cref="Update"/>'s in <see cref="IHandlersProvider"/>
/// </summary>
public sealed class HandlerDescriptorList : IEnumerable<HandlerDescriptor>
{
private readonly object _lock = new object();
private readonly SortedList<DescriptorIndexer, HandlerDescriptor> _innerCollection;
private readonly TelegratorOptions? _options;
private readonly UpdateType _handlingType;
private int count;
/// <summary>
/// The collection containing the <see cref="HandlerDescriptor"/>'s. Used to route <see cref="Update"/>'s in <see cref="IHandlersProvider"/>
/// Gets a value indicating whether the collection is read-only.
/// </summary>
public sealed class HandlerDescriptorList : IEnumerable<HandlerDescriptor>
public bool IsReadOnly { get; private set; } = false;
/// <summary>
/// Gets the <see cref="UpdateType"/> of handlers in this collection.
/// </summary>
public UpdateType HandlingType => _handlingType;
/// <summary>
/// Gets count of registered handlers in list
/// </summary>
public int Count => _innerCollection.Count;
/// <summary>
/// Gets or sets the <see cref="HandlerDescriptor"/> at the specified index.
/// </summary>
/// <param name="index"></param>
/// <returns></returns>
public HandlerDescriptor this[int index]
{
private readonly object _lock = new object();
private readonly SortedList<DescriptorIndexer, HandlerDescriptor> _innerCollection;
private readonly TelegratorOptions? _options;
private readonly UpdateType _handlingType;
get => _innerCollection.Values[index];
set => _innerCollection.Values[index] = value;
}
private int count;
/// <summary>
/// Initializes a new instance of the <see cref="HandlerDescriptorList"/> class without a specific <see cref="UpdateType"/>.
/// </summary>
public HandlerDescriptorList()
: this(UpdateType.Unknown, default) { }
/// <summary>
/// Gets a value indicating whether the collection is read-only.
/// </summary>
public bool IsReadOnly { get; private set; } = false;
/// <summary>
/// Initializes a new instance of the <see cref="HandlerDescriptorList"/> class.
/// </summary>
/// <param name="updateType">The update type for the handlers.</param>
/// <param name="options">The collecting options.</param>
public HandlerDescriptorList(UpdateType updateType, TelegratorOptions? options)
{
_innerCollection = [];
_handlingType = updateType;
_options = options;
}
/// <summary>
/// Gets the <see cref="UpdateType"/> of handlers in this collection.
/// </summary>
public UpdateType HandlingType => _handlingType;
/// <summary>
/// Gets count of registered handlers in list
/// </summary>
public int Count => _innerCollection.Count;
/// <summary>
/// Gets or sets the <see cref="HandlerDescriptor"/> at the specified index.
/// </summary>
/// <param name="index"></param>
/// <returns></returns>
public HandlerDescriptor this[int index]
/// <summary>
/// Adds a new <see cref="HandlerDescriptor"/> to the collection.
/// </summary>
/// <param name="descriptor">The handler descriptor to add.</param>
/// <exception cref="CollectionFrozenException">Thrown if the collection is frozen.</exception>
/// <exception cref="InvalidOperationException">Thrown if the update type does not match.</exception>
public void Add(HandlerDescriptor descriptor)
{
lock (_lock)
{
get => _innerCollection.Values[index];
set => _innerCollection.Values[index] = value;
}
if (IsReadOnly)
throw new CollectionFrozenException();
/// <summary>
/// Initializes a new instance of the <see cref="HandlerDescriptorList"/> class without a specific <see cref="UpdateType"/>.
/// </summary>
public HandlerDescriptorList()
: this(UpdateType.Unknown, default) { }
if (_handlingType != UpdateType.Unknown && descriptor.UpdateType != _handlingType)
throw new InvalidOperationException();
/// <summary>
/// Initializes a new instance of the <see cref="HandlerDescriptorList"/> class.
/// </summary>
/// <param name="updateType">The update type for the handlers.</param>
/// <param name="options">The collecting options.</param>
public HandlerDescriptorList(UpdateType updateType, TelegratorOptions? options)
{
_innerCollection = [];
_handlingType = updateType;
_options = options;
}
descriptor.Indexer = descriptor.Indexer.UpdateIndex(count++);
_innerCollection.Add(descriptor.Indexer, descriptor);
/// <summary>
/// Adds a new <see cref="HandlerDescriptor"/> to the collection.
/// </summary>
/// <param name="descriptor">The handler descriptor to add.</param>
/// <exception cref="CollectionFrozenException">Thrown if the collection is frozen.</exception>
/// <exception cref="InvalidOperationException">Thrown if the update type does not match.</exception>
public void Add(HandlerDescriptor descriptor)
{
lock (_lock)
{
if (IsReadOnly)
throw new CollectionFrozenException();
if (_handlingType != UpdateType.Unknown && descriptor.UpdateType != _handlingType)
throw new InvalidOperationException();
descriptor.Indexer = descriptor.Indexer.UpdateIndex(count++);
_innerCollection.Add(descriptor.Indexer, descriptor);
if (_handlingType == UpdateType.InlineQuery)
IsReadOnly = true;
}
}
/// <summary>
/// Checks if the collection contains a <see cref="HandlerDescriptor"/> with the specified <see cref="DescriptorIndexer"/>.
/// </summary>
/// <param name="indexer">The descriptor indexer.</param>
/// <returns>True if the descriptor exists; otherwise, false.</returns>
public bool ContainsKey(DescriptorIndexer indexer)
{
return _innerCollection.ContainsKey(indexer);
}
/// <summary>
/// Removes the <see cref="HandlerDescriptor"/> with the specified <see cref="DescriptorIndexer"/> from the collection.
/// </summary>
/// <param name="indexer">The descriptor indexer.</param>
/// <returns>True if the descriptor was removed; otherwise, false.</returns>
public bool Remove(DescriptorIndexer indexer)
{
lock (_lock)
{
return _innerCollection.Remove(indexer);
}
}
/// <summary>
/// Removes the <see cref="HandlerDescriptor"/> from the collection.
/// </summary>
/// <param name="descriptor"></param>
/// <returns></returns>
public bool Remove(HandlerDescriptor descriptor)
{
lock (_lock)
{
int index = _innerCollection.IndexOfValue(descriptor);
if (index == -1)
return false;
_innerCollection.RemoveAt(index);
return true;
}
}
/// <summary>
/// Removes all descriptos from the <see cref="HandlerDescriptorList"/>
/// </summary>
public void Clear()
{
lock (_lock)
{
_innerCollection.Clear();
}
}
/// <summary>
/// Freezes the <see cref="HandlerDescriptorList"/> and prohibits adding new elements to it.
/// </summary>
public void Freeze()
{
IsReadOnly = true;
}
/// <inheritdoc/>
public IEnumerator<HandlerDescriptor> GetEnumerator()
{
return _innerCollection.Values.GetEnumerator();
}
/// <inheritdoc/>
IEnumerator IEnumerable.GetEnumerator()
{
return _innerCollection.Values.GetEnumerator();
if (_handlingType == UpdateType.InlineQuery)
IsReadOnly = true;
}
}
/// <summary>
/// Checks if the collection contains a <see cref="HandlerDescriptor"/> with the specified <see cref="DescriptorIndexer"/>.
/// </summary>
/// <param name="indexer">The descriptor indexer.</param>
/// <returns>True if the descriptor exists; otherwise, false.</returns>
public bool ContainsKey(DescriptorIndexer indexer)
{
return _innerCollection.ContainsKey(indexer);
}
/// <summary>
/// Removes the <see cref="HandlerDescriptor"/> with the specified <see cref="DescriptorIndexer"/> from the collection.
/// </summary>
/// <param name="indexer">The descriptor indexer.</param>
/// <returns>True if the descriptor was removed; otherwise, false.</returns>
public bool Remove(DescriptorIndexer indexer)
{
lock (_lock)
{
return _innerCollection.Remove(indexer);
}
}
/// <summary>
/// Removes the <see cref="HandlerDescriptor"/> from the collection.
/// </summary>
/// <param name="descriptor"></param>
/// <returns></returns>
public bool Remove(HandlerDescriptor descriptor)
{
lock (_lock)
{
int index = _innerCollection.IndexOfValue(descriptor);
if (index == -1)
return false;
_innerCollection.RemoveAt(index);
return true;
}
}
/// <summary>
/// Removes all descriptos from the <see cref="HandlerDescriptorList"/>
/// </summary>
public void Clear()
{
lock (_lock)
{
_innerCollection.Clear();
}
}
/// <summary>
/// Freezes the <see cref="HandlerDescriptorList"/> and prohibits adding new elements to it.
/// </summary>
public void Freeze()
{
IsReadOnly = true;
}
/// <inheritdoc/>
public IEnumerator<HandlerDescriptor> GetEnumerator()
{
return _innerCollection.Values.GetEnumerator();
}
/// <inheritdoc/>
IEnumerator IEnumerable.GetEnumerator()
{
return _innerCollection.Values.GetEnumerator();
}
}
@@ -2,97 +2,97 @@
using System.Reflection;
using Telegram.Bot.Types;
using Telegram.Bot.Types.Enums;
using Telegrator.Annotations;
using Telegrator.Aspects;
using Telegrator.Core.Attributes;
using Telegrator.Core.Filters;
namespace Telegrator.Core.Descriptors
namespace Telegrator.Core.Descriptors;
/// <summary>
/// Provides methods for inspecting handler types and retrieving their attributes and filters.
/// </summary>
public static class HandlerInspector
{
/// <summary>
/// Provides methods for inspecting handler types and retrieving their attributes and filters.
/// Gets handler's display name
/// </summary>
public static class HandlerInspector
/// <param name="handlerType"></param>
/// <returns></returns>
public static string? GetDisplayName(MemberInfo handlerType)
{
/// <summary>
/// Gets handler's display name
/// </summary>
/// <param name="handlerType"></param>
/// <returns></returns>
public static string? GetDisplayName(MemberInfo handlerType)
return handlerType.GetCustomAttribute<DisplayNameAttribute>()?.DisplayName;
}
/// <summary>
/// Gets the handler attribute from the specified member info.
/// </summary>
/// <param name="handlerType">The member info representing the handler type.</param>
/// <returns>The handler attribute.</returns>
public static UpdateHandlerAttributeBase GetHandlerAttribute(MemberInfo handlerType)
{
// Getting polling handler attribute
IEnumerable<UpdateHandlerAttributeBase> handlerAttrs = handlerType.GetCustomAttributes<UpdateHandlerAttributeBase>();
//
return handlerAttrs.Single();
}
/// <summary>
/// Gets the state keeper attribute from the specified member info, if present.
/// </summary>
/// <param name="handlerType">The member info representing the handler type.</param>
/// <returns>The state keeper attribute, or null if not present.</returns>
public static IFilter<Update>? GetStateKeeperAttribute(MemberInfo handlerType)
{
// Getting polling handler attribute
Attribute stateAttr = handlerType.GetCustomAttribute(typeof(StateAttribute<,>));
//
return stateAttr as IFilter<Update>;
}
/// <summary>
/// Gets all filter attributes for the specified handler type and update type.
/// </summary>
/// <param name="handlerType">The member info representing the handler type.</param>
/// <param name="validUpdType">The valid update type.</param>
/// <returns>An enumerable of filter attributes.</returns>
public static IEnumerable<IFilter<Update>> GetFilterAttributes(MemberInfo handlerType, UpdateType validUpdType)
{
//
IEnumerable<UpdateFilterAttributeBase> filters = handlerType.GetCustomAttributes<UpdateFilterAttributeBase>();
//
if (filters.Any(filterAttr => !filterAttr.AllowedTypes.Contains(validUpdType)))
throw new InvalidOperationException();
UpdateFilterAttributeBase? lastFilterAttribute = null;
foreach (UpdateFilterAttributeBase filterAttribute in filters)
{
return handlerType.GetCustomAttribute<DisplayNameAttribute>()?.DisplayName;
}
/// <summary>
/// Gets the handler attribute from the specified member info.
/// </summary>
/// <param name="handlerType">The member info representing the handler type.</param>
/// <returns>The handler attribute.</returns>
public static UpdateHandlerAttributeBase GetHandlerAttribute(MemberInfo handlerType)
{
// Getting polling handler attribute
IEnumerable<UpdateHandlerAttributeBase> handlerAttrs = handlerType.GetCustomAttributes<UpdateHandlerAttributeBase>();
//
return handlerAttrs.Single();
}
/// <summary>
/// Gets the state keeper attribute from the specified member info, if present.
/// </summary>
/// <param name="handlerType">The member info representing the handler type.</param>
/// <returns>The state keeper attribute, or null if not present.</returns>
public static StateKeeperAttributeBase? GetStateKeeperAttribute(MemberInfo handlerType)
{
// Getting polling handler attribute
IEnumerable<StateKeeperAttributeBase> handlerAttrs = handlerType.GetCustomAttributes<StateKeeperAttributeBase>();
//
return handlerAttrs.Any() ? handlerAttrs.Single() : null;
}
/// <summary>
/// Gets all filter attributes for the specified handler type and update type.
/// </summary>
/// <param name="handlerType">The member info representing the handler type.</param>
/// <param name="validUpdType">The valid update type.</param>
/// <returns>An enumerable of filter attributes.</returns>
public static IEnumerable<IFilter<Update>> GetFilterAttributes(MemberInfo handlerType, UpdateType validUpdType)
{
//
IEnumerable<UpdateFilterAttributeBase> filters = handlerType.GetCustomAttributes<UpdateFilterAttributeBase>();
//
if (filters.Any(filterAttr => !filterAttr.AllowedTypes.Contains(validUpdType)))
throw new InvalidOperationException();
UpdateFilterAttributeBase? lastFilterAttribute = null;
foreach (UpdateFilterAttributeBase filterAttribute in filters)
if (!filterAttribute.ProcessModifiers(lastFilterAttribute))
{
if (!filterAttribute.ProcessModifiers(lastFilterAttribute))
{
lastFilterAttribute = null;
yield return filterAttribute.AnonymousFilter;
}
else
{
lastFilterAttribute = filterAttribute;
continue;
}
lastFilterAttribute = null;
yield return filterAttribute.AnonymousFilter;
}
else
{
lastFilterAttribute = filterAttribute;
continue;
}
}
}
/// <summary>
/// Gets the aspects configuration for the specified handler type.
/// Inspects the handler for both self-processing (implements interfaces) and typed processing (uses attributes).
/// </summary>
/// <param name="handlerType">The type of the handler to inspect.</param>
/// <returns>A <see cref="DescriptorAspectsSet"/> containing the aspects configuration.</returns>
public static DescriptorAspectsSet GetAspects(Type handlerType)
{
Type? typedPre = handlerType.GetCustomAttribute(typeof(BeforeExecutionAttribute<>))?.GetType().GetGenericArguments()[0];
Type? typedPost = handlerType.GetCustomAttribute(typeof(AfterExecutionAttribute<>))?.GetType().GetGenericArguments()[0];
return new DescriptorAspectsSet(typedPre, typedPost);
}
/// <summary>
/// Gets the aspects configuration for the specified handler type.
/// Inspects the handler for both self-processing (implements interfaces) and typed processing (uses attributes).
/// </summary>
/// <param name="handlerType">The type of the handler to inspect.</param>
/// <returns>A <see cref="DescriptorAspectsSet"/> containing the aspects configuration.</returns>
public static DescriptorAspectsSet GetAspects(Type handlerType)
{
Type? typedPre = handlerType.GetCustomAttribute(typeof(BeforeExecutionAttribute<>))?.GetType().GetGenericArguments()[0];
Type? typedPost = handlerType.GetCustomAttribute(typeof(AfterExecutionAttribute<>))?.GetType().GetGenericArguments()[0];
return new DescriptorAspectsSet(typedPre, typedPost);
}
}
@@ -2,108 +2,107 @@
using Telegrator.Filters;
using Telegrator.Logging;
namespace Telegrator.Core.Filters
namespace Telegrator.Core.Filters;
/// <summary>
/// Represents a compiled filter that applies a set of filters to an anonymous target type.
/// </summary>
public class AnonymousCompiledFilter : Filter<Update>, INamedFilter
{
private readonly Func<FilterExecutionContext<Update>, object, bool> FilterAction;
private readonly Func<Update, object?> GetFilterringTarget;
private readonly string _name;
/// <summary>
/// Represents a compiled filter that applies a set of filters to an anonymous target type.
/// Gets the name of this compiled filter.
/// </summary>
public class AnonymousCompiledFilter : Filter<Update>, INamedFilter
public virtual string Name => _name;
/// <summary>
/// Initializes a new instance of the <see cref="AnonymousCompiledFilter"/> class.
/// </summary>
/// <param name="name">The name of the compiled filter.</param>
/// <param name="filterAction">The filter action delegate.</param>
/// <param name="getFilterringTarget">The function to get the filtering target from an update.</param>
private AnonymousCompiledFilter(string name, Func<Update, object?> getFilterringTarget, Func<FilterExecutionContext<Update>, object, bool> filterAction)
{
private readonly Func<FilterExecutionContext<Update>, object, bool> FilterAction;
private readonly Func<Update, object?> GetFilterringTarget;
private readonly string _name;
FilterAction = filterAction;
GetFilterringTarget = getFilterringTarget;
_name = name;
}
/// <summary>
/// Gets the name of this compiled filter.
/// </summary>
public virtual string Name => _name;
/// <summary>
/// Compiles a set of filters into an <see cref="AnonymousCompiledFilter"/> for a specific target type.
/// </summary>
/// <typeparam name="T">The type of the filtering target.</typeparam>
/// <param name="filters">The list of filters to compile.</param>
/// <param name="getFilterringTarget">The function to get the filtering target from an update.</param>
/// <returns>The compiled filter.</returns>
public static AnonymousCompiledFilter Compile<T>(IEnumerable<IFilter<T>> filters, Func<Update, object?> getFilterringTarget) where T : class
{
return new AnonymousCompiledFilter(
string.Join("+", filters.Select(fltr => fltr.GetType().Name)),
getFilterringTarget,
(context, filterringTarget) => CanPassInternal(context, filters, filterringTarget));
}
/// <summary>
/// Initializes a new instance of the <see cref="AnonymousCompiledFilter"/> class.
/// </summary>
/// <param name="name">The name of the compiled filter.</param>
/// <param name="filterAction">The filter action delegate.</param>
/// <param name="getFilterringTarget">The function to get the filtering target from an update.</param>
private AnonymousCompiledFilter(string name, Func<Update, object?> getFilterringTarget, Func<FilterExecutionContext<Update>, object, bool> filterAction)
/// <summary>
/// Compiles a set of filters into an <see cref="AnonymousCompiledFilter"/> for a specific target type with a custom name.
/// </summary>
/// <typeparam name="T">The type of the filtering target.</typeparam>
/// <param name="name">The custom name for the compiled filter.</param>
/// <param name="filters">The list of filters to compile.</param>
/// <param name="getFilterringTarget">The function to get the filtering target from an update.</param>
/// <returns>The compiled filter.</returns>
public static AnonymousCompiledFilter Compile<T>(string name, IEnumerable<IFilter<T>> filters, Func<Update, object?> getFilterringTarget) where T : class
{
return new AnonymousCompiledFilter(
name,
getFilterringTarget,
(context, filterringTarget) => CanPassInternal(context, filters, filterringTarget));
}
/// <summary>
/// Determines whether all filters can pass for the given context and filtering target.
/// </summary>
/// <typeparam name="T">The type of the filtering target.</typeparam>
/// <param name="filters">The list of filters.</param>
/// <param name="updateContext">The filter execution context.</param>
/// <param name="filterringTarget">The filtering target.</param>
/// <returns>True if all filters pass; otherwise, false.</returns>
private static bool CanPassInternal<T>(FilterExecutionContext<Update> updateContext, IEnumerable<IFilter<T>> filters, object filterringTarget) where T : class
{
FilterExecutionContext<T> context = updateContext.CreateChild((T)filterringTarget);
foreach (IFilter<T> filter in filters)
{
FilterAction = filterAction;
GetFilterringTarget = getFilterringTarget;
_name = name;
}
/// <summary>
/// Compiles a set of filters into an <see cref="AnonymousCompiledFilter"/> for a specific target type.
/// </summary>
/// <typeparam name="T">The type of the filtering target.</typeparam>
/// <param name="filters">The list of filters to compile.</param>
/// <param name="getFilterringTarget">The function to get the filtering target from an update.</param>
/// <returns>The compiled filter.</returns>
public static AnonymousCompiledFilter Compile<T>(IEnumerable<IFilter<T>> filters, Func<Update, object?> getFilterringTarget) where T : class
{
return new AnonymousCompiledFilter(
string.Join("+", filters.Select(fltr => fltr.GetType().Name)),
getFilterringTarget,
(context, filterringTarget) => CanPassInternal(context, filters, filterringTarget));
}
/// <summary>
/// Compiles a set of filters into an <see cref="AnonymousCompiledFilter"/> for a specific target type with a custom name.
/// </summary>
/// <typeparam name="T">The type of the filtering target.</typeparam>
/// <param name="name">The custom name for the compiled filter.</param>
/// <param name="filters">The list of filters to compile.</param>
/// <param name="getFilterringTarget">The function to get the filtering target from an update.</param>
/// <returns>The compiled filter.</returns>
public static AnonymousCompiledFilter Compile<T>(string name, IEnumerable<IFilter<T>> filters, Func<Update, object?> getFilterringTarget) where T : class
{
return new AnonymousCompiledFilter(
name,
getFilterringTarget,
(context, filterringTarget) => CanPassInternal(context, filters, filterringTarget));
}
/// <summary>
/// Determines whether all filters can pass for the given context and filtering target.
/// </summary>
/// <typeparam name="T">The type of the filtering target.</typeparam>
/// <param name="filters">The list of filters.</param>
/// <param name="updateContext">The filter execution context.</param>
/// <param name="filterringTarget">The filtering target.</param>
/// <returns>True if all filters pass; otherwise, false.</returns>
private static bool CanPassInternal<T>(FilterExecutionContext<Update> updateContext, IEnumerable<IFilter<T>> filters, object filterringTarget) where T : class
{
FilterExecutionContext<T> context = updateContext.CreateChild((T)filterringTarget);
foreach (IFilter<T> filter in filters)
if (!filter.CanPass(context))
{
if (!filter.CanPass(context))
{
if (filter is not AnonymousCompiledFilter && filter is not AnonymousTypeFilter)
TelegratorLogging.LogDebug("{0} filter of {1} didnt pass! (Compiled anonymous)", filter.GetType().Name, context.Data["handler_name"]);
if (filter is not AnonymousCompiledFilter && filter is not AnonymousTypeFilter)
TelegratorLogging.LogDebug("{0} filter of {1} didnt pass! (Compiled anonymous)", filter.GetType().Name, context.Data["handler_name"]);
return false;
}
context.CompletedFilters.Add(filter);
}
return true;
}
/// <inheritdoc/>
public override bool CanPass(FilterExecutionContext<Update> context)
{
try
{
object? filterringTarget = GetFilterringTarget.Invoke(context.Input);
if (filterringTarget == null)
return false;
return FilterAction.Invoke(context, filterringTarget);
}
catch
{
return false;
}
context.CompletedFilters.Add(filter);
}
return true;
}
/// <inheritdoc/>
public override bool CanPass(FilterExecutionContext<Update> context)
{
try
{
object? filterringTarget = GetFilterringTarget.Invoke(context.Input);
if (filterringTarget == null)
return false;
return FilterAction.Invoke(context, filterringTarget);
}
catch
{
return false;
}
}
}
@@ -2,109 +2,108 @@
using Telegrator.Filters;
using Telegrator.Logging;
namespace Telegrator.Core.Filters
namespace Telegrator.Core.Filters;
/// <summary>
/// Represents a filter that applies a filter action to an anonymous target type extracted from an update.
/// </summary>
public class AnonymousTypeFilter : Filter<Update>, INamedFilter
{
private static readonly Type[] IgnoreLog = [typeof(CompiledFilter<>), typeof(AnonymousCompiledFilter), typeof(AnonymousTypeFilter)];
private readonly Func<FilterExecutionContext<Update>, object, bool> FilterAction;
private readonly Func<Update, object?> GetFilterringTarget;
private readonly string _name;
/// <summary>
/// Represents a filter that applies a filter action to an anonymous target type extracted from an update.
/// Gets the name of this filter.
/// </summary>
public class AnonymousTypeFilter : Filter<Update>, INamedFilter
public virtual string Name => _name;
/// <summary>
/// Initializes a new instance of the <see cref="AnonymousTypeFilter"/> class.
/// </summary>
/// <param name="name">The name of the filter.</param>
/// <param name="filterAction">The filter action delegate.</param>
/// <param name="getFilterringTarget">The function to get the filtering target from an update.</param>
public AnonymousTypeFilter(string name, Func<Update, object?> getFilterringTarget, Func<FilterExecutionContext<Update>, object, bool> filterAction)
{
private static readonly Type[] IgnoreLog = [typeof(CompiledFilter<>), typeof(AnonymousCompiledFilter), typeof(AnonymousTypeFilter)];
FilterAction = filterAction;
GetFilterringTarget = getFilterringTarget;
_name = name;
}
private readonly Func<FilterExecutionContext<Update>, object, bool> FilterAction;
private readonly Func<Update, object?> GetFilterringTarget;
private readonly string _name;
/// <summary>
/// Compiles a filter for a specific target type.
/// </summary>
/// <typeparam name="T">The type of the filtering target.</typeparam>
/// <param name="filter">The filter to apply.</param>
/// <param name="getFilterringTarget">The function to get the filtering target from an update.</param>
/// <returns>The compiled filter.</returns>
public static AnonymousTypeFilter Compile<T>(IFilter<T> filter, Func<Update, T?> getFilterringTarget) where T : class
{
return new AnonymousTypeFilter(
filter.GetType().Name, getFilterringTarget,
(context, filterringTarget) => CanPassInternal(context, filter, filterringTarget));
}
/// <summary>
/// Gets the name of this filter.
/// </summary>
public virtual string Name => _name;
/// <summary>
/// Compiles a filter for a specific target type with a custom name.
/// </summary>
/// <typeparam name="T">The type of the filtering target.</typeparam>
/// <param name="name">The custom name for the compiled filter.</param>
/// <param name="filter">The filter to apply.</param>
/// <param name="getFilterringTarget">The function to get the filtering target from an update.</param>
/// <returns>The compiled filter.</returns>
public static AnonymousTypeFilter Compile<T>(string name, IFilter<T> filter, Func<Update, T?> getFilterringTarget) where T : class
{
return new AnonymousTypeFilter(
name,
getFilterringTarget,
(context, filterringTarget) => CanPassInternal(context, filter, filterringTarget));
}
/// <summary>
/// Initializes a new instance of the <see cref="AnonymousTypeFilter"/> class.
/// </summary>
/// <param name="name">The name of the filter.</param>
/// <param name="filterAction">The filter action delegate.</param>
/// <param name="getFilterringTarget">The function to get the filtering target from an update.</param>
public AnonymousTypeFilter(string name, Func<Update, object?> getFilterringTarget, Func<FilterExecutionContext<Update>, object, bool> filterAction)
/// <summary>
/// Determines whether the filter can pass for the given context and filtering target.
/// </summary>
/// <typeparam name="T">The type of the filtering target.</typeparam>
/// <param name="updateContext">The filter execution context.</param>
/// <param name="filter">The filter to apply.</param>
/// <param name="filterringTarget">The filtering target.</param>
/// <returns>True if the filter passes; otherwise, false.</returns>
private static bool CanPassInternal<T>(FilterExecutionContext<Update> updateContext, IFilter<T> filter, object filterringTarget) where T : class
{
FilterExecutionContext<T> context = updateContext.CreateChild((T)filterringTarget);
if (!filter.CanPass(context))
{
FilterAction = filterAction;
GetFilterringTarget = getFilterringTarget;
_name = name;
if (IgnoreLog.Contains(filter.GetType().MakeGenericType()))
TelegratorLogging.LogDebug("{0} filter of {1} didnt pass!", filter.GetType().Name, context.Data["handler_name"]);
return false;
}
/// <summary>
/// Compiles a filter for a specific target type.
/// </summary>
/// <typeparam name="T">The type of the filtering target.</typeparam>
/// <param name="filter">The filter to apply.</param>
/// <param name="getFilterringTarget">The function to get the filtering target from an update.</param>
/// <returns>The compiled filter.</returns>
public static AnonymousTypeFilter Compile<T>(IFilter<T> filter, Func<Update, T?> getFilterringTarget) where T : class
{
return new AnonymousTypeFilter(
filter.GetType().Name, getFilterringTarget,
(context, filterringTarget) => CanPassInternal(context, filter, filterringTarget));
}
context.CompletedFilters.Add(filter);
return true;
}
/// <summary>
/// Compiles a filter for a specific target type with a custom name.
/// </summary>
/// <typeparam name="T">The type of the filtering target.</typeparam>
/// <param name="name">The custom name for the compiled filter.</param>
/// <param name="filter">The filter to apply.</param>
/// <param name="getFilterringTarget">The function to get the filtering target from an update.</param>
/// <returns>The compiled filter.</returns>
public static AnonymousTypeFilter Compile<T>(string name, IFilter<T> filter, Func<Update, T?> getFilterringTarget) where T : class
/// <summary>
/// Determines whether the filter can pass for the given context by extracting the filtering target and applying the filter action.
/// </summary>
/// <param name="context">The filter execution context.</param>
/// <returns>True if the filter passes; otherwise, false.</returns>
public override bool CanPass(FilterExecutionContext<Update> context)
{
try
{
return new AnonymousTypeFilter(
name,
getFilterringTarget,
(context, filterringTarget) => CanPassInternal(context, filter, filterringTarget));
}
/// <summary>
/// Determines whether the filter can pass for the given context and filtering target.
/// </summary>
/// <typeparam name="T">The type of the filtering target.</typeparam>
/// <param name="updateContext">The filter execution context.</param>
/// <param name="filter">The filter to apply.</param>
/// <param name="filterringTarget">The filtering target.</param>
/// <returns>True if the filter passes; otherwise, false.</returns>
private static bool CanPassInternal<T>(FilterExecutionContext<Update> updateContext, IFilter<T> filter, object filterringTarget) where T : class
{
FilterExecutionContext<T> context = updateContext.CreateChild((T)filterringTarget);
if (!filter.CanPass(context))
{
if (IgnoreLog.Contains(filter.GetType().MakeGenericType()))
TelegratorLogging.LogDebug("{0} filter of {1} didnt pass!", filter.GetType().Name, context.Data["handler_name"]);
object? filterringTarget = GetFilterringTarget.Invoke(context.Input);
if (filterringTarget == null)
return false;
}
context.CompletedFilters.Add(filter);
return true;
return FilterAction.Invoke(context, filterringTarget);
}
/// <summary>
/// Determines whether the filter can pass for the given context by extracting the filtering target and applying the filter action.
/// </summary>
/// <param name="context">The filter execution context.</param>
/// <returns>True if the filter passes; otherwise, false.</returns>
public override bool CanPass(FilterExecutionContext<Update> context)
catch
{
try
{
object? filterringTarget = GetFilterringTarget.Invoke(context.Input);
if (filterringTarget == null)
return false;
return FilterAction.Invoke(context, filterringTarget);
}
catch
{
return false;
}
return false;
}
}
}
+46 -47
View File
@@ -1,64 +1,63 @@
using Telegrator.Filters;
using Telegrator.Logging;
namespace Telegrator.Core.Filters
namespace Telegrator.Core.Filters;
/// <summary>
/// Represents a filter that composes multiple filters and passes only if all of them pass.
/// </summary>
/// <typeparam name="T">The type of the input for the filter.</typeparam>
public class CompiledFilter<T> : Filter<T>, INamedFilter where T : class
{
private readonly IFilter<T>[] Filters;
private readonly string _name;
/// <summary>
/// Represents a filter that composes multiple filters and passes only if all of them pass.
/// Gets the name of this compiled filter.
/// </summary>
/// <typeparam name="T">The type of the input for the filter.</typeparam>
public class CompiledFilter<T> : Filter<T>, INamedFilter where T : class
public virtual string Name => _name;
/// <summary>
/// Initializes a new instance of the <see cref="CompiledFilter{T}"/> class.
/// </summary>
/// <param name="filters">The filters to compose.</param>
public CompiledFilter(params IFilter<T>[] filters)
{
private readonly IFilter<T>[] Filters;
private readonly string _name;
_name = string.Join("+", filters.Select(fltr => fltr.GetType().Name));
Filters = filters;
}
/// <summary>
/// Gets the name of this compiled filter.
/// </summary>
public virtual string Name => _name;
/// <summary>
/// Initializes a new instance of the <see cref="CompiledFilter{T}"/> class with a custom name.
/// </summary>
/// <param name="name">The custom name for the compiled filter.</param>
/// <param name="filters">The filters to compose.</param>
public CompiledFilter(string name, params IFilter<T>[] filters)
{
_name = name;
Filters = filters;
}
/// <summary>
/// Initializes a new instance of the <see cref="CompiledFilter{T}"/> class.
/// </summary>
/// <param name="filters">The filters to compose.</param>
public CompiledFilter(params IFilter<T>[] filters)
/// <summary>
/// Determines whether all composed filters pass for the given context.
/// </summary>
/// <param name="context">The filter execution context.</param>
/// <returns>True if all filters pass; otherwise, false.</returns>
public override bool CanPass(FilterExecutionContext<T> context)
{
foreach (IFilter<T> filter in Filters)
{
_name = string.Join("+", filters.Select(fltr => fltr.GetType().Name));
Filters = filters;
}
/// <summary>
/// Initializes a new instance of the <see cref="CompiledFilter{T}"/> class with a custom name.
/// </summary>
/// <param name="name">The custom name for the compiled filter.</param>
/// <param name="filters">The filters to compose.</param>
public CompiledFilter(string name, params IFilter<T>[] filters)
{
_name = name;
Filters = filters;
}
/// <summary>
/// Determines whether all composed filters pass for the given context.
/// </summary>
/// <param name="context">The filter execution context.</param>
/// <returns>True if all filters pass; otherwise, false.</returns>
public override bool CanPass(FilterExecutionContext<T> context)
{
foreach (IFilter<T> filter in Filters)
if (!filter.CanPass(context))
{
if (!filter.CanPass(context))
{
if (filter is not AnonymousCompiledFilter && filter is not AnonymousTypeFilter)
TelegratorLogging.LogTrace("{0} filter of {1} didnt pass! (Compiled)", filter.GetType().Name, context.Data["handler_name"]);
if (filter is not AnonymousCompiledFilter && filter is not AnonymousTypeFilter)
TelegratorLogging.LogTrace("{0} filter of {1} didnt pass! (Compiled)", filter.GetType().Name, context.Data["handler_name"]);
return false;
}
context.CompletedFilters.Add(filter);
return false;
}
return true;
context.CompletedFilters.Add(filter);
}
return true;
}
}
@@ -1,86 +1,85 @@
using System.Collections;
namespace Telegrator.Core.Filters
namespace Telegrator.Core.Filters;
/// <summary>
/// The list containing filters worked out during Polling to further obtain additional filtering information
/// </summary>
public class CompletedFiltersList : IEnumerable<IFilterCollectable>
{
private readonly List<IFilterCollectable> CompletedFilters = [];
/// <summary>
/// The list containing filters worked out during Polling to further obtain additional filtering information
/// Adds the completed filter to the list.
/// </summary>
public class CompletedFiltersList : IEnumerable<IFilterCollectable>
/// <typeparam name="TUpdate">The type of update.</typeparam>
/// <param name="filter">The filter to add.</param>
public void Add<TUpdate>(IFilter<TUpdate> filter) where TUpdate : class
{
private readonly List<IFilterCollectable> CompletedFilters = [];
if (filter is AnonymousTypeFilter | filter is AnonymousCompiledFilter)
return;
/// <summary>
/// Adds the completed filter to the list.
/// </summary>
/// <typeparam name="TUpdate">The type of update.</typeparam>
/// <param name="filter">The filter to add.</param>
public void Add<TUpdate>(IFilter<TUpdate> filter) where TUpdate : class
{
if (filter is AnonymousTypeFilter | filter is AnonymousCompiledFilter)
return;
if (!filter.IsCollectible)
return;
if (!filter.IsCollectible)
return;
CompletedFilters.Add(filter);
}
/// <summary>
/// Adds many completed filters to the list.
/// </summary>
/// <typeparam name="TUpdate">The type of update.</typeparam>
/// <param name="filters">The filters to add.</param>
public void AddRange<TUpdate>(IEnumerable<IFilter<TUpdate>> filters) where TUpdate : class
{
foreach (IFilter<TUpdate> filter in filters)
Add(filter);
}
/// <summary>
/// Looks for filters of a given type in the list.
/// </summary>
/// <typeparam name="TFilter">The filter type to search for.</typeparam>
/// <returns>The enumerable containing filters of the given type.</returns>
/// <exception cref="NotFilterTypeException">Thrown if the type is not a filter type.</exception>
public IEnumerable<TFilter> Get<TFilter>() where TFilter : notnull, IFilterCollectable
{
if (!typeof(TFilter).IsFilterType())
throw new NotFilterTypeException(typeof(TFilter));
return CompletedFilters.OfType<TFilter>();
}
/// <summary>
/// Looks for a filter of a given type at the specified index in the list.
/// </summary>
/// <typeparam name="TFilter">The filter type to search for.</typeparam>
/// <param name="index">The index of the filter.</param>
/// <returns>The filter of the given type at the specified index.</returns>
/// <exception cref="NotFilterTypeException">Thrown if the type is not a filter type.</exception>
/// <exception cref="KeyNotFoundException">Thrown if no filter is found at the index.</exception>
public TFilter Get<TFilter>(int index) where TFilter : notnull, IFilterCollectable
{
IEnumerable<TFilter> filters = Get<TFilter>();
return filters.Any() ? filters.ElementAt(index) : throw new KeyNotFoundException();
}
/// <summary>
/// Returns a filter of a given type at the specified index, or null if it does not exist.
/// </summary>
/// <typeparam name="TFilter">The filter type to search for.</typeparam>
/// <param name="index">The index of the filter.</param>
/// <returns>The filter at the specified index, or null if it does not exist.</returns>
/// <exception cref="NotFilterTypeException">Thrown if the type is not a filter type.</exception>
public TFilter? GetOrDefault<TFilter>(int index) where TFilter : IFilterCollectable
{
IEnumerable<TFilter> filters = Get<TFilter>();
return filters.Any() ? filters.ElementAt(index) : default;
}
/// <inheritdoc/>
public IEnumerator<IFilterCollectable> GetEnumerator() => CompletedFilters.GetEnumerator();
/// <inheritdoc/>
IEnumerator IEnumerable.GetEnumerator() => CompletedFilters.GetEnumerator();
CompletedFilters.Add(filter);
}
/// <summary>
/// Adds many completed filters to the list.
/// </summary>
/// <typeparam name="TUpdate">The type of update.</typeparam>
/// <param name="filters">The filters to add.</param>
public void AddRange<TUpdate>(IEnumerable<IFilter<TUpdate>> filters) where TUpdate : class
{
foreach (IFilter<TUpdate> filter in filters)
Add(filter);
}
/// <summary>
/// Looks for filters of a given type in the list.
/// </summary>
/// <typeparam name="TFilter">The filter type to search for.</typeparam>
/// <returns>The enumerable containing filters of the given type.</returns>
/// <exception cref="NotFilterTypeException">Thrown if the type is not a filter type.</exception>
public IEnumerable<TFilter> Get<TFilter>() where TFilter : notnull, IFilterCollectable
{
if (!typeof(TFilter).IsFilterType())
throw new NotFilterTypeException(typeof(TFilter));
return CompletedFilters.OfType<TFilter>();
}
/// <summary>
/// Looks for a filter of a given type at the specified index in the list.
/// </summary>
/// <typeparam name="TFilter">The filter type to search for.</typeparam>
/// <param name="index">The index of the filter.</param>
/// <returns>The filter of the given type at the specified index.</returns>
/// <exception cref="NotFilterTypeException">Thrown if the type is not a filter type.</exception>
/// <exception cref="KeyNotFoundException">Thrown if no filter is found at the index.</exception>
public TFilter Get<TFilter>(int index) where TFilter : notnull, IFilterCollectable
{
IEnumerable<TFilter> filters = Get<TFilter>();
return filters.Any() ? filters.ElementAt(index) : throw new KeyNotFoundException();
}
/// <summary>
/// Returns a filter of a given type at the specified index, or null if it does not exist.
/// </summary>
/// <typeparam name="TFilter">The filter type to search for.</typeparam>
/// <param name="index">The index of the filter.</param>
/// <returns>The filter at the specified index, or null if it does not exist.</returns>
/// <exception cref="NotFilterTypeException">Thrown if the type is not a filter type.</exception>
public TFilter? GetOrDefault<TFilter>(int index) where TFilter : IFilterCollectable
{
IEnumerable<TFilter> filters = Get<TFilter>();
return filters.Any() ? filters.ElementAt(index) : default;
}
/// <inheritdoc/>
public IEnumerator<IFilterCollectable> GetEnumerator() => CompletedFilters.GetEnumerator();
/// <inheritdoc/>
IEnumerator IEnumerable.GetEnumerator() => CompletedFilters.GetEnumerator();
}
@@ -1,78 +1,85 @@
using Telegram.Bot.Types;
using Telegram.Bot.Types.Enums;
namespace Telegrator.Core.Filters
namespace Telegrator.Core.Filters;
/// <summary>
/// Represents the context for filter execution, including update, input, and additional data.
/// </summary>
/// <typeparam name="T">The type of the input for the filter.</typeparam>
public class FilterExecutionContext<T> where T : class
{
/// <summary>
/// Represents the context for filter execution, including update, input, and additional data.
/// Gets the <see cref="ITelegramBotInfo"/> for the current context.
/// </summary>
/// <typeparam name="T">The type of the input for the filter.</typeparam>
public class FilterExecutionContext<T> where T : class
public IUpdateRouter UpdateRouter { get; }
/// <summary>
/// Gets the <see cref="ITelegramBotInfo"/> for the current context.
/// </summary>
public ITelegramBotInfo BotInfo { get; }
/// <summary>
/// Gets the additional data dictionary for the context.
/// </summary>
public Dictionary<string, object> Data { get; }
/// <summary>
/// Gets the list of completed filters for the context.
/// </summary>
public CompletedFiltersList CompletedFilters { get; }
/// <summary>
/// Gets the <see cref="Update"/> being processed.
/// </summary>
public Update Update { get; }
/// <summary>
/// Gets the <see cref="UpdateType"/> of the update.
/// </summary>
public UpdateType Type { get; }
/// <summary>
/// Gets the input object for the filter.
/// </summary>
public T Input { get; }
/// <summary>
/// Initializes a new instance of the <see cref="FilterExecutionContext{T}"/> class with all parameters.
/// </summary>
/// <param name="router">The router, that invoked filter.</param>
/// <param name="botInfo">The bot info.</param>
/// <param name="update">The update.</param>
/// <param name="input">The input object.</param>
/// <param name="data">The additional data dictionary.</param>
/// <param name="completedFilters">The list of completed filters.</param>
public FilterExecutionContext(IUpdateRouter router, ITelegramBotInfo botInfo, Update update, T input, Dictionary<string, object> data, CompletedFiltersList completedFilters)
{
/// <summary>
/// Gets the <see cref="ITelegramBotInfo"/> for the current context.
/// </summary>
public ITelegramBotInfo BotInfo { get; }
/// <summary>
/// Gets the additional data dictionary for the context.
/// </summary>
public Dictionary<string, object> Data { get; }
/// <summary>
/// Gets the list of completed filters for the context.
/// </summary>
public CompletedFiltersList CompletedFilters { get; }
/// <summary>
/// Gets the <see cref="Update"/> being processed.
/// </summary>
public Update Update { get; }
/// <summary>
/// Gets the <see cref="UpdateType"/> of the update.
/// </summary>
public UpdateType Type { get; }
/// <summary>
/// Gets the input object for the filter.
/// </summary>
public T Input { get; }
/// <summary>
/// Initializes a new instance of the <see cref="FilterExecutionContext{T}"/> class with all parameters.
/// </summary>
/// <param name="botInfo">The bot info.</param>
/// <param name="update">The update.</param>
/// <param name="input">The input object.</param>
/// <param name="data">The additional data dictionary.</param>
/// <param name="completedFilters">The list of completed filters.</param>
public FilterExecutionContext(ITelegramBotInfo botInfo, Update update, T input, Dictionary<string, object> data, CompletedFiltersList completedFilters)
{
BotInfo = botInfo;
Data = data;
CompletedFilters = completedFilters;
Update = update;
Type = update.Type;
Input = input;
}
/// <summary>
/// Initializes a new instance of the <see cref="FilterExecutionContext{T}"/> class with default data and filters.
/// </summary>
/// <param name="botInfo">The bot info.</param>
/// <param name="update">The update.</param>
/// <param name="input">The input object.</param>
public FilterExecutionContext(ITelegramBotInfo botInfo, Update update, T input)
: this(botInfo, update, input, [], []) { }
/// <summary>
/// Creates a child context for a different input type, sharing the same data and completed filters.
/// </summary>
/// <typeparam name="C">The type of the new input.</typeparam>
/// <param name="input">The new input object.</param>
/// <returns>A new <see cref="FilterExecutionContext{C}"/> instance.</returns>
public FilterExecutionContext<C> CreateChild<C>(C input) where C : class
=> new FilterExecutionContext<C>(BotInfo, Update, input, Data, CompletedFilters);
UpdateRouter = router;
BotInfo = botInfo;
Data = data;
CompletedFilters = completedFilters;
Update = update;
Type = update.Type;
Input = input;
}
/// <summary>
/// Initializes a new instance of the <see cref="FilterExecutionContext{T}"/> class with default data and filters.
/// </summary>
/// <param name="router">The router, that invoked filter.</param>
/// <param name="botInfo">The bot info.</param>
/// <param name="update">The update.</param>
/// <param name="input">The input object.</param>
public FilterExecutionContext(IUpdateRouter router, ITelegramBotInfo botInfo, Update update, T input)
: this(router, botInfo, update, input, [], []) { }
/// <summary>
/// Creates a child context for a different input type, sharing the same data and completed filters.
/// </summary>
/// <typeparam name="C">The type of the new input.</typeparam>
/// <param name="input">The new input object.</param>
/// <returns>A new <see cref="FilterExecutionContext{C}"/> instance.</returns>
public FilterExecutionContext<C> CreateChild<C>(C input) where C : class
=> new FilterExecutionContext<C>(UpdateRouter, BotInfo, Update, input, Data, CompletedFilters);
}
+46 -35
View File
@@ -1,39 +1,50 @@
namespace Telegrator.Core.Filters
namespace Telegrator.Core.Filters;
/// <summary>
/// Interface for filters that have a name for identification and debugging purposes.
/// </summary>
public interface INamedFilter
{
/// <summary>
/// Interface for filters that have a name for identification and debugging purposes.
/// Gets the name of the filter.
/// </summary>
public interface INamedFilter
{
/// <summary>
/// Gets the name of the filter.
/// </summary>
public string Name { get; }
}
/// <summary>
/// Interface for filters that can be collected into a completed filters list.
/// Provides information about whether a filter should be tracked during execution.
/// </summary>
public interface IFilterCollectable
{
/// <summary>
/// Gets if filter can be collected to <see cref="CompletedFiltersList"/>
/// </summary>
public bool IsCollectible { get; }
}
/// <summary>
/// Represents a filter for a specific update type.
/// </summary>
/// <typeparam name="T">The type of the update to filter.</typeparam>
public interface IFilter<T> : IFilterCollectable where T : class
{
/// <summary>
/// Determines whether the filter can pass for the given context.
/// </summary>
/// <param name="info">The filter execution context.</param>
/// <returns>True if the filter passes; otherwise, false.</returns>
public bool CanPass(FilterExecutionContext<T> info);
}
public string Name { get; }
}
/// <summary>
/// Interface for filters that can be collected into a completed filters list.
/// Provides information about whether a filter should be tracked during execution.
/// </summary>
public interface IFilterCollectable
{
/// <summary>
/// Gets if filter can be collected to <see cref="CompletedFiltersList"/>
/// </summary>
public bool IsCollectible { get; }
}
/// <summary>
/// Represents a filter for a specific update type.
/// </summary>
/// <typeparam name="T">The type of the update to filter.</typeparam>
public interface IFilter<T> : IFilterCollectable where T : class
{
/// <summary>
/// Determines whether the filter can pass for the given context.
/// </summary>
/// <param name="info">The filter execution context.</param>
/// <returns>True if the filter passes; otherwise, false.</returns>
public bool CanPass(FilterExecutionContext<T> info);
}
/// <summary>
/// Represents a filter that joins multiple filters together.
/// </summary>
/// <typeparam name="T">The type of the input for the filter.</typeparam>
public interface IJoinedFilter<T> : IFilter<T> where T : class
{
/// <summary>
/// Gets the array of joined filters.
/// </summary>
public IFilter<T>[] Filters { get; }
}
@@ -1,14 +0,0 @@
namespace Telegrator.Core.Filters
{
/// <summary>
/// Represents a filter that joins multiple filters together.
/// </summary>
/// <typeparam name="T">The type of the input for the filter.</typeparam>
public interface IJoinedFilter<T> : IFilter<T> where T : class
{
/// <summary>
/// Gets the array of joined filters.
/// </summary>
public IFilter<T>[] Filters { get; }
}
}
@@ -3,88 +3,93 @@ using Telegram.Bot.Types;
using Telegram.Bot.Types.Enums;
using Telegrator.Core.Descriptors;
using Telegrator.Core.Filters;
using Telegrator.Core.States;
using Telegrator.Handlers;
namespace Telegrator.Core.Handlers
namespace Telegrator.Core.Handlers;
/// <summary>
/// Abstract handler for Telegram updates of type <typeparamref name="TUpdate"/>.
/// </summary>
public abstract class AbstractUpdateHandler<TUpdate> : UpdateHandlerBase, IHandlerContainerFactory, IAbstractUpdateHandler<TUpdate> where TUpdate : class
{
/// <summary>
/// Abstract handler for Telegram updates of type <typeparamref name="TUpdate"/>.
/// Handler container for the current update.
/// </summary>
public abstract class AbstractUpdateHandler<TUpdate> : UpdateHandlerBase, IHandlerContainerFactory, IAbstractUpdateHandler<TUpdate> where TUpdate : class
public IHandlerContainer<TUpdate> Container { get; private set; } = default!;
/// <summary>
/// Telegram Bot client associated with the current container.
/// </summary>
protected ITelegramBotClient Client => Container.Client;
/// <summary>
/// Incoming update of type <typeparamref name="TUpdate"/>.
/// </summary>
protected TUpdate Input => Container.ActualUpdate;
/// <summary>
/// The Telegram update being handled.
/// </summary>
protected Update HandlingUpdate => Container.HandlingUpdate;
/// <summary>
/// Additional data associated with the handler execution.
/// </summary>
protected Dictionary<string, object> ExtraData => Container.ExtraData;
/// <summary>
/// List of successfully passed filters.
/// </summary>
protected CompletedFiltersList CompletedFilters => Container.CompletedFilters;
/// <summary>
/// Provider for awaiting asynchronous operations.
/// </summary>
protected IAwaitingProvider AwaitingProvider => Container.AwaitingProvider;
/// <summary>
/// Storage of bot states.
/// </summary>
protected IStateStorage StateStorage => Container.StateStorage;
/// <summary>
/// Initializes a new instance and checks that the update type matches <typeparamref name="TUpdate"/>.
/// </summary>
/// <param name="handlingUpdateType">The type of update to handle.</param>
protected AbstractUpdateHandler(UpdateType handlingUpdateType) : base(handlingUpdateType)
{
/// <summary>
/// Handler container for the current update.
/// </summary>
public IHandlerContainer<TUpdate> Container { get; private set; } = default!;
/// <summary>
/// Telegram Bot client associated with the current container.
/// </summary>
protected ITelegramBotClient Client => Container.Client;
/// <summary>
/// Incoming update of type <typeparamref name="TUpdate"/>.
/// </summary>
protected TUpdate Input => Container.ActualUpdate;
/// <summary>
/// The Telegram update being handled.
/// </summary>
protected Update HandlingUpdate => Container.HandlingUpdate;
/// <summary>
/// Additional data associated with the handler execution.
/// </summary>
protected Dictionary<string, object> ExtraData => Container.ExtraData;
/// <summary>
/// List of successfully passed filters.
/// </summary>
protected CompletedFiltersList CompletedFilters => Container.CompletedFilters;
/// <summary>
/// Provider for awaiting asynchronous operations.
/// </summary>
protected IAwaitingProvider AwaitingProvider => Container.AwaitingProvider;
/// <summary>
/// Initializes a new instance and checks that the update type matches <typeparamref name="TUpdate"/>.
/// </summary>
/// <param name="handlingUpdateType">The type of update to handle.</param>
protected AbstractUpdateHandler(UpdateType handlingUpdateType) : base(handlingUpdateType)
{
if (!HandlingUpdateType.IsValidUpdateObject<TUpdate>())
throw new Exception();
}
/// <summary>
/// Creates a handler container for the specified awaiting provider and handler info.
/// </summary>
/// <param name="handlerInfo">The handler descriptor info.</param>
/// <returns>The created handler container.</returns>
public virtual IHandlerContainer CreateContainer(DescribedHandlerDescriptor handlerInfo)
{
return new HandlerContainer<TUpdate>(handlerInfo);
}
/// <summary>
/// Executes the handler logic using the specified container.
/// </summary>
/// <param name="container">The handler container.</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>A task representing the asynchronous operation.</returns>
protected override sealed async Task<Result> ExecuteInternal(IHandlerContainer container, CancellationToken cancellationToken)
{
Container = (IHandlerContainer<TUpdate>)container;
return await Execute(Container, cancellationToken);
}
/// <summary>
/// Abstract method to execute the update handling logic.
/// </summary>
/// <param name="container">The handler container.</param>
/// <param name="cancellation">Cancellation token.</param>
/// <returns>A task representing the asynchronous operation.</returns>
public abstract Task<Result> Execute(IHandlerContainer<TUpdate> container, CancellationToken cancellation);
if (!HandlingUpdateType.IsValidUpdateObject<TUpdate>())
throw new Exception();
}
/// <summary>
/// Creates a handler container for the specified awaiting provider and handler info.
/// </summary>
/// <param name="handlerInfo">The handler descriptor info.</param>
/// <returns>The created handler container.</returns>
public virtual IHandlerContainer CreateContainer(DescribedHandlerDescriptor handlerInfo)
{
return new HandlerContainer<TUpdate>(handlerInfo);
}
/// <summary>
/// Executes the handler logic using the specified container.
/// </summary>
/// <param name="container">The handler container.</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>A task representing the asynchronous operation.</returns>
protected override sealed async Task<Result> ExecuteInternal(IHandlerContainer container, CancellationToken cancellationToken)
{
Container = (IHandlerContainer<TUpdate>)container;
return await Execute(Container, cancellationToken);
}
/// <summary>
/// Abstract method to execute the update handling logic.
/// </summary>
/// <param name="container">The handler container.</param>
/// <param name="cancellation">Cancellation token.</param>
/// <returns>A task representing the asynchronous operation.</returns>
public abstract Task<Result> Execute(IHandlerContainer<TUpdate> container, CancellationToken cancellation);
}
@@ -6,160 +6,159 @@ using Telegrator.Core.Descriptors;
using Telegrator.Core.Filters;
using Telegrator.Handlers;
namespace Telegrator.Core.Handlers
namespace Telegrator.Core.Handlers;
/// <summary>
/// Abstract base class for handlers that support branching execution based on different methods.
/// Allows multiple handler methods to be defined in a single class, each with its own filters.
/// </summary>
/// <typeparam name="TUpdate">The type of update being handled.</typeparam>
public abstract class BranchingUpdateHandler<TUpdate> : AbstractUpdateHandler<TUpdate>, IHandlerContainerFactory, ICustomDescriptorsProvider where TUpdate : class
{
/// <summary>
/// Abstract base class for handlers that support branching execution based on different methods.
/// Allows multiple handler methods to be defined in a single class, each with its own filters.
/// The method info for the current branch being executed.
/// </summary>
/// <typeparam name="TUpdate">The type of update being handled.</typeparam>
public abstract class BranchingUpdateHandler<TUpdate> : AbstractUpdateHandler<TUpdate>, IHandlerContainerFactory, ICustomDescriptorsProvider where TUpdate : class
private MethodInfo? branchMethodInfo = null;
/// <summary>
/// Gets the binding flags used to discover branch methods.
/// </summary>
protected virtual BindingFlags BranchesBindingFlags => BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public;
/// <summary>
/// Gets the allowed return types for branch methods.
/// </summary>
protected virtual Type[] AllowedBranchReturnTypes => [typeof(void), typeof(Task<Result>)];
/// <summary>
/// Gets the cancellation token for the current execution.
/// </summary>
protected CancellationToken Cancellation { get; private set; } = default;
/// <summary>
/// Initializes a new instance of the <see cref="BranchingUpdateHandler{TUpdate}"/> class.
/// </summary>
/// <param name="handlingUpdateType">The type of update this handler processes.</param>
protected BranchingUpdateHandler(UpdateType handlingUpdateType)
: base(handlingUpdateType) { }
/// <summary>
/// Describes all handler branches in this class.
/// </summary>
/// <returns>A collection of handler descriptors for each branch method.</returns>
/// <exception cref="Exception">Thrown when no branch methods are found.</exception>
public IEnumerable<HandlerDescriptor> DescribeHandlers()
{
/// <summary>
/// The method info for the current branch being executed.
/// </summary>
private MethodInfo? branchMethodInfo = null;
Type thisType = GetType();
UpdateHandlerAttributeBase updateHandlerAttribute = HandlerInspector.GetHandlerAttribute(thisType);
IEnumerable<IFilter<Update>> handlerFilters = HandlerInspector.GetFilterAttributes(thisType, HandlingUpdateType);
/// <summary>
/// Gets the binding flags used to discover branch methods.
/// </summary>
protected virtual BindingFlags BranchesBindingFlags => BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public;
MethodInfo[] handlerBranches = thisType.GetMethods().Where(branch => branch.DeclaringType == thisType).ToArray();
if (handlerBranches.Length == 0)
throw new Exception();
/// <summary>
/// Gets the allowed return types for branch methods.
/// </summary>
protected virtual Type[] AllowedBranchReturnTypes => [typeof(void), typeof(Task<Result>)];
foreach (MethodInfo branch in handlerBranches)
yield return DescribeBranch(branch, updateHandlerAttribute, handlerFilters);
}
/// <summary>
/// Gets the cancellation token for the current execution.
/// </summary>
protected CancellationToken Cancellation { get; private set; } = default;
/// <summary>
/// Describes a specific branch method.
/// </summary>
/// <param name="branch">The branch method to describe.</param>
/// <param name="handlerAttribute">The handler attribute for the class.</param>
/// <param name="handlerFilters">The filters applied to the class.</param>
/// <returns>A handler descriptor for the branch method.</returns>
/// <exception cref="Exception">Thrown when the branch method has parameters or invalid return type.</exception>
protected virtual HandlerDescriptor DescribeBranch(MethodInfo branch, UpdateHandlerAttributeBase handlerAttribute, IEnumerable<IFilter<Update>> handlerFilters)
{
Type thisType = GetType();
/// <summary>
/// Initializes a new instance of the <see cref="BranchingUpdateHandler{TUpdate}"/> class.
/// </summary>
/// <param name="handlingUpdateType">The type of update this handler processes.</param>
protected BranchingUpdateHandler(UpdateType handlingUpdateType)
: base(handlingUpdateType) { }
if (branch.GetParameters().Length != 0)
throw new Exception("Branch method must have no parameters.");
/// <summary>
/// Describes all handler branches in this class.
/// </summary>
/// <returns>A collection of handler descriptors for each branch method.</returns>
/// <exception cref="Exception">Thrown when no branch methods are found.</exception>
public IEnumerable<HandlerDescriptor> DescribeHandlers()
if (!AllowedBranchReturnTypes.Any(branch.ReturnType.Equals))
throw new Exception("Branch method must have one of allowed return types. [void, Task<Result>]");
try
{
Type thisType = GetType();
UpdateHandlerAttributeBase updateHandlerAttribute = HandlerInspector.GetHandlerAttribute(thisType);
IEnumerable<IFilter<Update>> handlerFilters = HandlerInspector.GetFilterAttributes(thisType, HandlingUpdateType);
MethodInfo[] handlerBranches = thisType.GetMethods().Where(branch => branch.DeclaringType == thisType).ToArray();
if (handlerBranches.Length == 0)
throw new Exception();
foreach (MethodInfo branch in handlerBranches)
yield return DescribeBranch(branch, updateHandlerAttribute, handlerFilters);
handlerAttribute = HandlerInspector.GetHandlerAttribute(branch);
}
catch
{
_ = 0xBAD + 0xC0DE;
}
/// <summary>
/// Describes a specific branch method.
/// </summary>
/// <param name="branch">The branch method to describe.</param>
/// <param name="handlerAttribute">The handler attribute for the class.</param>
/// <param name="handlerFilters">The filters applied to the class.</param>
/// <returns>A handler descriptor for the branch method.</returns>
/// <exception cref="Exception">Thrown when the branch method has parameters or invalid return type.</exception>
protected virtual HandlerDescriptor DescribeBranch(MethodInfo branch, UpdateHandlerAttributeBase handlerAttribute, IEnumerable<IFilter<Update>> handlerFilters)
List<IFilter<Update>> branchFiltersList = HandlerInspector.GetFilterAttributes(branch, HandlingUpdateType).ToList();
branchFiltersList.AddRange(handlerFilters);
DescriptorFiltersSet filtersSet = new DescriptorFiltersSet(
handlerAttribute,
HandlerInspector.GetStateKeeperAttribute(branch),
branchFiltersList.ToArray());
return new HandlerBranchDescriptor(thisType, branch, HandlingUpdateType, handlerAttribute.GetIndexer(), filtersSet);
}
/// <summary>
/// Creates a handler container for this branching handler.
/// </summary>
/// <param name="handlerInfo">The handler information.</param>
/// <returns>A handler container for this branching handler.</returns>
/// <exception cref="Exception">Thrown when the awaiting provider is not of the expected type.</exception>
public override IHandlerContainer CreateContainer(DescribedHandlerDescriptor handlerInfo)
{
return new HandlerContainer<TUpdate>(handlerInfo);
}
/// <summary>
/// Executes the current branch method.
/// </summary>
/// <param name="container">The handler container.</param>
/// <param name="cancellation">The cancellation token.</param>
/// <exception cref="Exception">Thrown when no branch method is set.</exception>
public override async Task<Result> Execute(IHandlerContainer<TUpdate> container, CancellationToken cancellation)
{
if (branchMethodInfo is null)
throw new Exception();
Cancellation = cancellation;
return await BranchExecuteWrapper(container, branchMethodInfo);
}
/// <summary>
/// Wraps the execution of a branch method, handling both void and Task return types.
/// </summary>
/// <param name="container">The handler container.</param>
/// <param name="methodInfo">The method to execute.</param>
protected virtual async Task<Result> BranchExecuteWrapper(IHandlerContainer<TUpdate> container, MethodInfo methodInfo)
{
if (methodInfo.ReturnType == typeof(void))
{
Type thisType = GetType();
if (branch.GetParameters().Length != 0)
throw new Exception("Branch method must have no parameters.");
if (!AllowedBranchReturnTypes.Any(branch.ReturnType.Equals))
throw new Exception("Branch method must have one of allowed return types. [void, Task<Result>]");
try
{
handlerAttribute = HandlerInspector.GetHandlerAttribute(branch);
}
catch
{
_ = 0xBAD + 0xC0DE;
}
List<IFilter<Update>> branchFiltersList = HandlerInspector.GetFilterAttributes(branch, HandlingUpdateType).ToList();
branchFiltersList.AddRange(handlerFilters);
DescriptorFiltersSet filtersSet = new DescriptorFiltersSet(
handlerAttribute,
HandlerInspector.GetStateKeeperAttribute(branch),
branchFiltersList.ToArray());
return new HandlerBranchDescriptor(thisType, branch, HandlingUpdateType, handlerAttribute.GetIndexer(), filtersSet);
methodInfo.Invoke(this, []);
return Result.Ok();
}
/// <summary>
/// Creates a handler container for this branching handler.
/// </summary>
/// <param name="handlerInfo">The handler information.</param>
/// <returns>A handler container for this branching handler.</returns>
/// <exception cref="Exception">Thrown when the awaiting provider is not of the expected type.</exception>
public override IHandlerContainer CreateContainer(DescribedHandlerDescriptor handlerInfo)
else
{
return new HandlerContainer<TUpdate>(handlerInfo);
object branchReturn = methodInfo.Invoke(this, []);
if (branchReturn is not Task<Result> branchTask)
throw new InvalidOperationException();
return await branchTask;
}
}
/// <summary>
/// Executes the current branch method.
/// </summary>
/// <param name="container">The handler container.</param>
/// <param name="cancellation">The cancellation token.</param>
/// <exception cref="Exception">Thrown when no branch method is set.</exception>
public override async Task<Result> Execute(IHandlerContainer<TUpdate> container, CancellationToken cancellation)
private class HandlerBranchDescriptor : HandlerDescriptor
{
public HandlerBranchDescriptor(Type decalringType, MethodInfo method, UpdateType updateType, DescriptorIndexer indexer, DescriptorFiltersSet filters) : base(DescriptorType.General, decalringType, updateType, indexer, filters)
{
if (branchMethodInfo is null)
throw new Exception();
Cancellation = cancellation;
return await BranchExecuteWrapper(container, branchMethodInfo);
}
/// <summary>
/// Wraps the execution of a branch method, handling both void and Task return types.
/// </summary>
/// <param name="container">The handler container.</param>
/// <param name="methodInfo">The method to execute.</param>
protected virtual async Task<Result> BranchExecuteWrapper(IHandlerContainer<TUpdate> container, MethodInfo methodInfo)
{
if (methodInfo.ReturnType == typeof(void))
DisplayString = HandlerInspector.GetDisplayName(method) ?? string.Format("{0}+{1}", method.DeclaringType.Name, method.Name);
LazyInitialization = handler =>
{
methodInfo.Invoke(this, []);
return Result.Ok();
}
else
{
object branchReturn = methodInfo.Invoke(this, []);
if (branchReturn is not Task<Result> branchTask)
throw new InvalidOperationException();
if (handler is not BranchingUpdateHandler<TUpdate> brancher)
throw new InvalidDataException();
return await branchTask;
}
}
private class HandlerBranchDescriptor : HandlerDescriptor
{
public HandlerBranchDescriptor(Type decalringType, MethodInfo method, UpdateType updateType, DescriptorIndexer indexer, DescriptorFiltersSet filters) : base(DescriptorType.General, decalringType, updateType, indexer, filters)
{
DisplayString = HandlerInspector.GetDisplayName(method) ?? string.Format("{0}+{1}", method.DeclaringType.Name, method.Name);
LazyInitialization = handler =>
{
if (handler is not BranchingUpdateHandler<TUpdate> brancher)
throw new InvalidDataException();
brancher.branchMethodInfo = method;
};
}
brancher.branchMethodInfo = method;
};
}
}
}
@@ -1,200 +1,179 @@
using Telegram.Bot.Types;
using Telegram.Bot.Types.Enums;
using Telegrator.Annotations.StateKeeping;
using Telegrator.Core.Descriptors;
using Telegrator.Core.Filters;
using Telegrator.Core.StateKeeping;
using Telegrator.Core.States;
using Telegrator.Filters;
namespace Telegrator.Core.Handlers.Building
namespace Telegrator.Core.Handlers.Building;
/// <summary>
/// Base class for building handler descriptors and managing handler filters.
/// </summary>
public abstract class HandlerBuilderBase(Type buildingHandlerType, UpdateType updateType, IHandlersCollection? handlerCollection) : IHandlerBuilder
{
private static int HandlerServiceKeyIndex = 0;
/// <summary>
/// Base class for building handler descriptors and managing handler filters.
/// <see cref="IHandlersCollection"/> to ehich new builded handlers is adding
/// </summary>
public abstract class HandlerBuilderBase(Type buildingHandlerType, UpdateType updateType, IHandlersCollection? handlerCollection) : IHandlerBuilder
protected readonly IHandlersCollection? HandlerCollection = handlerCollection;
/// <summary>
/// <see cref="UpdateType"/> of building handler
/// </summary>
protected readonly UpdateType UpdateType = updateType;
/// <summary>
/// Type of handler to build
/// </summary>
protected readonly Type BuildingHandlerType = buildingHandlerType;
/// <summary>
/// Filters applied to handler
/// </summary>
protected readonly List<IFilter<Update>> Filters = [];
/// <summary>
/// <see cref="DescriptorIndexer"/> of building handler
/// </summary>
protected DescriptorIndexer Indexer = new DescriptorIndexer(0, 0, 0);
/// <summary>
/// Update validation filter of building handler
/// </summary>
protected IFilter<Update>? ValidateFilter;
/// <summary>
/// State keeper of building handler
/// </summary>
protected IFilter<Update>? StateKeeper;
/// <summary>
/// Builds an implicit <see cref="HandlerDescriptor"/> for the specified handler instance.
/// </summary>
/// <param name="instance">The <see cref="UpdateHandlerBase"/> instance.</param>
/// <returns>The created <see cref="HandlerDescriptor"/>.</returns>
protected HandlerDescriptor BuildImplicitDescriptor(UpdateHandlerBase instance)
{
private static int HandlerServiceKeyIndex = 0;
object handlerServiceKey = GetImplicitHandlerServiceKey(BuildingHandlerType);
/// <summary>
/// <see cref="IHandlersCollection"/> to ehich new builded handlers is adding
/// </summary>
protected readonly IHandlersCollection? HandlerCollection = handlerCollection;
HandlerDescriptor descriptor = new HandlerDescriptor(
DescriptorType.Implicit, BuildingHandlerType,
UpdateType, Indexer, ValidateFilter,
Filters.ToArray(), StateKeeper,
handlerServiceKey, instance);
/// <summary>
/// <see cref="UpdateType"/> of building handler
/// </summary>
protected readonly UpdateType UpdateType = updateType;
HandlerCollection?.AddDescriptor(descriptor);
return descriptor;
}
/// <summary>
/// Type of handler to build
/// </summary>
protected readonly Type BuildingHandlerType = buildingHandlerType;
/// <summary>
/// Gets a unique service key for an implicit handler type.
/// </summary>
/// <param name="BuildingHandlerType">The handler type.</param>
/// <returns>A unique service key string.</returns>
public static object GetImplicitHandlerServiceKey(Type BuildingHandlerType)
=> string.Format("ImplicitHandler_{0}+{1}", HandlerServiceKeyIndex++, BuildingHandlerType.Name);
/// <summary>
/// Filters applied to handler
/// </summary>
protected readonly List<IFilter<Update>> Filters = [];
/// <summary>
/// Sets the update validating action for the handler.
/// </summary>
/// <param name="validateAction">The <see cref="UpdateValidateAction"/> to use.</param>
/// <returns>The builder instance.</returns>
public void SetUpdateValidating(UpdateValidateAction validateAction)
{
ValidateFilter = new UpdateValidateFilter(validateAction);
}
/// <summary>
/// <see cref="DescriptorIndexer"/> of building handler
/// </summary>
protected DescriptorIndexer Indexer = new DescriptorIndexer(0, 0, 0);
/// <summary>
/// Sets the concurrency level for the handler.
/// </summary>
/// <param name="concurrency">The concurrency value.</param>
/// <returns>The builder instance.</returns>
public void SetConcurreny(int concurrency)
{
Indexer = Indexer.UpdateImportance(concurrency);
}
/// <summary>
/// Update validation filter of building handler
/// </summary>
protected IFilter<Update>? ValidateFilter;
/// <summary>
/// Sets the priority for the handler.
/// </summary>
/// <param name="priority">The priority value.</param>
/// <returns>The builder instance.</returns>
public void SetPriority(int priority)
{
Indexer = Indexer.UpdatePriority(priority);
}
/// <summary>
/// State keeper of building handler
/// </summary>
protected IFilter<Update>? StateKeeper;
/// <summary>
/// Sets both concurrency and priority for the handler.
/// </summary>
/// <param name="concurrency">The concurrency value.</param>
/// <param name="priority">The priority value.</param>
/// <returns>The builder instance.</returns>
public void SetIndexer(int concurrency, int priority)
{
Indexer = new DescriptorIndexer(0, concurrency, priority);
}
/// <summary>
/// Builds an implicit <see cref="HandlerDescriptor"/> for the specified handler instance.
/// </summary>
/// <param name="instance">The <see cref="UpdateHandlerBase"/> instance.</param>
/// <returns>The created <see cref="HandlerDescriptor"/>.</returns>
protected HandlerDescriptor BuildImplicitDescriptor(UpdateHandlerBase instance)
{
object handlerServiceKey = GetImplicitHandlerServiceKey(BuildingHandlerType);
/// <summary>
/// Adds a filter to the handler.
/// </summary>
/// <param name="filter">The <see cref="IFilter{Update}"/> to add.</param>
/// <returns>The builder instance.</returns>
public void AddFilter(IFilter<Update> filter)
{
Filters.Add(filter);
}
HandlerDescriptor descriptor = new HandlerDescriptor(
DescriptorType.Implicit, BuildingHandlerType,
UpdateType, Indexer, ValidateFilter,
Filters.ToArray(), StateKeeper,
handlerServiceKey, instance);
/// <summary>
/// Adds multiple filters to the handler.
/// </summary>
/// <param name="filters">The filters to add.</param>
/// <returns>The builder instance.</returns>
public void AddFilters(params IFilter<Update>[] filters)
{
Filters.AddRange(filters);
}
HandlerCollection?.AddDescriptor(descriptor);
return descriptor;
}
/// <summary>
/// Sets a state keeper for the handler using a specific state and key resolver.
/// </summary>
/// <typeparam name="TKey">The key resolver.</typeparam>
/// <typeparam name="TValue">The state value.</typeparam>
/// <param name="state">The state value.</param>
/// <returns>The builder instance.</returns>
public void SetState<TKey, TValue>(TValue? state)
where TKey : IStateKeyResolver, new()
where TValue : IEquatable<TValue>
{
StateKeeper = new StateKeyFilter<TKey, TValue>(state);
}
/// <summary>
/// Gets a unique service key for an implicit handler type.
/// </summary>
/// <param name="BuildingHandlerType">The handler type.</param>
/// <returns>A unique service key string.</returns>
public static object GetImplicitHandlerServiceKey(Type BuildingHandlerType)
=> string.Format("ImplicitHandler_{0}+{1}", HandlerServiceKeyIndex++, BuildingHandlerType.Name);
/// <summary>
/// Adds a targeted filter for a specific filter target type.
/// </summary>
/// <typeparam name="TFilterTarget">The type of the filter target.</typeparam>
/// <param name="getFilterringTarget">Function to get the filter target from an update.</param>
/// <param name="filter">The filter to add.</param>
/// <returns>The builder instance.</returns>
public void AddTargetedFilter<TFilterTarget>(Func<Update, TFilterTarget?> getFilterringTarget, IFilter<TFilterTarget> filter) where TFilterTarget : class
{
AnonymousTypeFilter anonymousTypeFilter = AnonymousTypeFilter.Compile(filter, getFilterringTarget);
Filters.Add(anonymousTypeFilter);
}
/// <summary>
/// Sets the update validating action for the handler.
/// </summary>
/// <param name="validateAction">The <see cref="UpdateValidateAction"/> to use.</param>
/// <returns>The builder instance.</returns>
public void SetUpdateValidating(UpdateValidateAction validateAction)
{
ValidateFilter = new UpdateValidateFilter(validateAction);
}
/// <summary>
/// Sets the concurrency level for the handler.
/// </summary>
/// <param name="concurrency">The concurrency value.</param>
/// <returns>The builder instance.</returns>
public void SetConcurreny(int concurrency)
{
Indexer = Indexer.UpdateImportance(concurrency);
}
/// <summary>
/// Sets the priority for the handler.
/// </summary>
/// <param name="priority">The priority value.</param>
/// <returns>The builder instance.</returns>
public void SetPriority(int priority)
{
Indexer = Indexer.UpdatePriority(priority);
}
/// <summary>
/// Sets both concurrency and priority for the handler.
/// </summary>
/// <param name="concurrency">The concurrency value.</param>
/// <param name="priority">The priority value.</param>
/// <returns>The builder instance.</returns>
public void SetIndexer(int concurrency, int priority)
{
Indexer = new DescriptorIndexer(0, concurrency, priority);
}
/// <summary>
/// Adds a filter to the handler.
/// </summary>
/// <param name="filter">The <see cref="IFilter{Update}"/> to add.</param>
/// <returns>The builder instance.</returns>
public void AddFilter(IFilter<Update> filter)
{
Filters.Add(filter);
}
/// <summary>
/// Adds multiple filters to the handler.
/// </summary>
/// <param name="filters">The filters to add.</param>
/// <returns>The builder instance.</returns>
public void AddFilters(params IFilter<Update>[] filters)
{
Filters.AddRange(filters);
}
/// <summary>
/// Sets a state keeper for the handler using a specific state and key resolver.
/// </summary>
/// <typeparam name="TKey">The type of the key.</typeparam>
/// <typeparam name="TState">The type of the state.</typeparam>
/// <typeparam name="TKeeper">The type of the state keeper.</typeparam>
/// <param name="myState">The state value.</param>
/// <param name="keyResolver">The key resolver.</param>
/// <returns>The builder instance.</returns>
public void SetStateKeeper<TKey, TState, TKeeper>(TState myState, IStateKeyResolver<TKey> keyResolver)
where TKey : notnull
where TState : IEquatable<TState>
where TKeeper : StateKeeperBase<TKey, TState>, new()
{
StateKeeper = new StateKeepFilter<TKey, TState, TKeeper>(myState, keyResolver);
}
/// <summary>
/// Sets a state keeper for the handler using a special state and key resolver.
/// </summary>
/// <typeparam name="TKey">The type of the key.</typeparam>
/// <typeparam name="TState">The type of the state.</typeparam>
/// <typeparam name="TKeeper">The type of the state keeper.</typeparam>
/// <param name="specialState">The special state value.</param>
/// <param name="keyResolver">The key resolver.</param>
/// <returns>The builder instance.</returns>
public void SetStateKeeper<TKey, TState, TKeeper>(SpecialState specialState, IStateKeyResolver<TKey> keyResolver)
where TKey : notnull
where TState : IEquatable<TState>
where TKeeper : StateKeeperBase<TKey, TState>, new()
{
StateKeeper = new StateKeepFilter<TKey, TState, TKeeper>(specialState, keyResolver);
}
/// <summary>
/// Adds a targeted filter for a specific filter target type.
/// </summary>
/// <typeparam name="TFilterTarget">The type of the filter target.</typeparam>
/// <param name="getFilterringTarget">Function to get the filter target from an update.</param>
/// <param name="filter">The filter to add.</param>
/// <returns>The builder instance.</returns>
public void AddTargetedFilter<TFilterTarget>(Func<Update, TFilterTarget?> getFilterringTarget, IFilter<TFilterTarget> filter) where TFilterTarget : class
{
AnonymousTypeFilter anonymousTypeFilter = AnonymousTypeFilter.Compile(filter, getFilterringTarget);
Filters.Add(anonymousTypeFilter);
}
/// <summary>
/// Adds multiple targeted filters for a specific filter target type.
/// </summary>
/// <typeparam name="TFilterTarget">The type of the filter target.</typeparam>
/// <param name="getFilterringTarget">Function to get the filter target from an update.</param>
/// <param name="filters">The filters to add.</param>
/// <returns>The builder instance.</returns>
public void AddTargetedFilters<TFilterTarget>(Func<Update, TFilterTarget?> getFilterringTarget, params IFilter<TFilterTarget>[] filters) where TFilterTarget : class
{
AnonymousCompiledFilter compiledPollingFilter = AnonymousCompiledFilter.Compile(filters, getFilterringTarget);
Filters.Add(compiledPollingFilter);
}
/// <summary>
/// Adds multiple targeted filters for a specific filter target type.
/// </summary>
/// <typeparam name="TFilterTarget">The type of the filter target.</typeparam>
/// <param name="getFilterringTarget">Function to get the filter target from an update.</param>
/// <param name="filters">The filters to add.</param>
/// <returns>The builder instance.</returns>
public void AddTargetedFilters<TFilterTarget>(Func<Update, TFilterTarget?> getFilterringTarget, params IFilter<TFilterTarget>[] filters) where TFilterTarget : class
{
AnonymousCompiledFilter compiledPollingFilter = AnonymousCompiledFilter.Compile(filters, getFilterringTarget);
Filters.Add(compiledPollingFilter);
}
}
@@ -1,19 +1,18 @@
using Telegrator.Core.StateKeeping;
using Telegrator.Core.States;
namespace Telegrator.Core.Handlers.Building
namespace Telegrator.Core.Handlers.Building;
/// <summary>
/// Defines a builder for awaiting handler logic for a specific update type.
/// </summary>
/// <typeparam name="TUpdate">The type of update to await.</typeparam>
public interface IAwaiterHandlerBuilder<TUpdate> : IHandlerBuilder where TUpdate : class
{
/// <summary>
/// Defines a builder for awaiting handler logic for a specific update type.
/// Awaits an update using the specified key resolver and cancellation token.
/// </summary>
/// <typeparam name="TUpdate">The type of update to await.</typeparam>
public interface IAwaiterHandlerBuilder<TUpdate> : IHandlerBuilder where TUpdate : class
{
/// <summary>
/// Awaits an update using the specified key resolver and cancellation token.
/// </summary>
/// <param name="keyResolver">The <see cref="IStateKeyResolver{TKey}"/> to resolve the key.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>A <see cref="Task{TUpdate}"/> representing the awaited update.</returns>
public Task<TUpdate> Await(IStateKeyResolver<long> keyResolver, CancellationToken cancellationToken = default);
}
/// <param name="keyResolver">The <see cref="IStateKeyResolver"/> to resolve the key.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>A <see cref="Task{TUpdate}"/> representing the awaited update.</returns>
public Task<TUpdate> Await(IStateKeyResolver keyResolver, CancellationToken cancellationToken = default);
}
@@ -1,104 +1,85 @@
using Telegram.Bot.Types;
using Telegrator.Annotations.StateKeeping;
using Telegrator.Core.Filters;
using Telegrator.Core.StateKeeping;
using Telegrator.Core.States;
namespace Telegrator.Core.Handlers.Building
namespace Telegrator.Core.Handlers.Building;
/// <summary>
/// Defines builder actions for configuring handler builders.
/// </summary>
public interface IHandlerBuilder
{
/// <summary>
/// Defines builder actions for configuring handler builders.
/// Sets the update validating action for the handler.
/// </summary>
public interface IHandlerBuilder
{
/// <summary>
/// Sets the update validating action for the handler.
/// </summary>
/// <param name="validateAction">The <see cref="UpdateValidateAction"/> to use.</param>
/// <returns>The builder instance.</returns>
public void SetUpdateValidating(UpdateValidateAction validateAction);
/// <param name="validateAction">The <see cref="UpdateValidateAction"/> to use.</param>
/// <returns>The builder instance.</returns>
public void SetUpdateValidating(UpdateValidateAction validateAction);
/// <summary>
/// Sets the concurrency level for the handler.
/// </summary>
/// <param name="concurrency">The concurrency value.</param>
/// <returns>The builder instance.</returns>
public void SetConcurreny(int concurrency);
/// <summary>
/// Sets the concurrency level for the handler.
/// </summary>
/// <param name="concurrency">The concurrency value.</param>
/// <returns>The builder instance.</returns>
public void SetConcurreny(int concurrency);
/// <summary>
/// Sets the priority for the handler.
/// </summary>
/// <param name="priority">The priority value.</param>
/// <returns>The builder instance.</returns>
public void SetPriority(int priority);
/// <summary>
/// Sets the priority for the handler.
/// </summary>
/// <param name="priority">The priority value.</param>
/// <returns>The builder instance.</returns>
public void SetPriority(int priority);
/// <summary>
/// Sets both concurrency and priority for the handler.
/// </summary>
/// <param name="concurrency">The concurrency value.</param>
/// <param name="priority">The priority value.</param>
/// <returns>The builder instance.</returns>
public void SetIndexer(int concurrency, int priority);
/// <summary>
/// Sets both concurrency and priority for the handler.
/// </summary>
/// <param name="concurrency">The concurrency value.</param>
/// <param name="priority">The priority value.</param>
/// <returns>The builder instance.</returns>
public void SetIndexer(int concurrency, int priority);
/// <summary>
/// Adds a filter to the handler.
/// </summary>
/// <param name="filter">The <see cref="IFilter{Update}"/> to add.</param>
/// <returns>The builder instance.</returns>
public void AddFilter(IFilter<Update> filter);
/// <summary>
/// Adds a filter to the handler.
/// </summary>
/// <param name="filter">The <see cref="IFilter{Update}"/> to add.</param>
/// <returns>The builder instance.</returns>
public void AddFilter(IFilter<Update> filter);
/// <summary>
/// Adds multiple filters to the handler.
/// </summary>
/// <param name="filters">The filters to add.</param>
/// <returns>The builder instance.</returns>
public void AddFilters(params IFilter<Update>[] filters);
/// <summary>
/// Adds multiple filters to the handler.
/// </summary>
/// <param name="filters">The filters to add.</param>
/// <returns>The builder instance.</returns>
public void AddFilters(params IFilter<Update>[] filters);
/// <summary>
/// Sets a state keeper for the handler using a specific state and key resolver.
/// </summary>
/// <typeparam name="TKey">The type of the key.</typeparam>
/// <typeparam name="TState">The type of the state.</typeparam>
/// <typeparam name="TKeeper">The type of the state keeper.</typeparam>
/// <param name="myState">The state value.</param>
/// <param name="keyResolver">The key resolver.</param>
/// <returns>The builder instance.</returns>
public void SetStateKeeper<TKey, TState, TKeeper>(TState myState, IStateKeyResolver<TKey> keyResolver)
where TKey : notnull
where TState : IEquatable<TState>
where TKeeper : StateKeeperBase<TKey, TState>, new();
/// <summary>
/// Sets a state keeper for the handler using a specific state and key resolver.
/// </summary>
/// <typeparam name="TKey">The key resolver.</typeparam>
/// <typeparam name="TValue">The state value.</typeparam>
/// <param name="state">The state value.</param>
/// <returns>The builder instance.</returns>
public void SetState<TKey, TValue>(TValue? state)
where TKey : IStateKeyResolver, new()
where TValue : IEquatable<TValue>;
/// <summary>
/// Sets a state keeper for the handler using a special state and key resolver.
/// </summary>
/// <typeparam name="TKey">The type of the key.</typeparam>
/// <typeparam name="TState">The type of the state.</typeparam>
/// <typeparam name="TKeeper">The type of the state keeper.</typeparam>
/// <param name="specialState">The special state value.</param>
/// <param name="keyResolver">The key resolver.</param>
/// <returns>The builder instance.</returns>
public void SetStateKeeper<TKey, TState, TKeeper>(SpecialState specialState, IStateKeyResolver<TKey> keyResolver)
where TKey : notnull
where TState : IEquatable<TState>
where TKeeper : StateKeeperBase<TKey, TState>, new();
/// <summary>
/// Adds a targeted filter for a specific filter target type.
/// </summary>
/// <typeparam name="TFilterTarget">The type of the filter target.</typeparam>
/// <param name="getFilterringTarget">Function to get the filter target from an update.</param>
/// <param name="filter">The filter to add.</param>
/// <returns>The builder instance.</returns>
public void AddTargetedFilter<TFilterTarget>(Func<Update, TFilterTarget?> getFilterringTarget, IFilter<TFilterTarget> filter)
where TFilterTarget : class;
/// <summary>
/// Adds a targeted filter for a specific filter target type.
/// </summary>
/// <typeparam name="TFilterTarget">The type of the filter target.</typeparam>
/// <param name="getFilterringTarget">Function to get the filter target from an update.</param>
/// <param name="filter">The filter to add.</param>
/// <returns>The builder instance.</returns>
public void AddTargetedFilter<TFilterTarget>(Func<Update, TFilterTarget?> getFilterringTarget, IFilter<TFilterTarget> filter)
where TFilterTarget : class;
/// <summary>
/// Adds multiple targeted filters for a specific filter target type.
/// </summary>
/// <typeparam name="TFilterTarget">The type of the filter target.</typeparam>
/// <param name="getFilterringTarget">Function to get the filter target from an update.</param>
/// <param name="filters">The filters to add.</param>
/// <returns>The builder instance.</returns>
public void AddTargetedFilters<TFilterTarget>(Func<Update, TFilterTarget?> getFilterringTarget, params IFilter<TFilterTarget>[] filters)
where TFilterTarget : class;
}
/// <summary>
/// Adds multiple targeted filters for a specific filter target type.
/// </summary>
/// <typeparam name="TFilterTarget">The type of the filter target.</typeparam>
/// <param name="getFilterringTarget">Function to get the filter target from an update.</param>
/// <param name="filters">The filters to add.</param>
/// <returns>The builder instance.</returns>
public void AddTargetedFilters<TFilterTarget>(Func<Update, TFilterTarget?> getFilterringTarget, params IFilter<TFilterTarget>[] filters)
where TFilterTarget : class;
}
@@ -1,17 +1,16 @@
using Telegrator.Handlers.Building;
namespace Telegrator.Core.Handlers.Building
namespace Telegrator.Core.Handlers.Building;
/// <summary>
/// Defines a builder for regular handler logic for a specific update type.
/// </summary>
/// <typeparam name="TUpdate">The type of update to handle.</typeparam>
public interface IRegularHandlerBuilder<TUpdate> : IHandlerBuilder where TUpdate : class
{
/// <summary>
/// Defines a builder for regular handler logic for a specific update type.
/// Builds the handler logic using the specified execution delegate.
/// </summary>
/// <typeparam name="TUpdate">The type of update to handle.</typeparam>
public interface IRegularHandlerBuilder<TUpdate> : IHandlerBuilder where TUpdate : class
{
/// <summary>
/// Builds the handler logic using the specified execution delegate.
/// </summary>
/// <param name="executeHandler">The delegate to execute the handler logic.</param>
public IHandlersCollection Build(AbstractHandlerAction<TUpdate> executeHandler);
}
/// <param name="executeHandler">The delegate to execute the handler logic.</param>
public IHandlersCollection Build(AbstractHandlerAction<TUpdate> executeHandler);
}
@@ -1,80 +0,0 @@
using Telegram.Bot.Types;
using Telegrator.Annotations.StateKeeping;
using Telegrator.Core.Filters;
using Telegrator.Core.StateKeeping;
using Telegrator.Filters;
namespace Telegrator.Core.Handlers.Building
{
/// <summary>
/// Filter for state keeping logic, allowing filtering based on state and special state conditions.
/// </summary>
/// <typeparam name="TKey">The type of the key for state resolution.</typeparam>
/// <typeparam name="TState">The type of the state.</typeparam>
/// <typeparam name="TKeeper">The type of the state keeper.</typeparam>
public class StateKeepFilter<TKey, TState, TKeeper> : Filter<Update>
where TKey : notnull
where TState : IEquatable<TState>
where TKeeper : StateKeeperBase<TKey, TState>, new()
{
/// <summary>
/// Gets or sets the state keeper instance.
/// </summary>
public static TKeeper StateKeeper { get; internal set; } = null!;
/// <summary>
/// Gets the state value for this filter.
/// </summary>
public TState MyState { get; private set; }
/// <summary>
/// Gets the special state value for this filter.
/// </summary>
public SpecialState SpecialState { get; private set; }
/// <summary>
/// Initializes a new instance of the <see cref="StateKeepFilter{TKey, TState, TKeeper}"/> class with a specific state.
/// </summary>
/// <param name="myState">The state value.</param>
/// <param name="keyResolver">The key resolver.</param>
public StateKeepFilter(TState myState, IStateKeyResolver<TKey> keyResolver)
{
StateKeeper ??= new TKeeper();
StateKeeper.KeyResolver = keyResolver;
MyState = myState;
SpecialState = SpecialState.None;
}
/// <summary>
/// Initializes a new instance of the <see cref="StateKeepFilter{TKey, TState, TKeeper}"/> class with a special state.
/// </summary>
/// <param name="specialState">The special state value.</param>
/// <param name="keyResolver">The key resolver.</param>
public StateKeepFilter(SpecialState specialState, IStateKeyResolver<TKey> keyResolver)
{
StateKeeper ??= new TKeeper();
StateKeeper.KeyResolver = keyResolver;
MyState = StateKeeper.DefaultState;
SpecialState = specialState;
}
/// <summary>
/// Determines whether the filter can pass for the given context based on state logic.
/// </summary>
/// <param name="context">The filter execution context.</param>
/// <returns>True if the filter passes; otherwise, false.</returns>
public override bool CanPass(FilterExecutionContext<Update> context)
{
if (SpecialState == SpecialState.AnyState)
return true;
if (!StateKeeper.TryGetState(context.Input, out TState? state))
return SpecialState == SpecialState.NoState;
if (state == null)
return false;
return MyState.Equals(state);
}
}
}
@@ -1,41 +1,40 @@
using Telegram.Bot.Types;
using Telegrator.Core.Filters;
namespace Telegrator.Core.Handlers.Building
namespace Telegrator.Core.Handlers.Building;
/// <summary>
/// Delegate for validating an update in a filter context.
/// </summary>
/// <param name="context">The filter execution context.</param>
/// <returns>True if the update is valid; otherwise, false.</returns>
public delegate bool UpdateValidateAction(FilterExecutionContext<Update> context);
/// <summary>
/// Filter that uses a delegate to validate updates.
/// </summary>
public class UpdateValidateFilter : IFilter<Update>
{
/// <summary>
/// Delegate for validating an update in a filter context.
/// Gets a value indicating whether this filter is collectable. Always false for this filter.
/// </summary>
/// <param name="context">The filter execution context.</param>
/// <returns>True if the update is valid; otherwise, false.</returns>
public delegate bool UpdateValidateAction(FilterExecutionContext<Update> context);
public bool IsCollectible => false;
private readonly UpdateValidateAction UpdateValidateAction;
/// <summary>
/// Filter that uses a delegate to validate updates.
/// Initializes a new instance of the <see cref="UpdateValidateFilter"/> class.
/// </summary>
public class UpdateValidateFilter : IFilter<Update>
/// <param name="updateValidateAction">The validation delegate to use.</param>
public UpdateValidateFilter(UpdateValidateAction updateValidateAction)
{
/// <summary>
/// Gets a value indicating whether this filter is collectable. Always false for this filter.
/// </summary>
public bool IsCollectible => false;
private readonly UpdateValidateAction UpdateValidateAction;
/// <summary>
/// Initializes a new instance of the <see cref="UpdateValidateFilter"/> class.
/// </summary>
/// <param name="updateValidateAction">The validation delegate to use.</param>
public UpdateValidateFilter(UpdateValidateAction updateValidateAction)
{
UpdateValidateAction = updateValidateAction;
}
/// <summary>
/// Determines whether the filter can pass for the given context using the validation delegate.
/// </summary>
/// <param name="info">The filter execution context.</param>
/// <returns>True if the filter passes; otherwise, false.</returns>
public bool CanPass(FilterExecutionContext<Update> info)
=> UpdateValidateAction.Invoke(info);
UpdateValidateAction = updateValidateAction;
}
/// <summary>
/// Determines whether the filter can pass for the given context using the validation delegate.
/// </summary>
/// <param name="info">The filter execution context.</param>
/// <returns>True if the filter passes; otherwise, false.</returns>
public bool CanPass(FilterExecutionContext<Update> info)
=> UpdateValidateAction.Invoke(info);
}
@@ -1,27 +1,30 @@
using Telegram.Bot;
using Telegram.Bot.Types;
using Telegrator.Core.Filters;
using Telegrator.Core.States;
namespace Telegrator.Core.Handlers
namespace Telegrator.Core.Handlers;
/// <summary>
/// Represents an empty handler container that throws <see cref="NotImplementedException"/> for all members.
/// </summary>
public class EmptyHandlerContainer : IHandlerContainer
{
/// <summary>
/// Represents an empty handler container that throws <see cref="NotImplementedException"/> for all members.
/// </summary>
public class EmptyHandlerContainer : IHandlerContainer
{
/// <inheritdoc/>
public Update HandlingUpdate => throw new NotImplementedException();
/// <inheritdoc/>
public Update HandlingUpdate => throw new NotImplementedException();
/// <inheritdoc/>
public ITelegramBotClient Client => throw new NotImplementedException();
/// <inheritdoc/>
public ITelegramBotClient Client => throw new NotImplementedException();
/// <inheritdoc/>
public Dictionary<string, object> ExtraData => throw new NotImplementedException();
/// <inheritdoc/>
public Dictionary<string, object> ExtraData => throw new NotImplementedException();
/// <inheritdoc/>
public CompletedFiltersList CompletedFilters => throw new NotImplementedException();
/// <inheritdoc/>
public CompletedFiltersList CompletedFilters => throw new NotImplementedException();
/// <inheritdoc/>
public IAwaitingProvider AwaitingProvider => throw new NotImplementedException();
}
/// <inheritdoc/>
public IAwaitingProvider AwaitingProvider => throw new NotImplementedException();
/// <inheritdoc/>
public IStateStorage StateStorage => throw new NotImplementedException();
}
@@ -1,27 +1,26 @@
namespace Telegrator.Core.Handlers
namespace Telegrator.Core.Handlers;
/// <summary>
/// Represents a token that tracks the lifetime of a handler instance.
/// </summary>
public class HandlerLifetimeToken
{
/// <summary>
/// Represents a token that tracks the lifetime of a handler instance.
/// Event triggered when the handler's lifetime has ended.
/// </summary>
public class HandlerLifetimeToken
public event Action<HandlerLifetimeToken>? OnLifetimeEnded;
/// <summary>
/// Gets a value indicating whether the handler's lifetime has ended.
/// </summary>
public bool IsEnded { get; private set; }
/// <summary>
/// Marks the handler's lifetime as ended and triggers the event.
/// </summary>
public void LifetimeEnded()
{
/// <summary>
/// Event triggered when the handler's lifetime has ended.
/// </summary>
public event Action<HandlerLifetimeToken>? OnLifetimeEnded;
/// <summary>
/// Gets a value indicating whether the handler's lifetime has ended.
/// </summary>
public bool IsEnded { get; private set; }
/// <summary>
/// Marks the handler's lifetime as ended and triggers the event.
/// </summary>
public void LifetimeEnded()
{
IsEnded = true;
OnLifetimeEnded?.Invoke(this);
}
IsEnded = true;
OnLifetimeEnded?.Invoke(this);
}
}
@@ -1,38 +1,43 @@
using Telegram.Bot;
using Telegram.Bot.Types;
using Telegrator.Core.Filters;
using Telegrator.Core.States;
namespace Telegrator.Core.Handlers
namespace Telegrator.Core.Handlers;
/// <summary>
/// Interface for handler containers that provide context and resources for update handlers.
/// Contains all necessary information and services that handlers need during execution.
/// </summary>
public interface IHandlerContainer
{
/// <summary>
/// Interface for handler containers that provide context and resources for update handlers.
/// Contains all necessary information and services that handlers need during execution.
/// Gets the <see cref="Update"/> being handled.
/// </summary>
public interface IHandlerContainer
{
/// <summary>
/// Gets the <see cref="Update"/> being handled.
/// </summary>
public Update HandlingUpdate { get; }
public Update HandlingUpdate { get; }
/// <summary>
/// Gets the <see cref="ITelegramBotClient"/> used for this handler.
/// </summary>
public ITelegramBotClient Client { get; }
/// <summary>
/// Gets the <see cref="ITelegramBotClient"/> used for this handler.
/// </summary>
public ITelegramBotClient Client { get; }
/// <summary>
/// Gets the extra data associated with the handler execution.
/// </summary>
public Dictionary<string, object> ExtraData { get; }
/// <summary>
/// Gets the extra data associated with the handler execution.
/// </summary>
public Dictionary<string, object> ExtraData { get; }
/// <summary>
/// Gets the <see cref="CompletedFiltersList"/> for this handler.
/// </summary>
public CompletedFiltersList CompletedFilters { get; }
/// <summary>
/// Gets the <see cref="CompletedFiltersList"/> for this handler.
/// </summary>
public CompletedFiltersList CompletedFilters { get; }
/// <summary>
/// Gets the <see cref="IAwaitingProvider"/> for awaiting operations.
/// </summary>
public IAwaitingProvider AwaitingProvider { get; }
}
/// <summary>
/// Gets the <see cref="IAwaitingProvider"/> for awaiting operations.
/// </summary>
public IAwaitingProvider AwaitingProvider { get; }
/// <summary>
/// Gets the <see cref="IStateStorage"/> for state managment.
/// </summary>
public IStateStorage StateStorage { get; }
}
@@ -1,18 +1,17 @@
using Telegrator.Core.Descriptors;
namespace Telegrator.Core.Handlers
namespace Telegrator.Core.Handlers;
/// <summary>
/// Factory interface for creating handler containers.
/// Provides a way to create handler containers with specific providers and handler information.
/// </summary>
public interface IHandlerContainerFactory
{
/// <summary>
/// Factory interface for creating handler containers.
/// Provides a way to create handler containers with specific providers and handler information.
/// Creates a new <see cref="IHandlerContainer"/> for the specified awaiting provider and handler info.
/// </summary>
public interface IHandlerContainerFactory
{
/// <summary>
/// Creates a new <see cref="IHandlerContainer"/> for the specified awaiting provider and handler info.
/// </summary>
/// <param name="handlerInfo">The <see cref="DescribedHandlerDescriptor"/> for the handler.</param>
/// <returns>A new <see cref="IHandlerContainer"/> instance.</returns>
public IHandlerContainer CreateContainer(DescribedHandlerDescriptor handlerInfo);
}
/// <param name="handlerInfo">The <see cref="DescribedHandlerDescriptor"/> for the handler.</param>
/// <returns>A new <see cref="IHandlerContainer"/> instance.</returns>
public IHandlerContainer CreateContainer(DescribedHandlerDescriptor handlerInfo);
}
+149 -150
View File
@@ -4,179 +4,178 @@ using Telegram.Bot.Types.Enums;
using Telegrator.Core.Descriptors;
using Telegrator.Handlers.Diagnostics;
namespace Telegrator.Core.Handlers
namespace Telegrator.Core.Handlers;
/// <summary>
/// Base class for update handlers, providing execution and lifetime management for Telegram updates.
/// </summary>
public abstract class UpdateHandlerBase(UpdateType handlingUpdateType) : IUpdateHandlerBase
{
/// <summary>
/// Base class for update handlers, providing execution and lifetime management for Telegram updates.
/// Gets the <see cref="UpdateType"/> that this handler processes.
/// </summary>
public abstract class UpdateHandlerBase(UpdateType handlingUpdateType) : IUpdateHandlerBase
public UpdateType HandlingUpdateType { get; } = handlingUpdateType;
/// <summary>
/// Gets the <see cref="HandlerLifetimeToken"/> associated with this handler instance.
/// </summary>
public HandlerLifetimeToken LifetimeToken { get; } = new HandlerLifetimeToken();
/// <inheritdoc cref="Result.Ok"/>
public static Result Ok => Result.Ok();
/// <inheritdoc cref="Result.Fault"/>
public static Result Fault => Result.Fault();
/// <inheritdoc cref="Result.Next"/>
public static Result Next => Result.Next();
/// <summary>
/// Executes the handler logic and marks the lifetime as ended after execution.
/// </summary>
/// <param name="described"></param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public async Task<Result> Execute(DescribedHandlerDescriptor described, CancellationToken cancellationToken = default)
{
/// <summary>
/// Gets the <see cref="UpdateType"/> that this handler processes.
/// </summary>
public UpdateType HandlingUpdateType { get; } = handlingUpdateType;
if (LifetimeToken.IsEnded)
throw new Exception();
/// <summary>
/// Gets the <see cref="HandlerLifetimeToken"/> associated with this handler instance.
/// </summary>
public HandlerLifetimeToken LifetimeToken { get; } = new HandlerLifetimeToken();
/// <inheritdoc cref="Result.Ok"/>
public static Result Ok => Result.Ok();
/// <inheritdoc cref="Result.Fault"/>
public static Result Fault => Result.Fault();
/// <inheritdoc cref="Result.Next"/>
public static Result Next => Result.Next();
/// <summary>
/// Executes the handler logic and marks the lifetime as ended after execution.
/// </summary>
/// <param name="described"></param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public async Task<Result> Execute(DescribedHandlerDescriptor described, CancellationToken cancellationToken = default)
try
{
if (LifetimeToken.IsEnded)
throw new Exception();
// Creating container
IHandlerContainer container = GetContainer(described);
DescriptorAspectsSet? aspects = described.From.Aspects;
// Executing pre processor
if (aspects != null)
{
try
{
Result? preResult = await aspects
.ExecutePre(this, container, cancellationToken)
.ConfigureAwait(false);
if (!preResult.Positive)
return preResult;
}
catch (NotImplementedException)
{
_ = 0xBAD + 0xC0DE;
}
}
try
{
// Creating container
IHandlerContainer container = GetContainer(described);
DescriptorAspectsSet? aspects = described.From.Aspects;
// Executing handler
Result execResult = await ExecuteInternal(container, cancellationToken).ConfigureAwait(false);
if (!execResult.Positive)
return execResult;
}
catch (NotImplementedException)
{
_ = 0xBAD + 0xC0DE;
}
// Executing pre processor
try
{
// Executing post processor
if (aspects != null)
{
try
{
Result? preResult = await aspects
.ExecutePre(this, container, cancellationToken)
.ConfigureAwait(false);
if (!preResult.Positive)
return preResult;
}
catch (NotImplementedException)
{
_ = 0xBAD + 0xC0DE;
}
}
try
{
// Executing handler
Result execResult = await ExecuteInternal(container, cancellationToken).ConfigureAwait(false);
if (!execResult.Positive)
return execResult;
}
catch (NotImplementedException)
{
_ = 0xBAD + 0xC0DE;
}
try
{
// Executing post processor
if (aspects != null)
{
Result postResult = await aspects
.ExecutePost(this, container, cancellationToken)
.ConfigureAwait(false);
if (!postResult.Positive)
return postResult;
}
}
catch (NotImplementedException)
{
_ = 0xBAD + 0xC0DE;
}
// Success
return Result.Ok();
}
catch (OperationCanceledException)
{
// Cancelled
_ = 0xBAD + 0xC0DE;
return Result.Ok();
}
catch (Exception exception)
{
try
{
await described.UpdateRouter
.HandleErrorAsync(described.Client, exception, HandleErrorSource.HandleUpdateError, cancellationToken)
Result postResult = await aspects
.ExecutePost(this, container, cancellationToken)
.ConfigureAwait(false);
}
catch (NotImplementedException)
{
_ = 0xBAD + 0xC0DE;
}
return Result.Fault();
if (!postResult.Positive)
return postResult;
}
}
finally
catch (NotImplementedException)
{
LifetimeToken.LifetimeEnded();
_ = 0xBAD + 0xC0DE;
}
// Success
return Result.Ok();
}
internal IHandlerContainer GetContainer(DescribedHandlerDescriptor handlerInfo)
catch (OperationCanceledException)
{
if (this is IHandlerContainerFactory handlerDefainedContainerFactory)
return handlerDefainedContainerFactory.CreateContainer(handlerInfo);
if (handlerInfo.UpdateRouter.DefaultContainerFactory is not null)
return handlerInfo.UpdateRouter.DefaultContainerFactory.CreateContainer(handlerInfo);
throw new Exception();
// Cancelled
_ = 0xBAD + 0xC0DE;
return Result.Ok();
}
/// <summary>
/// Executes the handler logic for the given container and cancellation token.
/// </summary>
/// <param name="container">The <see cref="IHandlerContainer"/> for the update.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
protected abstract Task<Result> ExecuteInternal(IHandlerContainer container, CancellationToken cancellationToken);
/// <summary>
/// Dispose resources of this handler. Override if needed
/// </summary>
/// <param name="disposing"></param>
/// <returns>Return <see langword="true"/> if dispose was successfull and garbage collecting for this object can be supressed</returns>
protected virtual bool Dispose(bool disposing)
catch (Exception exception)
{
return false;
try
{
await described.UpdateRouter
.HandleErrorAsync(described.Client, exception, HandleErrorSource.HandleUpdateError, cancellationToken)
.ConfigureAwait(false);
}
catch (NotImplementedException)
{
_ = 0xBAD + 0xC0DE;
}
return Result.Fault();
}
/// <summary>
/// Handles failed filters during handler describing.
/// Use <see cref="Result"/> to control how router should treat this fail.
/// <see cref="Result.Next"/> to silently continue decribing.
/// <see cref="Result.Fault"/> to stop\break desribing sequence.
/// </summary>
/// <param name="report"></param>
/// <param name="client"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public virtual Task<Result> FiltersFallback(FiltersFallbackReport report, ITelegramBotClient client, CancellationToken cancellationToken = default)
finally
{
return Task.FromResult(Result.Ok());
}
/// <inheritdoc/>
public void Dispose()
{
if (LifetimeToken.IsEnded)
return;
if (Dispose(true))
GC.SuppressFinalize(this);
LifetimeToken.LifetimeEnded();
}
}
private IHandlerContainer GetContainer(DescribedHandlerDescriptor handlerInfo)
{
if (this is IHandlerContainerFactory handlerDefainedContainerFactory)
return handlerDefainedContainerFactory.CreateContainer(handlerInfo);
if (handlerInfo.UpdateRouter.DefaultContainerFactory is not null)
return handlerInfo.UpdateRouter.DefaultContainerFactory.CreateContainer(handlerInfo);
throw new Exception();
}
/// <summary>
/// Executes the handler logic for the given container and cancellation token.
/// </summary>
/// <param name="container">The <see cref="IHandlerContainer"/> for the update.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
protected abstract Task<Result> ExecuteInternal(IHandlerContainer container, CancellationToken cancellationToken);
/// <summary>
/// Dispose resources of this handler. Override if needed
/// </summary>
/// <param name="disposing"></param>
/// <returns>Return <see langword="true"/> if dispose was successfull and garbage collecting for this object can be supressed</returns>
protected virtual bool Dispose(bool disposing)
{
return false;
}
/// <summary>
/// Handles failed filters during handler describing.
/// Use <see cref="Result"/> to control how router should treat this fail.
/// <see cref="Result.Next"/> to silently continue decribing.
/// <see cref="Result.Fault"/> to stop\break desribing sequence.
/// </summary>
/// <param name="report"></param>
/// <param name="client"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public virtual Task<Result> FiltersFallback(FiltersFallbackReport report, ITelegramBotClient client, CancellationToken cancellationToken = default)
{
return Task.FromResult(Result.Ok());
}
/// <inheritdoc/>
public void Dispose()
{
if (LifetimeToken.IsEnded)
return;
if (Dispose(true))
GC.SuppressFinalize(this);
}
}
+10 -11
View File
@@ -1,17 +1,16 @@
using Telegrator.Core.Descriptors;
namespace Telegrator.Core
namespace Telegrator.Core;
/// <summary>
/// Provider for managing awaiting handlers that can wait for specific update types.
/// </summary>
public interface IAwaitingProvider : IHandlersProvider
{
/// <summary>
/// Provider for managing awaiting handlers that can wait for specific update types.
/// Registers the usage of a handler and returns a disposable object to manage its lifetime.
/// </summary>
public interface IAwaitingProvider : IHandlersProvider
{
/// <summary>
/// Registers the usage of a handler and returns a disposable object to manage its lifetime.
/// </summary>
/// <param name="handlerDescriptor">The <see cref="HandlerDescriptor"/> to use.</param>
/// <returns>An <see cref="IDisposable"/> that manages the handler's usage lifetime.</returns>
public IDisposable UseHandler(HandlerDescriptor handlerDescriptor);
}
/// <param name="handlerDescriptor">The <see cref="HandlerDescriptor"/> to use.</param>
/// <returns>An <see cref="IDisposable"/> that manages the handler's usage lifetime.</returns>
public IDisposable UseHandler(HandlerDescriptor handlerDescriptor);
}
+9 -10
View File
@@ -1,14 +1,13 @@
namespace Telegrator.Core
namespace Telegrator.Core;
/// <summary>
/// Interface for providers that collect and manage handler collections.
/// Provides access to a collection of handlers for various processing operations.
/// </summary>
public interface ICollectingProvider
{
/// <summary>
/// Interface for providers that collect and manage handler collections.
/// Provides access to a collection of handlers for various processing operations.
/// Gets the collection of handlers managed by this provider.
/// </summary>
public interface ICollectingProvider
{
/// <summary>
/// Gets the collection of handlers managed by this provider.
/// </summary>
public IHandlersCollection Handlers { get; }
}
public IHandlersCollection Handlers { get; }
}

Some files were not shown because too many files have changed in this diff Show More