11 Commits

Author SHA1 Message Date
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
78 changed files with 1516 additions and 2287 deletions
+2 -2
View File
@@ -361,5 +361,5 @@ MigrationBackup/
# Fody - auto-generated XML schema
FodyWeavers.xsd
/GETTING_STARTED.md
/ANNOTATION_OVERVIEW.md
.aider*
+3 -8
View File
@@ -1,14 +1,10 @@
# 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.
---
@@ -95,13 +91,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,15 @@ 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();
.WithMembers([extensionsClass]);
CompilationUnitSyntax compilationUnit = SyntaxFactory.CompilationUnit()
.WithUsings([.. usings])
.WithMembers([namespaceDeclaration]);
.WithMembers([namespaceDeclaration])
.NormalizeWhitespace();
context.AddSource("GeneratedHandlerBuilderExtensions.cs", compilationUnit.ToFullString());
}
@@ -175,7 +179,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 +208,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 +233,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 +241,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>
+27 -1
View File
@@ -100,6 +100,12 @@
<member name="P:Telegrator.Hosting.Web.TelegramBotWebHostBuilder.Environment">
<inheritdoc/>
</member>
<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,Microsoft.AspNetCore.Builder.WebApplicationOptions)">
<summary>
Initializes a new instance of the <see cref="T:Telegrator.Hosting.Web.TelegramBotWebHostBuilder"/> class.
@@ -107,6 +113,14 @@
<param name="webApplicationBuilder"></param>
<param name="settings"></param>
</member>
<member name="M:Telegrator.Hosting.Web.TelegramBotWebHostBuilder.#ctor(Microsoft.AspNetCore.Builder.WebApplicationBuilder,Telegrator.TelegratorOptions,Microsoft.AspNetCore.Builder.WebApplicationOptions)">
<summary>
Initializes a new instance of the <see cref="T:Telegrator.Hosting.Web.TelegramBotWebHostBuilder"/> class.
</summary>
<param name="webApplicationBuilder"></param>
<param name="options"></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)">
<summary>
Initializes a new instance of the <see cref="T:Telegrator.Hosting.Web.TelegramBotWebHostBuilder"/> class.
@@ -115,12 +129,24 @@
<param name="handlers"></param>
<param name="settings"></param>
</member>
<member name="M:Telegrator.Hosting.Web.TelegramBotWebHostBuilder.#ctor(Microsoft.AspNetCore.Builder.WebApplicationBuilder,Telegrator.Core.IHandlersCollection,Telegrator.TelegratorOptions,Microsoft.AspNetCore.Builder.WebApplicationOptions)">
<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="options"></param>
<param name="settings"></param>
</member>
<member name="M:Telegrator.Hosting.Web.TelegramBotWebHostBuilder.Build">
<summary>
Builds the host.
</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.
@@ -185,7 +211,7 @@
<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(Microsoft.Extensions.Hosting.IHostApplicationBuilder,Telegrator.TelegratorOptions,Telegrator.Core.IHandlersCollection)">
<summary>
Replaces TelegramBotWebHostBuilder. Configures DI, options, and handlers.
</summary>
+29 -30
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
@@ -35,27 +35,6 @@
Provides access to configuration of this Hosted telegram bot
</summary>
</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.
</summary>
</member>
<member name="T:Telegrator.Hosting.TelegramBotHost">
<summary>
Represents a hosted telegram bot
@@ -133,6 +112,12 @@
<member name="P:Telegrator.Hosting.TelegramBotHostBuilder.Environment">
<inheritdoc/>
</member>
<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,Microsoft.Extensions.Hosting.HostApplicationBuilderSettings)">
<summary>
Initializes a new instance of the <see cref="T:Telegrator.Hosting.TelegramBotHostBuilder"/> class.
@@ -140,6 +125,14 @@
<param name="hostApplicationBuilder"></param>
<param name="settings"></param>
</member>
<member name="M:Telegrator.Hosting.TelegramBotHostBuilder.#ctor(Microsoft.Extensions.Hosting.HostApplicationBuilder,Telegrator.TelegratorOptions,Microsoft.Extensions.Hosting.HostApplicationBuilderSettings)">
<summary>
Initializes a new instance of the <see cref="T:Telegrator.Hosting.TelegramBotHostBuilder"/> class.
</summary>
<param name="hostApplicationBuilder"></param>
<param name="options"></param>
<param name="settings"></param>
</member>
<member name="M:Telegrator.Hosting.TelegramBotHostBuilder.#ctor(Microsoft.Extensions.Hosting.HostApplicationBuilder,Telegrator.Core.IHandlersCollection,Microsoft.Extensions.Hosting.HostApplicationBuilderSettings)">
<summary>
Initializes a new instance of the <see cref="T:Telegrator.Hosting.TelegramBotHostBuilder"/> class.
@@ -148,12 +141,24 @@
<param name="handlers"></param>
<param name="settings"></param>
</member>
<member name="M:Telegrator.Hosting.TelegramBotHostBuilder.#ctor(Microsoft.Extensions.Hosting.HostApplicationBuilder,Telegrator.Core.IHandlersCollection,Telegrator.TelegratorOptions,Microsoft.Extensions.Hosting.HostApplicationBuilderSettings)">
<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="options"></param>
<param name="settings"></param>
</member>
<member name="M:Telegrator.Hosting.TelegramBotHostBuilder.Build">
<summary>
Builds the host.
</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 +195,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 +203,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)">
@@ -259,7 +258,7 @@
<member name="M:Telegrator.HostBuilderExtensions.get_Handlers(Microsoft.Extensions.Hosting.IHostApplicationBuilder)">
<inheritdoc cref="P:Telegrator.HostBuilderExtensions.&lt;G&gt;$605D8CCF64349EA050C790D67C500BD9.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(Microsoft.Extensions.Hosting.IHostApplicationBuilder,Telegrator.TelegratorOptions,Telegrator.Core.IHandlersCollection)">
<summary>
Replaces TelegramBotWebHostBuilder. Configures DI, options, and handlers.
</summary>
+264 -689
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.1</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.4</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.11.8 " />
</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>
@@ -53,7 +53,6 @@ namespace Telegrator.Hosting.Web
{
// Building proxy application
_innerApp = webApplicationBuilder.Build();
_innerApp.UseTelegratorWeb();
// Reruesting services for this host
_updateRouter = Services.GetRequiredService<IUpdateRouter>();
@@ -1,6 +1,7 @@
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;
@@ -11,7 +12,7 @@ namespace Telegrator.Hosting.Web
/// <summary>
/// Represents a web hosted telegram bots and services builder that helps manage configuration, logging, lifetime, and more.
/// </summary>
public class TelegramBotWebHostBuilder : ITelegramBotHostBuilder
public class TelegramBotWebHostBuilder : IHostApplicationBuilder, ICollectingProvider
{
private readonly WebApplicationBuilder _innerBuilder;
private readonly WebApplicationOptions _settings;
@@ -32,17 +33,37 @@ namespace Telegrator.Hosting.Web
/// <inheritdoc/>
public IHostEnvironment Environment => _innerBuilder.Environment;
/// <inheritdoc/>
public IDictionary<object, object> Properties => ((IHostApplicationBuilder)_innerBuilder).Properties;
/// <inheritdoc/>
public IMetricsBuilder Metrics => _innerBuilder.Metrics;
/// <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)
public TelegramBotWebHostBuilder(WebApplicationBuilder webApplicationBuilder, WebApplicationOptions? settings = null)
{
_innerBuilder = webApplicationBuilder ?? throw new ArgumentNullException(nameof(webApplicationBuilder));
_settings = settings ?? throw new ArgumentNullException(nameof(settings));
_innerBuilder.AddTelegratorWeb();
this.AddTelegratorWeb();
}
/// <summary>
/// Initializes a new instance of the <see cref="TelegramBotWebHostBuilder"/> class.
/// </summary>
/// <param name="webApplicationBuilder"></param>
/// <param name="options"></param>
/// <param name="settings"></param>
public TelegramBotWebHostBuilder(WebApplicationBuilder webApplicationBuilder, TelegratorOptions? options, WebApplicationOptions? settings)
{
_innerBuilder = webApplicationBuilder ?? throw new ArgumentNullException(nameof(webApplicationBuilder));
_settings = settings ?? throw new ArgumentNullException(nameof(settings));
this.AddTelegratorWeb(options, null);
}
/// <summary>
@@ -56,7 +77,22 @@ namespace Telegrator.Hosting.Web
_innerBuilder = webApplicationBuilder ?? throw new ArgumentNullException(nameof(webApplicationBuilder));
_settings = settings ?? throw new ArgumentNullException(nameof(settings));
_innerBuilder.AddTelegratorWeb(null, handlers);
this.AddTelegratorWeb(null, handlers);
}
/// <summary>
/// Initializes a new instance of the <see cref="TelegramBotWebHostBuilder"/> class.
/// </summary>
/// <param name="webApplicationBuilder"></param>
/// <param name="handlers"></param>
/// <param name="options"></param>
/// <param name="settings"></param>
public TelegramBotWebHostBuilder(WebApplicationBuilder webApplicationBuilder, IHandlersCollection handlers, TelegratorOptions? options, WebApplicationOptions settings)
{
_innerBuilder = webApplicationBuilder ?? throw new ArgumentNullException(nameof(webApplicationBuilder));
_settings = settings ?? throw new ArgumentNullException(nameof(settings));
this.AddTelegratorWeb(options, handlers);
}
/// <summary>
@@ -69,5 +105,11 @@ namespace Telegrator.Hosting.Web
host.UseTelegrator();
return host;
}
/// <inheritdoc/>
public void ConfigureContainer<TContainerBuilder>(IServiceProviderFactory<TContainerBuilder> factory, Action<TContainerBuilder>? configure = null) where TContainerBuilder : notnull
{
((IHostApplicationBuilder)_innerBuilder).ConfigureContainer(factory, configure);
}
}
}
+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.4</Version>
<Authors>Rikitav Tim4ik</Authors>
<Company>Rikitav Tim4ik</Company>
<RepositoryUrl>https://github.com/Rikitav/Telegrator</RepositoryUrl>
@@ -7,6 +7,7 @@ using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Telegram.Bot;
using Telegrator.Core;
using Telegrator.Hosting;
using Telegrator.Hosting.Web;
using Telegrator.Mediation;
using Telegrator.Providers;
@@ -12,7 +12,7 @@ namespace Telegrator.Hosting
/// <param name="client"></param>
/// <param name="services"></param>
/// <param name="configuration"></param>
public class HostedTelegramBotInfo(ITelegramBotClient client, IServiceProvider services, IConfigurationManager configuration) : ITelegramBotInfo
public class HostedTelegramBotInfo(ITelegramBotClient client, IServiceProvider services, IConfiguration configuration) : ITelegramBotInfo
{
/// <inheritdoc/>
public User User { get; } = client.GetMe().Result;
@@ -25,6 +25,6 @@ namespace Telegrator.Hosting
/// <summary>
/// Provides access to configuration of this Hosted telegram bot
/// </summary>
public IConfigurationManager Configuration { get; } = configuration;
public IConfiguration Configuration { get; } = configuration;
}
}
@@ -1,29 +0,0 @@
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Telegrator.Core;
namespace Telegrator.Hosting
{
/// <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; }
}
}
@@ -1,5 +1,4 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Telegrator.Core;
@@ -12,14 +11,13 @@ namespace Telegrator.Hosting
public class TelegramBotHost : IHost, ITelegratorBot
{
private readonly IHost _innerHost;
private readonly IServiceProvider _serviceProvider;
private readonly IUpdateRouter _updateRouter;
private readonly ILogger<TelegramBotHost> _logger;
private bool _disposed;
/// <inheritdoc/>
public IServiceProvider Services => _serviceProvider;
public IServiceProvider Services => _innerHost.Services;
/// <inheritdoc/>
public IUpdateRouter UpdateRouter => _updateRouter;
@@ -40,8 +38,6 @@ namespace Telegrator.Hosting
// Building proxy hoster
_innerHost = hostApplicationBuilder.Build();
_serviceProvider = _innerHost.Services;
_innerHost.UseTelegrator();
// Reruesting services for this host
_updateRouter = Services.GetRequiredService<IUpdateRouter>();
@@ -56,9 +52,6 @@ namespace Telegrator.Hosting
{
HostApplicationBuilder innerBuilder = new HostApplicationBuilder(settings: null);
TelegramBotHostBuilder builder = new TelegramBotHostBuilder(innerBuilder, null);
builder.Services.AddTelegramBotHostDefaults();
builder.Services.AddTelegramReceiver();
return builder;
}
@@ -70,9 +63,6 @@ namespace Telegrator.Hosting
{
HostApplicationBuilder innerBuilder = new HostApplicationBuilder(settings);
TelegramBotHostBuilder builder = new TelegramBotHostBuilder(innerBuilder, settings);
builder.Services.AddTelegramBotHostDefaults();
builder.Services.AddTelegramReceiver();
return builder;
}
@@ -1,7 +1,9 @@
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Diagnostics.Metrics;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Telegrator.Core;
using Telegrator.Providers;
@@ -11,7 +13,7 @@ namespace Telegrator.Hosting
/// <summary>
/// Represents a hosted telegram bots and services builder that helps manage configuration, logging, lifetime, and more.
/// </summary>
public class TelegramBotHostBuilder : ICollectingProvider
public class TelegramBotHostBuilder : IHostApplicationBuilder, ICollectingProvider
{
private readonly HostApplicationBuilder _innerBuilder;
private readonly HostApplicationBuilderSettings _settings;
@@ -32,6 +34,12 @@ namespace Telegrator.Hosting
/// <inheritdoc/>
public IHostEnvironment Environment => _innerBuilder.Environment;
/// <inheritdoc/>
public IDictionary<object, object> Properties => ((IHostApplicationBuilder)_innerBuilder).Properties;
/// <inheritdoc/>
public IMetricsBuilder Metrics => _innerBuilder.Metrics;
/// <summary>
/// Initializes a new instance of the <see cref="TelegramBotHostBuilder"/> class.
/// </summary>
@@ -42,8 +50,21 @@ namespace Telegrator.Hosting
_innerBuilder = hostApplicationBuilder ?? throw new ArgumentNullException(nameof(hostApplicationBuilder));
_settings = settings ?? new HostApplicationBuilderSettings();
_innerBuilder.AddTelegrator();
_innerBuilder.Logging.ClearProviders();
this.AddTelegrator();
}
/// <summary>
/// Initializes a new instance of the <see cref="TelegramBotHostBuilder"/> class.
/// </summary>
/// <param name="hostApplicationBuilder"></param>
/// <param name="options"></param>
/// <param name="settings"></param>
public TelegramBotHostBuilder(HostApplicationBuilder hostApplicationBuilder, TelegratorOptions? options, HostApplicationBuilderSettings? settings)
{
_innerBuilder = hostApplicationBuilder ?? throw new ArgumentNullException(nameof(hostApplicationBuilder));
_settings = settings ?? new HostApplicationBuilderSettings();
this.AddTelegrator(options, null);
}
/// <summary>
@@ -52,13 +73,27 @@ namespace Telegrator.Hosting
/// <param name="hostApplicationBuilder"></param>
/// <param name="handlers"></param>
/// <param name="settings"></param>
public TelegramBotHostBuilder(HostApplicationBuilder hostApplicationBuilder, IHandlersCollection handlers, HostApplicationBuilderSettings? settings = null)
public TelegramBotHostBuilder(HostApplicationBuilder hostApplicationBuilder, IHandlersCollection handlers, HostApplicationBuilderSettings? settings)
{
_innerBuilder = hostApplicationBuilder ?? throw new ArgumentNullException(nameof(hostApplicationBuilder));
_settings = settings ?? new HostApplicationBuilderSettings();
_innerBuilder.AddTelegrator(null, handlers);
_innerBuilder.Logging.ClearProviders();
this.AddTelegrator(null, handlers);
}
/// <summary>
/// Initializes a new instance of the <see cref="TelegramBotHostBuilder"/> class.
/// </summary>
/// <param name="hostApplicationBuilder"></param>
/// <param name="handlers"></param>
/// <param name="options"></param>
/// <param name="settings"></param>
public TelegramBotHostBuilder(HostApplicationBuilder hostApplicationBuilder, IHandlersCollection handlers, TelegratorOptions? options, HostApplicationBuilderSettings? settings)
{
_innerBuilder = hostApplicationBuilder ?? throw new ArgumentNullException(nameof(hostApplicationBuilder));
_settings = settings ?? new HostApplicationBuilderSettings();
this.AddTelegrator(options, handlers);
}
/// <summary>
@@ -71,5 +106,11 @@ namespace Telegrator.Hosting
host.UseTelegrator();
return host;
}
/// <inheritdoc/>
public void ConfigureContainer<TContainerBuilder>(IServiceProviderFactory<TContainerBuilder> factory, Action<TContainerBuilder>? configure = null) where TContainerBuilder : notnull
{
this.ConfigureContainer(factory, configure);
}
}
}
@@ -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,6 +4,7 @@ using Telegram.Bot;
using Telegram.Bot.Polling;
using Telegram.Bot.Types;
using Telegrator.Core;
using Telegrator.Core.States;
using Telegrator.Mediation;
namespace Telegrator.Polling
@@ -20,10 +21,10 @@ namespace Telegrator.Polling
public HostUpdateRouter(
IHandlersProvider handlersProvider,
IAwaitingProvider awaitingProvider,
IStateStorage stateStorage,
IOptions<TelegratorOptions> options,
IUpdateHandlersPool handlersPool,
ITelegramBotInfo botInfo,
ILogger<HostUpdateRouter> logger) : base(handlersProvider, awaitingProvider, options.Value, handlersPool, botInfo)
ILogger<HostUpdateRouter> logger) : base(handlersProvider, awaitingProvider, stateStorage, options.Value, botInfo)
{
Logger = logger;
ExceptionHandler = new DefaultRouterExceptionHandler(HandleException);
@@ -15,7 +15,7 @@
<EnforceCodeStyleInBuild>True</EnforceCodeStyleInBuild>
<Title>Telegrator.Hosting</Title>
<Version>1.16.1</Version>
<Version>1.16.4</Version>
<Authors>Rikitav Tim4ik</Authors>
<Company>Rikitav Tim4ik</Company>
<RepositoryUrl>https://github.com/Rikitav/Telegrator</RepositoryUrl>
@@ -120,7 +120,6 @@ public static class ServicesCollectionExtensions
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>();
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<TargetFramework>netstandard2.1</TargetFramework>
<LangVersion>latest</LangVersion>
</PropertyGroup>
@@ -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()) { }
}
}
@@ -2,7 +2,7 @@
using Telegram.Bot.Types.Enums;
using Telegrator.Filters;
namespace Telegrator.Annotations.Targetted
namespace Telegrator.Annotations
{
/// <summary>
/// Attribute for filtering message with command "start" in bot's private chats.
@@ -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);
}
}
}
@@ -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);
}
}
@@ -24,7 +24,7 @@ namespace Telegrator.Core.Descriptors
public MethodHandlerDescriptor(AbstractHandlerAction<TUpdate> action) : base(DescriptorType.General, typeof(MethodHandler), true)
{
UpdateHandlerAttributeBase handlerAttribute = HandlerInspector.GetHandlerAttribute(action.Method);
StateKeeperAttributeBase? stateKeeperAttribute = HandlerInspector.GetStateKeeperAttribute(action.Method);
IFilter<Update>? stateKeeperAttribute = HandlerInspector.GetStateKeeperAttribute(action.Method);
IFilter<Update>[] filters = HandlerInspector.GetFilterAttributes(action.Method, handlerAttribute.Type).ToArray();
UpdateType = handlerAttribute.Type;
@@ -2,6 +2,7 @@
using Telegram.Bot.Types;
using Telegrator.Core.Filters;
using Telegrator.Core.Handlers;
using Telegrator.Core.States;
namespace Telegrator.Core.Descriptors
{
@@ -27,6 +28,11 @@ namespace Telegrator.Core.Descriptors
/// </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>
@@ -73,6 +79,7 @@ namespace Telegrator.Core.Descriptors
/// <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>
@@ -81,6 +88,7 @@ namespace Telegrator.Core.Descriptors
HandlerDescriptor fromDescriptor,
IUpdateRouter updateRouter,
IAwaitingProvider awaitingProvider,
IStateStorage stateStorage,
ITelegramBotClient client,
UpdateHandlerBase handlerInstance,
FilterExecutionContext<Update> filterContext,
@@ -89,6 +97,7 @@ namespace Telegrator.Core.Descriptors
From = fromDescriptor;
UpdateRouter = updateRouter;
AwaitingProvider = awaitingProvider;
StateStorage = stateStorage;
Client = client;
HandlerInstance = handlerInstance;
ExtraData = filterContext.Data;
@@ -114,7 +123,7 @@ namespace Telegrator.Core.Descriptors
/// <param name="result">The execution result.</param>
public void ReportResult(Result? result)
{
if (result != null)
if (Result != null)
throw new InvalidOperationException("Result already reported");
Result = result;
@@ -167,7 +167,7 @@ namespace Telegrator.Core.Descriptors
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>? stateKeeperAttribute = HandlerInspector.GetStateKeeperAttribute(handlerType);
IFilter<Update>[] filters = HandlerInspector.GetFilterAttributes(handlerType, handlerAttribute.Type).ToArray();
UpdateType = handlerAttribute.Type;
@@ -2,6 +2,7 @@
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;
@@ -42,13 +43,13 @@ namespace Telegrator.Core.Descriptors
/// </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)
public static IFilter<Update>? GetStateKeeperAttribute(MemberInfo handlerType)
{
// Getting polling handler attribute
IEnumerable<StateKeeperAttributeBase> handlerAttrs = handlerType.GetCustomAttributes<StateKeeperAttributeBase>();
Attribute stateAttr = handlerType.GetCustomAttribute(typeof(StateAttribute<,>));
//
return handlerAttrs.Any() ? handlerAttrs.Single() : null;
return stateAttr as IFilter<Update>;
}
/// <summary>
@@ -9,6 +9,11 @@ namespace Telegrator.Core.Filters
/// <typeparam name="T">The type of the input for the filter.</typeparam>
public class FilterExecutionContext<T> where T : class
{
/// <summary>
/// Gets the <see cref="ITelegramBotInfo"/> for the current context.
/// </summary>
public IUpdateRouter UpdateRouter { get; }
/// <summary>
/// Gets the <see cref="ITelegramBotInfo"/> for the current context.
/// </summary>
@@ -42,13 +47,15 @@ namespace Telegrator.Core.Filters
/// <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(ITelegramBotInfo botInfo, Update update, T input, Dictionary<string, object> data, CompletedFiltersList completedFilters)
public FilterExecutionContext(IUpdateRouter router, ITelegramBotInfo botInfo, Update update, T input, Dictionary<string, object> data, CompletedFiltersList completedFilters)
{
UpdateRouter = router;
BotInfo = botInfo;
Data = data;
CompletedFilters = completedFilters;
@@ -60,11 +67,12 @@ namespace Telegrator.Core.Filters
/// <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(ITelegramBotInfo botInfo, Update update, T input)
: this(botInfo, update, input, [], []) { }
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.
@@ -73,6 +81,6 @@ namespace Telegrator.Core.Filters
/// <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);
=> new FilterExecutionContext<C>(UpdateRouter, BotInfo, Update, input, Data, CompletedFilters);
}
}
@@ -3,6 +3,7 @@ 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
@@ -47,6 +48,11 @@ namespace Telegrator.Core.Handlers
/// </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>
@@ -1,9 +1,9 @@
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
{
@@ -140,35 +140,15 @@ namespace Telegrator.Core.Handlers.Building
/// <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>
/// <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 SetStateKeeper<TKey, TState, TKeeper>(TState myState, IStateKeyResolver<TKey> keyResolver)
where TKey : notnull
where TState : IEquatable<TState>
where TKeeper : StateKeeperBase<TKey, TState>, new()
public void SetState<TKey, TValue>(TValue? state)
where TKey : IStateKeyResolver, new()
where TValue : IEquatable<TValue>
{
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);
StateKeeper = new StateKeyFilter<TKey, TValue>(state);
}
/// <summary>
@@ -1,4 +1,4 @@
using Telegrator.Core.StateKeeping;
using Telegrator.Core.States;
namespace Telegrator.Core.Handlers.Building
{
@@ -11,9 +11,9 @@ namespace Telegrator.Core.Handlers.Building
/// <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="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<long> keyResolver, CancellationToken cancellationToken = default);
public Task<TUpdate> Await(IStateKeyResolver keyResolver, CancellationToken cancellationToken = default);
}
}
@@ -1,7 +1,6 @@
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
{
@@ -56,30 +55,13 @@ namespace Telegrator.Core.Handlers.Building
/// <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>
/// <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 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 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();
public void SetState<TKey, TValue>(TValue? state)
where TKey : IStateKeyResolver, new()
where TValue : IEquatable<TValue>;
/// <summary>
/// Adds a targeted filter for a specific filter target type.
@@ -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,6 +1,7 @@
using Telegram.Bot;
using Telegram.Bot.Types;
using Telegrator.Core.Filters;
using Telegrator.Core.States;
namespace Telegrator.Core.Handlers
{
@@ -23,5 +24,8 @@ namespace Telegrator.Core.Handlers
/// <inheritdoc/>
public IAwaitingProvider AwaitingProvider => throw new NotImplementedException();
/// <inheritdoc/>
public IStateStorage StateStorage => throw new NotImplementedException();
}
}
@@ -1,6 +1,7 @@
using Telegram.Bot;
using Telegram.Bot.Types;
using Telegrator.Core.Filters;
using Telegrator.Core.States;
namespace Telegrator.Core.Handlers
{
@@ -34,5 +35,10 @@ namespace Telegrator.Core.Handlers
/// 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; }
}
}
@@ -125,7 +125,7 @@ namespace Telegrator.Core.Handlers
}
}
internal IHandlerContainer GetContainer(DescribedHandlerDescriptor handlerInfo)
private IHandlerContainer GetContainer(DescribedHandlerDescriptor handlerInfo)
{
if (this is IHandlerContainerFactory handlerDefainedContainerFactory)
return handlerDefainedContainerFactory.CreateContainer(handlerInfo);
-19
View File
@@ -1,19 +0,0 @@
namespace Telegrator.Core
{
/// <summary>
/// Interface for polling providers that manage both regular and awaiting handlers.
/// Provides access to handlers for different types of update processing during polling operations.
/// </summary>
public interface IPollingProvider
{
/// <summary>
/// Gets the <see cref="IHandlersProvider"/> that manages handlers for polling.
/// </summary>
public IHandlersProvider HandlersProvider { get; }
/// <summary>
/// Gets the <see cref="IAwaitingProvider"/> that manages awaiting handlers for polling.
/// </summary>
public IAwaitingProvider AwaitingProvider { get; }
}
}
+17 -1
View File
@@ -1,5 +1,6 @@
using Telegram.Bot.Polling;
using Telegrator.Core.Handlers;
using Telegrator.Core.States;
namespace Telegrator.Core
{
@@ -7,7 +8,7 @@ namespace Telegrator.Core
/// Interface for update routers that handle incoming updates and manage handler execution.
/// Combines update handling capabilities with polling provider functionality and exception handling.
/// </summary>
public interface IUpdateRouter : IUpdateHandler, IPollingProvider
public interface IUpdateRouter : IUpdateHandler
{
/// <summary>
/// Gets the <see cref="TelegratorOptions"/> for the router.
@@ -19,6 +20,21 @@ namespace Telegrator.Core
/// </summary>
public IUpdateHandlersPool HandlersPool { get; }
/// <summary>
/// Gets the <see cref="IHandlersProvider"/> that manages handlers for polling.
/// </summary>
public IHandlersProvider HandlersProvider { get; }
/// <summary>
/// Gets the <see cref="IAwaitingProvider"/> that manages awaiting handlers for polling.
/// </summary>
public IAwaitingProvider AwaitingProvider { get; }
/// <summary>
/// Gets the <see cref="IStateStorage"/> that manages storing of handlers state.
/// </summary>
public IStateStorage StateStorage { get; }
/// <summary>
/// Gets or sets the <see cref="IRouterExceptionHandler"/> for handling exceptions.
/// </summary>
@@ -1,18 +0,0 @@
using Telegram.Bot.Types;
namespace Telegrator.Core.StateKeeping
{
/// <summary>
/// Defines a resolver for extracting a key from an update for state keeping purposes.
/// </summary>
/// <typeparam name="TKey">The type of the key.</typeparam>
public interface IStateKeyResolver<TKey> where TKey : notnull
{
/// <summary>
/// Resolves a key from the specified <see cref="Update"/>.
/// </summary>
/// <param name="keySource">The update to resolve the key from.</param>
/// <returns>The resolved key.</returns>
public TKey ResolveKey(Update keySource);
}
}
@@ -1,150 +0,0 @@
using Telegram.Bot.Types;
namespace Telegrator.Core.StateKeeping
{
/// <summary>
/// Base class for managing state associated with updates and keys.
/// </summary>
/// <typeparam name="TKey">The type of the key used for state resolution.</typeparam>
/// <typeparam name="TState">The type of the state.</typeparam>
public abstract class StateKeeperBase<TKey, TState> where TState : notnull where TKey : notnull
{
private readonly Dictionary<TKey, TState> States = [];
/// <summary>
/// Gets or sets the key resolver used to resolve keys from updates.
/// </summary>
public IStateKeyResolver<TKey> KeyResolver { get; set; } = null!;
/// <summary>
/// Gets the default state value.
/// </summary>
public abstract TState DefaultState { get; }
/// <summary>
/// Sets the state for the specified update.
/// </summary>
/// <param name="keySource">The update to use as a key source.</param>
/// <param name="newState">The new state value.</param>
public virtual void SetState(Update keySource, TState newState)
{
TKey key = KeyResolver.ResolveKey(keySource);
States.Set(key, newState, DefaultState);
}
/// <summary>
/// Gets the state for the specified update.
/// </summary>
/// <param name="keySource">The update to use as a key source.</param>
/// <returns>The state value.</returns>
public virtual TState GetState(Update keySource)
{
TKey key = KeyResolver.ResolveKey(keySource);
return States[key];
}
/// <summary>
/// Tries to get the state for the specified update.
/// </summary>
/// <param name="keySource">The update to use as a key source.</param>
/// <param name="state">When this method returns, contains the state value if found; otherwise, the default value.</param>
/// <returns>True if the state was found; otherwise, false.</returns>
public virtual bool TryGetState(Update keySource, out TState? state)
{
TKey key = KeyResolver.ResolveKey(keySource);
return States.TryGetValue(key, out state);
}
/// <summary>
/// Determines whether a state exists for the specified update.
/// </summary>
/// <param name="keySource">The update to use as a key source.</param>
/// <returns>True if the state exists; otherwise, false.</returns>
public virtual bool HasState(Update keySource)
{
TKey key = KeyResolver.ResolveKey(keySource);
return States.ContainsKey(key);
}
/// <summary>
/// Creates a state for the specified update using the default state value.
/// </summary>
/// <param name="keySource">The update to use as a key source.</param>
public virtual void CreateState(Update keySource)
{
TKey key = KeyResolver.ResolveKey(keySource);
States.Set(key, DefaultState);
}
/// <summary>
/// Deletes the state for the specified update.
/// </summary>
/// <param name="keySource">The update to use as a key source.</param>
public virtual void DeleteState(Update keySource)
{
TKey key = KeyResolver.ResolveKey(keySource);
States.Remove(key);
}
/// <summary>
/// Moves the state forward for the specified update.
/// </summary>
/// <param name="keySource">The update to use as a key source.</param>
public virtual void MoveForward(Update keySource)
{
TKey key = KeyResolver.ResolveKey(keySource);
if (!States.TryGetValue(key, out TState currentState))
{
States.Set(key, DefaultState);
currentState = DefaultState;
}
TState newState = MoveForward(currentState, key);
States[key] = newState;
}
/// <summary>
/// Moves the state backward for the specified update.
/// </summary>
/// <param name="keySource">The update to use as a key source.</param>
public virtual void MoveBackward(Update keySource)
{
TKey key = KeyResolver.ResolveKey(keySource);
if (!States.TryGetValue(key, out TState currentState))
{
States.Set(key, DefaultState);
return;
}
TState newState = MoveBackward(currentState, key);
States[key] = newState;
}
/*
/// <summary>
/// Gets the state keeper for the specified key.
/// </summary>
/// <typeparam name="TStateKeeper">The type of the state keeper.</typeparam>
/// <param name="key">The key.</param>
/// <returns>The state keeper instance.</returns>
protected virtual TStateKeeper GetKeeper<TStateKeeper>(TKey key) where TStateKeeper : StateKeeperBase<TKey, TState>
=> States[key] as TStateKeeper ?? throw new InvalidCastException();
*/
/// <summary>
/// Moves the state forward for the specified current state and key.
/// </summary>
/// <param name="currentState">The current state value.</param>
/// <param name="currentKey">The key.</param>
/// <returns>The new state value.</returns>
protected abstract TState MoveForward(TState currentState, TKey currentKey);
/// <summary>
/// Moves the state backward for the specified current state and key.
/// </summary>
/// <param name="currentState">The current state value.</param>
/// <param name="currentKey">The key.</param>
/// <returns>The new state value.</returns>
protected abstract TState MoveBackward(TState currentState, TKey currentKey);
}
}
@@ -0,0 +1,16 @@
using Telegram.Bot.Types;
namespace Telegrator.Core.States;
/// <summary>
/// Defines a resolver for extracting a key from an update for state keeping purposes.
/// </summary>
public interface IStateKeyResolver
{
/// <summary>
/// Resolves a key from the specified <see cref="Update"/>.
/// </summary>
/// <param name="keySource">The update to resolve the key from.</param>
/// <returns>The resolved key.</returns>
public string? ResolveKey(Update keySource);
}
@@ -0,0 +1,44 @@
namespace Telegrator.Core.States;
/// <summary>
/// Defines a contract for a state machine that manages transitions and retrieves states for specific updates.
/// </summary>
/// <typeparam name="TState">The type of the state. Must implement <see cref="IEquatable{T}"/>.</typeparam>
public interface IStateMachine<TState> where TState : IEquatable<TState>
{
/// <summary>
/// Gets the current state associated with the specified update key.
/// </summary>
/// <param name="storage">The storage mechanism used to persist the state.</param>
/// <param name="updateKey">The unique key identifying the current update context (e.g., chat and user ID).</param>
/// <param name="cancellationToken">A cancellation token that can be used by other objects or threads to receive notice of cancellation.</param>
/// <returns>A task that represents the asynchronous operation. The task result contains the current state, or the default value if no state is found.</returns>
Task<TState?> Current(IStateStorage storage, string updateKey, CancellationToken cancellationToken = default);
/// <summary>
/// Advances the state machine to the next state in the sequence.
/// </summary>
/// <param name="storage">The storage mechanism used to persist the state.</param>
/// <param name="updateKey">The unique key identifying the current update context.</param>
/// <param name="cancellationToken">A cancellation token that can be used by other objects or threads to receive notice of cancellation.</param>
/// <returns>A task that represents the asynchronous transition operation.</returns>
Task Advance(IStateStorage storage, string updateKey, CancellationToken cancellationToken = default);
/// <summary>
/// Moves the state machine backward to the previous state in the sequence.
/// </summary>
/// <param name="storage">The storage mechanism used to persist the state.</param>
/// <param name="updateKey">The unique key identifying the current update context.</param>
/// <param name="cancellationToken">A cancellation token that can be used by other objects or threads to receive notice of cancellation.</param>
/// <returns>A task that represents the asynchronous transition operation.</returns>
Task Retreat(IStateStorage storage, string updateKey, CancellationToken cancellationToken = default);
/// <summary>
/// Resets the state machine to its initial or default state.
/// </summary>
/// <param name="storage">The storage mechanism used to persist the state.</param>
/// <param name="updateKey">The unique key identifying the current update context.</param>
/// <param name="cancellationToken">A cancellation token that can be used by other objects or threads to receive notice of cancellation.</param>
/// <returns>A task that represents the asynchronous reset operation.</returns>
Task Reset(IStateStorage storage, string updateKey, CancellationToken cancellationToken = default);
}
@@ -0,0 +1,34 @@
namespace Telegrator.Core.States;
/// <summary>
/// Defines a contract for an asynchronous state storage mechanism.
/// </summary>
public interface IStateStorage
{
/// <summary>
/// Saves or updates a state value associated with the specified key.
/// </summary>
/// <typeparam name="T">The type of the state object.</typeparam>
/// <param name="key">The unique identifier for the state.</param>
/// <param name="state">The state object to store.</param>
/// <param name="cancellationToken">A cancellation token that can be used by other objects or threads to receive notice of cancellation.</param>
/// <returns>A task that represents the asynchronous save operation.</returns>
Task SetAsync<T>(string key, T state, CancellationToken cancellationToken = default);
/// <summary>
/// Retrieves a state value associated with the specified key.
/// </summary>
/// <typeparam name="T">The type of the state object to retrieve.</typeparam>
/// <param name="key">The unique identifier for the state.</param>
/// <param name="cancellationToken">A cancellation token that can be used by other objects or threads to receive notice of cancellation.</param>
/// <returns>A task that represents the asynchronous retrieve operation. The task result contains the state object if found; otherwise, the default value of <typeparamref name="T"/>.</returns>
Task<T?> GetAsync<T>(string key, CancellationToken cancellationToken = default);
/// <summary>
/// Deletes the state value associated with the specified key.
/// </summary>
/// <param name="key">The unique identifier for the state to remove.</param>
/// <param name="cancellationToken">A cancellation token that can be used by other objects or threads to receive notice of cancellation.</param>
/// <returns>A task that represents the asynchronous delete operation.</returns>
Task DeleteAsync(string key, CancellationToken cancellationToken = default);
}
+23 -10
View File
@@ -1,31 +1,44 @@
using Telegram.Bot.Types;
using Telegrator.Core.Filters;
using Telegrator.Core.StateKeeping;
using Telegrator.Core.States;
namespace Telegrator.Filters
{
/// <summary>
/// Filters updates by comparing a resolved state key with a target key.
/// </summary>
/// <typeparam name="TKey">The type of the key used for state resolution.</typeparam>
public class StateKeyFilter<TKey> : Filter<Update> where TKey : IEquatable<TKey>
/// <typeparam name="TKey">The type of the key resolver used to get state key.</typeparam>
/// <typeparam name="TValue">The type of the key used for state resolution.</typeparam>
public class StateKeyFilter<TKey, TValue> : Filter<Update>
where TKey : IStateKeyResolver, new()
where TValue : IEquatable<TValue>
{
private readonly IStateKeyResolver<TKey> KeyResolver;
private readonly TKey TargetKey;
private readonly TValue? TargetKey;
/// <summary>
/// Initializes a new instance of the <see cref="StateKeyFilter{TKey}"/> class.
/// Initializes a new instance of the <see cref="StateKeyFilter{TKey, TValue}"/> class.
/// </summary>
/// <param name="keyResolver">The key resolver to extract the key from the update.</param>
/// <param name="targetKey">The target key to compare with.</param>
public StateKeyFilter(IStateKeyResolver<TKey> keyResolver, TKey targetKey)
public StateKeyFilter(TValue? targetKey)
{
KeyResolver = keyResolver;
TargetKey = targetKey;
}
/// <inheritdoc/>
public override bool CanPass(FilterExecutionContext<Update> context)
=> KeyResolver.ResolveKey(context.Input).Equals(TargetKey);
{
string? key = new TKey().ResolveKey(context.Input);
if (key is null)
return TargetKey is null;
TValue? value = context.UpdateRouter.StateStorage.GetAsync<TValue>(key).Result;
if (value is null)
return TargetKey is null;
if (TargetKey is null)
return false;
return TargetKey.Equals(value);
}
}
}
@@ -1,11 +1,11 @@
using Telegram.Bot.Types;
using Telegram.Bot.Types.Enums;
using Telegrator.Filters;
using Telegrator.StateKeeping;
using Telegrator.Core;
using Telegrator.Core.Handlers.Building;
using Telegrator.Core.Descriptors;
using Telegrator.Core.StateKeeping;
using Telegrator.Core.Handlers.Building;
using Telegrator.Core.States;
using Telegrator.Filters;
using Telegrator.States;
namespace Telegrator.Handlers.Building
{
@@ -56,9 +56,21 @@ namespace Telegrator.Handlers.Building
/// <param name="keyResolver">The state key resolver to use for filtering updates.</param>
/// <param name="cancellationToken">The cancellation token to cancel the wait operation.</param>
/// <returns>The awaited update of type TUpdate.</returns>
public async Task<TUpdate> Await(IStateKeyResolver<long> keyResolver, CancellationToken cancellationToken = default)
public async Task<TUpdate> Await(IStateKeyResolver keyResolver, CancellationToken cancellationToken = default)
{
Filters.Add(new StateKeyFilter<long>(keyResolver, keyResolver.ResolveKey(HandlingUpdate)));
string? handlingKey = keyResolver.ResolveKey(HandlingUpdate);
if (handlingKey is null)
throw new InvalidOperationException("Cannot await update with resolved key as NULL");
Filters.Add(Filter<Update>.If(ctx =>
{
string? key = keyResolver.ResolveKey(ctx.Update);
if (key is null)
return false;
return key == handlingKey;
}));
AwaiterHandler handlerInstance = new AwaiterHandler(UpdateType);
HandlerDescriptor descriptor = BuildImplicitDescriptor(handlerInstance);
@@ -1,9 +1,7 @@
using Telegram.Bot.Types;
using Telegrator.Annotations.StateKeeping;
using Telegrator.Core.Filters;
using Telegrator.Core.Handlers.Building;
using Telegrator.Core.StateKeeping;
using Telegrator.StateKeeping;
using Telegrator.Core.States;
namespace Telegrator.Handlers.Building
{
@@ -61,25 +59,13 @@ namespace Telegrator.Handlers.Building
return handlerBuilder;
}
/// <inheritdoc cref="HandlerBuilderBase.SetStateKeeper{TKey, TState, TKeeper}(TState, IStateKeyResolver{TKey})"/>
public static TBuilder SetStateKeeper<TBuilder, TKey, TState, TKeeper>(this TBuilder handlerBuilder, TState myState, IStateKeyResolver<TKey> keyResolver)
/// <inheritdoc cref="HandlerBuilderBase.SetState{TKey, TValue}(TValue?)"/>
public static TBuilder SetState<TBuilder, TKey, TValue>(this TBuilder handlerBuilder, TValue? myState)
where TBuilder : HandlerBuilderBase
where TKey : notnull
where TState : IEquatable<TState>
where TKeeper : StateKeeperBase<TKey, TState>, new()
where TKey : IStateKeyResolver, new()
where TValue : IEquatable<TValue>
{
handlerBuilder.SetStateKeeper<TKey, TState, TKeeper>(myState, keyResolver);
return handlerBuilder;
}
/// <inheritdoc cref="HandlerBuilderBase.SetStateKeeper{TKey, TState, TKeeper}(SpecialState, IStateKeyResolver{TKey})"/>
public static TBuilder SetStateKeeper<TBuilder, TKey, TState, TKeeper>(this TBuilder handlerBuilder, SpecialState specialState, IStateKeyResolver<TKey> keyResolver)
where TBuilder : HandlerBuilderBase
where TKey : notnull
where TState : IEquatable<TState>
where TKeeper : StateKeeperBase<TKey, TState>, new()
{
handlerBuilder.SetStateKeeper<TKey, TState, TKeeper>(specialState, keyResolver);
handlerBuilder.SetState<TKey, TValue>(myState);
return handlerBuilder;
}
@@ -116,129 +102,5 @@ namespace Telegrator.Handlers.Building
handlerBuilder.AddTargetedFilters(getFilterringTarget, filters);
return handlerBuilder;
}
/// <summary>
/// Sets a numeric state keeper with a custom key resolver.
/// </summary>
/// <typeparam name="TBuilder">The type of the handler builder.</typeparam>
/// <param name="handlerBuilder">The handler builder.</param>
/// <param name="myState">The numeric state value.</param>
/// <param name="keyResolver">The key resolver for the state.</param>
/// <returns>The handler builder for method chaining.</returns>
public static TBuilder SetNumericState<TBuilder>(this TBuilder handlerBuilder, int myState, IStateKeyResolver<long> keyResolver)
where TBuilder : HandlerBuilderBase
{
handlerBuilder.SetStateKeeper<long, int, NumericStateKeeper>(myState, keyResolver);
return handlerBuilder;
}
/// <summary>
/// Sets a numeric state keeper with a special state and custom key resolver.
/// </summary>
/// <typeparam name="TBuilder">The type of the handler builder.</typeparam>
/// <param name="handlerBuilder">The handler builder.</param>
/// <param name="specialState">The special state value.</param>
/// <param name="keyResolver">The key resolver for the state.</param>
/// <returns>The handler builder for method chaining.</returns>
public static TBuilder SetNumericState<TBuilder>(this TBuilder handlerBuilder, SpecialState specialState, IStateKeyResolver<long> keyResolver)
where TBuilder : HandlerBuilderBase
{
handlerBuilder.SetStateKeeper<long, int, NumericStateKeeper>(specialState, keyResolver);
return handlerBuilder;
}
/// <summary>
/// Sets a numeric state keeper with the default sender ID resolver.
/// </summary>
/// <typeparam name="TBuilder">The type of the handler builder.</typeparam>
/// <param name="handlerBuilder">The handler builder.</param>
/// <param name="myState">The numeric state value.</param>
/// <returns>The handler builder for method chaining.</returns>
public static TBuilder SetNumericState<TBuilder>(this TBuilder handlerBuilder, int myState)
where TBuilder : HandlerBuilderBase
{
handlerBuilder.SetStateKeeper<long, int, NumericStateKeeper>(myState, new SenderIdResolver());
return handlerBuilder;
}
/// <summary>
/// Sets a numeric state keeper with a special state and the default sender ID resolver.
/// </summary>
/// <typeparam name="TBuilder">The type of the handler builder.</typeparam>
/// <param name="handlerBuilder">The handler builder.</param>
/// <param name="specialState">The special state value.</param>
/// <returns>The handler builder for method chaining.</returns>
public static TBuilder SetNumericState<TBuilder>(this TBuilder handlerBuilder, SpecialState specialState)
where TBuilder : HandlerBuilderBase
{
handlerBuilder.SetStateKeeper<long, int, NumericStateKeeper>(specialState, new SenderIdResolver());
return handlerBuilder;
}
/// <summary>
/// Sets an enum state keeper with a custom key resolver.
/// </summary>
/// <typeparam name="TBuilder">The type of the handler builder.</typeparam>
/// <typeparam name="TEnum">The type of the enum state.</typeparam>
/// <param name="handlerBuilder">The handler builder.</param>
/// <param name="myState">The enum state value.</param>
/// <param name="keyResolver">The key resolver for the state.</param>
/// <returns>The handler builder for method chaining.</returns>
public static TBuilder SetEnumState<TBuilder, TEnum>(this TBuilder handlerBuilder, TEnum myState, IStateKeyResolver<long> keyResolver)
where TBuilder : HandlerBuilderBase
where TEnum : Enum, IEquatable<TEnum>
{
handlerBuilder.SetStateKeeper<long, TEnum, EnumStateKeeper<TEnum>>(myState, keyResolver);
return handlerBuilder;
}
/// <summary>
/// Sets an enum state keeper with a special state and custom key resolver.
/// </summary>
/// <typeparam name="TBuilder">The type of the handler builder.</typeparam>
/// <typeparam name="TEnum">The type of the enum state.</typeparam>
/// <param name="handlerBuilder">The handler builder.</param>
/// <param name="specialState">The special state value.</param>
/// <param name="keyResolver">The key resolver for the state.</param>
/// <returns>The handler builder for method chaining.</returns>
public static TBuilder SetEnumState<TBuilder, TEnum>(this TBuilder handlerBuilder, SpecialState specialState, IStateKeyResolver<long> keyResolver)
where TBuilder : HandlerBuilderBase
where TEnum : Enum, IEquatable<TEnum>
{
handlerBuilder.SetStateKeeper<long, TEnum, EnumStateKeeper<TEnum>>(specialState, keyResolver);
return handlerBuilder;
}
/// <summary>
/// Sets an enum state keeper with the default sender ID resolver.
/// </summary>
/// <typeparam name="TBuilder">The type of the handler builder.</typeparam>
/// <typeparam name="TEnum">The type of the enum state.</typeparam>
/// <param name="handlerBuilder">The handler builder.</param>
/// <param name="myState">The enum state value.</param>
/// <returns>The handler builder for method chaining.</returns>
public static TBuilder SetEnumState<TBuilder, TEnum>(this TBuilder handlerBuilder, TEnum myState)
where TBuilder : HandlerBuilderBase
where TEnum : Enum, IEquatable<TEnum>
{
handlerBuilder.SetStateKeeper<long, TEnum, EnumStateKeeper<TEnum>>(myState, new SenderIdResolver());
return handlerBuilder;
}
/// <summary>
/// Sets an enum state keeper with a special state and the default sender ID resolver.
/// </summary>
/// <typeparam name="TBuilder">The type of the handler builder.</typeparam>
/// <typeparam name="TEnum">The type of the enum state.</typeparam>
/// <param name="handlerBuilder">The handler builder.</param>
/// <param name="specialState">The special state value.</param>
/// <returns>The handler builder for method chaining.</returns>
public static TBuilder SetEnumState<TBuilder, TEnum>(this TBuilder handlerBuilder, SpecialState specialState)
where TBuilder : HandlerBuilderBase
where TEnum : Enum, IEquatable<TEnum>
{
handlerBuilder.SetStateKeeper<long, TEnum, EnumStateKeeper<TEnum>>(specialState, new SenderIdResolver());
return handlerBuilder;
}
}
}
@@ -43,6 +43,9 @@ namespace Telegrator.Handlers
{
string[] split = ReceivedCommand.Split('@');
ReceivedCommand = split[0];
if (!split.ElementAtOrDefault(1).Equals(context.BotInfo.User.Username))
return false;
}
return true;
+12 -5
View File
@@ -3,6 +3,7 @@ using Telegram.Bot.Types;
using Telegrator.Core;
using Telegrator.Core.Descriptors;
using Telegrator.Core.Filters;
using Telegrator.Core.States;
namespace Telegrator.Handlers
{
@@ -33,6 +34,9 @@ namespace Telegrator.Handlers
/// <inheritdoc/>
public IAwaitingProvider AwaitingProvider { get; }
/// <inheritdoc/>
public IStateStorage StateStorage { get; }
/// <summary>
/// Initializes new instance of <see cref="HandlerContainer{TUpdate}"/>
/// </summary>
@@ -45,6 +49,7 @@ namespace Telegrator.Handlers
ExtraData = handlerInfo.ExtraData;
CompletedFilters = handlerInfo.CompletedFilters;
AwaitingProvider = handlerInfo.AwaitingProvider;
StateStorage = handlerInfo.StateStorage;
}
/// <summary>
@@ -56,7 +61,8 @@ namespace Telegrator.Handlers
/// <param name="extraData"></param>
/// <param name="filters"></param>
/// <param name="awaitingProvider"></param>
public HandlerContainer(TUpdate actualUpdate, Update handlingUpdate, ITelegramBotClient client, Dictionary<string, object> extraData, CompletedFiltersList filters, IAwaitingProvider awaitingProvider)
/// <param name="stateStorage"></param>
public HandlerContainer(TUpdate actualUpdate, Update handlingUpdate, ITelegramBotClient client, Dictionary<string, object> extraData, CompletedFiltersList filters, IAwaitingProvider awaitingProvider, IStateStorage stateStorage)
{
ActualUpdate = actualUpdate;
HandlingUpdate = handlingUpdate;
@@ -64,6 +70,7 @@ namespace Telegrator.Handlers
ExtraData = extraData;
CompletedFilters = filters;
AwaitingProvider = awaitingProvider;
StateStorage = stateStorage;
}
/// <summary>
@@ -75,8 +82,8 @@ namespace Telegrator.Handlers
{
return new HandlerContainer<QUpdate>(
HandlingUpdate.GetActualUpdateObject<QUpdate>(),
HandlingUpdate, Client, ExtraData,
CompletedFilters, AwaitingProvider);
HandlingUpdate, Client, ExtraData, CompletedFilters,
AwaitingProvider, StateStorage);
}
/// <summary>
@@ -89,8 +96,8 @@ namespace Telegrator.Handlers
{
return new HandlerContainer<TUpdate>(
other.HandlingUpdate.GetActualUpdateObject<TUpdate>(),
other.HandlingUpdate, other.Client, other.ExtraData,
other.CompletedFilters, other.AwaitingProvider);
other.HandlingUpdate, other.Client, other.ExtraData, other.CompletedFilters,
other.AwaitingProvider, other.StateStorage);
}
}
}
@@ -28,7 +28,7 @@ namespace Telegrator.Mediation
{
_handler.Invoke(botClient, exception, source, cancellationToken);
}
finally
catch
{
_ = 0xBAD + 0xC0DE;
}
+10 -20
View File
@@ -7,6 +7,7 @@ using Telegrator.Core;
using Telegrator.Core.Descriptors;
using Telegrator.Core.Filters;
using Telegrator.Core.Handlers;
using Telegrator.Core.States;
using Telegrator.Handlers.Diagnostics;
using Telegrator.Logging;
@@ -21,6 +22,7 @@ namespace Telegrator.Mediation
private readonly TelegratorOptions _options;
private readonly IHandlersProvider _handlersProvider;
private readonly IAwaitingProvider _awaitingProvider;
private readonly IStateStorage _stateStorage;
private readonly IUpdateHandlersPool _HandlersPool;
private readonly ITelegramBotInfo _botInfo;
@@ -30,6 +32,9 @@ namespace Telegrator.Mediation
/// <inheritdoc/>
public IAwaitingProvider AwaitingProvider => _awaitingProvider;
/// <inheritdoc/>
public IStateStorage StateStorage => _stateStorage;
/// <inheritdoc/>
public TelegratorOptions Options => _options;
@@ -47,34 +52,19 @@ namespace Telegrator.Mediation
/// </summary>
/// <param name="handlersProvider">The provider for regular handlers.</param>
/// <param name="awaitingProvider">The provider for awaiting handlers.</param>
/// <param name="stateStorage">The state storage.</param>
/// <param name="options">The bot configuration options.</param>
/// <param name="botInfo"></param>
public UpdateRouter(IHandlersProvider handlersProvider, IAwaitingProvider awaitingProvider, TelegratorOptions options, ITelegramBotInfo botInfo)
public UpdateRouter(IHandlersProvider handlersProvider, IAwaitingProvider awaitingProvider, IStateStorage stateStorage, TelegratorOptions options, ITelegramBotInfo botInfo)
{
_options = options;
_handlersProvider = handlersProvider;
_awaitingProvider = awaitingProvider;
_stateStorage = stateStorage;
_HandlersPool = new UpdateHandlersPool(this, _options, _options.GlobalCancellationToken);
_botInfo = botInfo;
}
/// <summary>
/// Initializes a new instance of the <see cref="UpdateRouter"/> class with a custom handlers pool.
/// </summary>
/// <param name="handlersProvider">The provider for regular handlers.</param>
/// <param name="awaitingProvider">The provider for awaiting handlers.</param>
/// <param name="options">The bot configuration options.</param>
/// <param name="handlersPool">The custom handlers pool to use.</param>
/// <param name="botInfo"></param>
public UpdateRouter(IHandlersProvider handlersProvider, IAwaitingProvider awaitingProvider, TelegratorOptions options, IUpdateHandlersPool handlersPool, ITelegramBotInfo botInfo)
{
_options = options;
_handlersProvider = handlersProvider;
_awaitingProvider = awaitingProvider;
_HandlersPool = handlersPool;
_botInfo = botInfo;
}
/// <summary>
/// Handles errors that occur during update processing.
/// </summary>
@@ -252,7 +242,7 @@ namespace Telegrator.Mediation
};
UpdateHandlerBase handlerInstance = provider.GetHandlerInstance(descriptor, cancellationToken);
FilterExecutionContext<Update> filterContext = new FilterExecutionContext<Update>(_botInfo, update, update, data, []);
FilterExecutionContext<Update> filterContext = new FilterExecutionContext<Update>(this, _botInfo, update, update, data, []);
if (descriptor.Filters != null)
{
@@ -271,7 +261,7 @@ namespace Telegrator.Mediation
}
}
return new DescribedHandlerDescriptor(descriptor, this, AwaitingProvider, client, handlerInstance, filterContext, descriptor.DisplayString);
return new DescribedHandlerDescriptor(descriptor, this, AwaitingProvider, StateStorage, client, handlerInstance, filterContext, descriptor.DisplayString);
}
/// <summary>
@@ -1,59 +0,0 @@
using Telegrator.Core.StateKeeping;
namespace Telegrator.StateKeeping
{
/// <summary>
/// Abstract base class for state keepers that manage state transitions using an array of predefined states.
/// Provides forward and backward navigation through a fixed sequence of states.
/// </summary>
/// <typeparam name="TKey">The type of the key used to identify state contexts.</typeparam>
/// <typeparam name="TState">The type of the state values. Must be non-null.</typeparam>
/// <param name="states">The array of states that define the allowed state sequence.</param>
public abstract class ArrayStateKeeper<TKey, TState>(params TState[] states) : StateKeeperBase<TKey, TState> where TState : notnull where TKey : notnull
{
/// <summary>
/// The array of states that defines the allowed state sequence for navigation.
/// </summary>
protected readonly TState[] ArrayStates = states;
/// <summary>
/// Moves to the previous state in the array sequence.
/// </summary>
/// <param name="currentState">The current state to move backward from.</param>
/// <param name="_">The key parameter (unused in this implementation).</param>
/// <returns>The previous state in the array sequence.</returns>
/// <exception cref="ArgumentException">Thrown when the current state is not found in the array.</exception>
/// <exception cref="IndexOutOfRangeException">Thrown when trying to move backward from the first state.</exception>
protected override TState MoveBackward(TState currentState, TKey _)
{
int index = Array.IndexOf(ArrayStates, currentState);
if (index == -1)
throw new ArgumentException("Cannot resolve current state");
if (index == 0)
throw new IndexOutOfRangeException("This state cannot be moved backward");
return ArrayStates[index - 1];
}
/// <summary>
/// Moves to the next state in the array sequence.
/// </summary>
/// <param name="currentState">The current state to move forward from.</param>
/// <param name="_">The key parameter (unused in this implementation).</param>
/// <returns>The next state in the array sequence.</returns>
/// <exception cref="ArgumentException">Thrown when the current state is not found in the array.</exception>
/// <exception cref="IndexOutOfRangeException">Thrown when trying to move forward from the last state.</exception>
protected override TState MoveForward(TState currentState, TKey _)
{
int index = Array.IndexOf(ArrayStates, currentState);
if (index == -1)
throw new ArgumentException("Cannot resolve current state");
if (index == ArrayStates.Length - 1)
throw new IndexOutOfRangeException("This state cannot be moved forward");
return ArrayStates[index + 1];
}
}
}
@@ -1,75 +0,0 @@
using Telegrator.Annotations.StateKeeping;
using Telegrator.Core.Handlers;
namespace Telegrator.StateKeeping
{
/// <summary>
/// State keeper implementation for enum-based states.
/// Automatically creates an array of all enum values for state navigation.
/// </summary>
/// <typeparam name="TEnum">The enum type to be used for state management.</typeparam>
public class EnumStateKeeper<TEnum>() : ArrayStateKeeper<long, TEnum>(Enum.GetValues(typeof(TEnum)).Cast<TEnum>().ToArray()) where TEnum : Enum
{
/// <summary>
/// Gets the default state, which is the first value in the enum.
/// </summary>
public override TEnum DefaultState => ArrayStates.ElementAt(0);
}
/// <summary>
/// Extension methods for working with enum-based states in handler containers.
/// Provides convenient methods for state management operations.
/// </summary>
public static partial class StateHandlerContainerExtensions
{
/// <summary>
/// Gets the enum state keeper for the specified enum type.
/// </summary>
/// <typeparam name="TEnum">The enum type to get the state keeper for.</typeparam>
/// <param name="_">The handler container (unused parameter for extension method syntax).</param>
/// <returns>The enum state keeper instance.</returns>
public static EnumStateKeeper<TEnum> EnumStateKeeper<TEnum>(this IHandlerContainer _) where TEnum : Enum
=> EnumStateAttribute<TEnum>.Shared;
/// <summary>
/// Creates a new enum state for the current update.
/// </summary>
/// <typeparam name="TEnum">The enum type for state management.</typeparam>
/// <param name="container">The handler container.</param>
public static void CreateEnumState<TEnum>(this IHandlerContainer container) where TEnum : Enum
=> container.EnumStateKeeper<TEnum>().CreateState(container.HandlingUpdate);
/// <summary>
/// Deletes the enum state for the current update.
/// </summary>
/// <typeparam name="TEnum">The enum type for state management.</typeparam>
/// <param name="container">The handler container.</param>
public static void DeleteEnumState<TEnum>(this IHandlerContainer container) where TEnum : Enum
=> container.EnumStateKeeper<TEnum>().DeleteState(container.HandlingUpdate);
/// <summary>
/// Sets the enum state to a specific value for the current update.
/// </summary>
/// <typeparam name="TEnum">The enum type for state management.</typeparam>
/// <param name="container">The handler container.</param>
/// <param name="newState">The new state value. If null, uses the default state.</param>
public static void SetEnumState<TEnum>(this IHandlerContainer container, TEnum? newState) where TEnum : Enum
=> container.EnumStateKeeper<TEnum>().SetState(container.HandlingUpdate, newState ?? EnumStateAttribute<TEnum>.DefaultState);
/// <summary>
/// Moves the enum state forward to the next value in the enum sequence.
/// </summary>
/// <typeparam name="TEnum">The enum type for state management.</typeparam>
/// <param name="container">The handler container.</param>
public static void ForwardEnumState<TEnum>(this IHandlerContainer container) where TEnum : Enum
=> container.EnumStateKeeper<TEnum>().MoveForward(container.HandlingUpdate);
/// <summary>
/// Moves the enum state backward to the previous value in the enum sequence.
/// </summary>
/// <typeparam name="TEnum">The enum type for state management.</typeparam>
/// <param name="container">The handler container.</param>
public static void BackwardEnumState<TEnum>(this IHandlerContainer container) where TEnum : Enum
=> container.EnumStateKeeper<TEnum>().MoveBackward(container.HandlingUpdate);
}
}
@@ -1,92 +0,0 @@
using Telegrator.Annotations.StateKeeping;
using Telegrator.Core.Handlers;
using Telegrator.Core.StateKeeping;
namespace Telegrator.StateKeeping
{
/// <summary>
/// State keeper that manages numeric (integer) states for chat sessions.
/// Inherits from <see cref="StateKeeperBase{TKey, TState}"/> with long keys and int states.
/// Provides automatic increment/decrement functionality for state transitions.
/// </summary>
public class NumericStateKeeper : StateKeeperBase<long, int>
{
/// <summary>
/// Gets the default state value, which is 1.
/// </summary>
public override int DefaultState => 1;
/// <summary>
/// Moves the numeric state backward by decrementing the current state value.
/// </summary>
/// <param name="currentState">The current numeric state value</param>
/// <param name="_">The chat ID (unused in this implementation)</param>
/// <returns>The decremented state value</returns>
protected override int MoveBackward(int currentState, long _)
{
return currentState - 1;
}
/// <summary>
/// Moves the numeric state forward by incrementing the current state value.
/// </summary>
/// <param name="currentState">The current numeric state value</param>
/// <param name="_">The chat ID (unused in this implementation)</param>
/// <returns>The incremented state value</returns>
protected override int MoveForward(int currentState, long _)
{
return currentState + 1;
}
}
/// <summary>
/// Provides extension methods for managing numeric states in handler containers.
/// </summary>
public static partial class StateHandlerContainerExtensions
{
/// <summary>
/// Gets the numeric state keeper instance associated with the handler container.
/// </summary>
/// <param name="_">The handler container instance</param>
/// <returns>The <see cref="NumericStateKeeper"/> instance</returns>
public static NumericStateKeeper NumericStateKeeper(this IHandlerContainer _)
=> NumericStateAttribute.Shared;
/// <summary>
/// Creates a new numeric state for the current update being handled.
/// </summary>
/// <param name="container">The handler container instance</param>
public static void CreateNumericState(this IHandlerContainer container)
=> container.NumericStateKeeper().CreateState(container.HandlingUpdate);
/// <summary>
/// Deletes the numeric state for the current update being handled.
/// </summary>
/// <param name="container">The handler container instance</param>
public static void DeleteNumericState(this IHandlerContainer container)
=> container.NumericStateKeeper().DeleteState(container.HandlingUpdate);
/// <summary>
/// Sets the numeric state for the current update being handled.
/// If the new state is null, uses the default state from the state keeper.
/// </summary>
/// <param name="container">The handler container instance</param>
/// <param name="newState">The new numeric state to set, or null to use default</param>
public static void SetNumericState(this IHandlerContainer container, int? newState)
=> container.NumericStateKeeper().SetState(container.HandlingUpdate, newState ?? NumericStateAttribute.DefaultState);
/// <summary>
/// Moves the numeric state forward by incrementing the current value.
/// </summary>
/// <param name="container">The handler container instance</param>
public static void ForwardNumericState(this IHandlerContainer container)
=> container.NumericStateKeeper().MoveForward(container.HandlingUpdate);
/// <summary>
/// Moves the numeric state backward by decrementing the current value.
/// </summary>
/// <param name="container">The handler container instance</param>
public static void BackwardNumericState(this IHandlerContainer container)
=> container.NumericStateKeeper().MoveBackward(container.HandlingUpdate);
}
}
@@ -1,21 +0,0 @@
using Telegram.Bot.Types;
using Telegrator.Core.StateKeeping;
namespace Telegrator.StateKeeping
{
/// <summary>
/// Resolves sender ID from Telegram updates for state management purposes.
/// Extracts the sender identifier from various types of updates to provide a consistent key for state operations.
/// </summary>
public class SenderIdResolver : IStateKeyResolver<long>
{
/// <summary>
/// Resolves the sender ID from a Telegram update.
/// </summary>
/// <param name="keySource">The Telegram update to extract the sender ID from.</param>
/// <returns>The sender ID as a long value.</returns>
/// <exception cref="ArgumentException">Thrown when the update does not contain a valid sender ID.</exception>
public long ResolveKey(Update keySource)
=> keySource.GetSenderId() ?? throw new ArgumentException("Cannot resolve SenderID for this Update");
}
}
@@ -1,87 +0,0 @@
using Telegrator.Annotations.StateKeeping;
using Telegrator.Core.Handlers;
using Telegrator.Core.StateKeeping;
namespace Telegrator.StateKeeping
{
/// <summary>
/// State keeper that manages string-based states for chat sessions.
/// </summary>
public class StringStateKeeper() : StateKeeperBase<long, string>()
{
/// <summary>
/// Gets the default state value, which is an empty string.
/// </summary>
public override string DefaultState => string.Empty;
/// <inheritdoc/>
protected override string MoveBackward(string currentState, long currentKey)
{
throw new NotImplementedException();
}
/// <inheritdoc/>
protected override string MoveForward(string currentState, long currentKey)
{
throw new NotImplementedException();
}
}
/// <summary>
/// Provides extension methods for managing string states in handler containers.
/// </summary>
public static partial class StateHandlerContainerExtensions
{
/// <summary>
/// Gets the string state keeper instance associated with the handler container.
/// </summary>
/// <param name="_">The handler container instance</param>
/// <returns>The <see cref="StringStateKeeper"/> instance</returns>
public static StringStateKeeper StringStateKeeper(this IHandlerContainer _)
=> StringStateAttribute.Shared;
/// <summary>
/// Creates a new string state for the current update being handled.
/// </summary>
/// <param name="container">The handler container instance</param>
public static void CreateStringState(this IHandlerContainer container)
=> container.StringStateKeeper().CreateState(container.HandlingUpdate);
/// <summary>
/// Deletes the string state for the current update being handled.
/// </summary>
/// <param name="container">The handler container instance</param>
public static void DeleteStringState(this IHandlerContainer container)
=> container.StringStateKeeper().DeleteState(container.HandlingUpdate);
/// <summary>
/// Sets the string state for the current update being handled.
/// If the new state is null, uses the default state from the state keeper.
/// </summary>
/// <param name="container">The handler container instance</param>
/// <param name="newState">The new string state to set, or null to use default</param>
public static void SetStringState(this IHandlerContainer container, string? newState)
=> container.StringStateKeeper().SetState(container.HandlingUpdate, newState ?? StringStateAttribute.DefaultState);
/*
public static string GetStringState(this IHandlerContainer container, string key)
=> container.StringStateKeeper().GetState()
*/
/*
/// <summary>
/// Moves the string state forward to the next state in the sequence.
/// </summary>
/// <param name="container">The handler container instance</param>
public static void ForwardStringState(this IHandlerContainer container)
=> container.StringStateKeeper().MoveForward(container.HandlingUpdate);
/// <summary>
/// Moves the string state backward to the previous state in the sequence.
/// </summary>
/// <param name="container">The handler container instance</param>
public static void BackwardStringState(this IHandlerContainer container)
=> container.StringStateKeeper().MoveBackward(container.HandlingUpdate);
*/
}
}
@@ -1,13 +1,13 @@
using Telegram.Bot.Types;
using Telegrator.Core.StateKeeping;
using Telegrator.Core.States;
namespace Telegrator.StateKeeping
namespace Telegrator.States
{
/// <summary>
/// Resolves chat ID from Telegram updates for state management purposes.
/// Extracts the chat identifier from various types of updates to provide a consistent key for state operations.
/// </summary>
public class ChatIdResolver : IStateKeyResolver<long>
public class ChatIdResolver : IStateKeyResolver
{
/// <summary>
/// Resolves the chat ID from a Telegram update.
@@ -15,7 +15,7 @@ namespace Telegrator.StateKeeping
/// <param name="keySource">The Telegram update to extract the chat ID from.</param>
/// <returns>The chat ID as a long value.</returns>
/// <exception cref="ArgumentException">Thrown when the update does not contain a valid chat ID.</exception>
public long ResolveKey(Update keySource)
=> keySource.GetChatId() ?? throw new ArgumentException("Cannot resolve ChatID for this Update");
public string? ResolveKey(Update keySource)
=> keySource.GetChatId()?.ToString() ?? throw new ArgumentException("Cannot resolve ChatID for this Update");
}
}
@@ -0,0 +1,49 @@
using System.Collections.Concurrent;
using Telegrator.Core.States;
namespace Telegrator.States;
/// <summary>
/// Defines default in-memory state storage
/// </summary>
public class DefaultStateStorage : IStateStorage
{
private readonly ConcurrentDictionary<string, object> storage = [];
/// <inheritdoc/>
public Task DeleteAsync(string key, CancellationToken cancellationToken = default)
{
if (key is null)
throw new ArgumentNullException(nameof(key));
if (!storage.TryRemove(key, out object value))
throw new Exception("Failed to remove key '" + key + "' from storage.");
return Task.CompletedTask;
}
/// <inheritdoc/>
public Task<T?> GetAsync<T>(string key, CancellationToken ccancellationTokent = default)
{
if (key is null)
throw new ArgumentNullException(nameof(key));
if (storage.TryGetValue(key, out object value) && value is T)
return Task.FromResult((T?)value);
return Task.FromResult(default(T));
}
/// <inheritdoc/>
public Task SetAsync<T>(string key, T state, CancellationToken cancellationToken = default)
{
if (key is null)
throw new ArgumentNullException(nameof(key));
if (state is null)
throw new ArgumentNullException(nameof(state));
storage.Set(key, state);
return Task.CompletedTask;
}
}
+62
View File
@@ -0,0 +1,62 @@
using Telegrator.Core.States;
namespace Telegrator.States;
/// <summary>
/// State machine implementation for enum-based states.
/// Automatically creates an array of all enum values for state navigation.
/// </summary>
/// <typeparam name="TEnum">The enum type to be used for state management.</typeparam>
public class EnumStateMachine<TEnum> : IStateMachine<TEnum> where TEnum : struct, Enum, IEquatable<TEnum>
{
private readonly TEnum[] _states = Enum.GetValues(typeof(TEnum)).Cast<TEnum>().ToArray();
private TEnum _defaultState => _states.FirstOrDefault();
/// <inheritdoc/>
public async Task<TEnum> Current(IStateStorage storage, string updateKey, CancellationToken cancellationToken = default)
{
string key = FormatKey(updateKey);
TEnum state = await storage.GetAsync<TEnum>(key, cancellationToken);
return EqualityComparer<TEnum>.Default.Equals(state, default)
? _defaultState : state;
}
/// <inheritdoc/>
public async Task Advance(IStateStorage storage, string updateKey, CancellationToken cancellationToken = default)
{
string key = FormatKey(updateKey);
TEnum currentState = await storage.GetAsync<TEnum>(key, cancellationToken);
int currentIndex = Array.IndexOf(_states, currentState);
if (currentIndex < _states.Length - 1)
{
var nextState = _states[currentIndex + 1];
await storage.SetAsync(key, nextState, cancellationToken);
}
}
/// <inheritdoc/>
public async Task Retreat(IStateStorage storage, string updateKey, CancellationToken cancellationToken = default)
{
string key = FormatKey(updateKey);
TEnum currentState = await storage.GetAsync<TEnum>(key, cancellationToken);
int currentIndex = Array.IndexOf(_states, currentState);
if (currentIndex > 0)
{
var nextState = _states[currentIndex - 1];
await storage.SetAsync(key, nextState, cancellationToken);
}
}
/// <inheritdoc/>
public async Task Reset(IStateStorage storage, string updateKey, CancellationToken cancellationToken = default)
{
string key = FormatKey(updateKey);
await storage.SetAsync(key, _defaultState, cancellationToken);
}
private static string FormatKey(string updateKey)
=> typeof(TEnum).Name + ":" + updateKey;
}
+20
View File
@@ -0,0 +1,20 @@
using Telegram.Bot.Types;
using Telegrator.Core.States;
namespace Telegrator.States;
/// <summary>
/// Resolves sender ID from Telegram updates for state management purposes.
/// Extracts the sender identifier from various types of updates to provide a consistent key for state operations.
/// </summary>
public class SenderIdResolver : IStateKeyResolver
{
/// <summary>
/// Resolves the sender ID from a Telegram update.
/// </summary>
/// <param name="keySource">The Telegram update to extract the sender ID from.</param>
/// <returns>The sender ID as a long value.</returns>
/// <exception cref="ArgumentException">Thrown when the update does not contain a valid sender ID.</exception>
public string ResolveKey(Update keySource)
=> keySource.GetSenderId()?.ToString() ?? throw new ArgumentException("Cannot resolve SenderID for this Update");
}
+71
View File
@@ -0,0 +1,71 @@
using Telegram.Bot.Types;
using Telegrator.Core.States;
namespace Telegrator.States;
/// <inheritdoc cref="IStateMachine{TState}"/>
public class StateMachine<TMachine, TState>(IStateStorage stateStorage, Update handlingUpdate)
where TMachine : IStateMachine<TState>, new()
where TState : IEquatable<TState>
{
private readonly IStateStorage _stateStorage = stateStorage;
private readonly Update _handlingUpdate = handlingUpdate;
private readonly IStateMachine<TState> _stateMachine = new TMachine();
/// <summary>
/// Chosen key resolver
/// </summary>
public IStateKeyResolver? KeyResolver;
/// <inheritdoc cref="IStateMachine{TState}.Advance(IStateStorage, string, CancellationToken)"/>
public async Task Advance(CancellationToken cancellationToken = default)
{
if (KeyResolver is null)
throw new InvalidOperationException("KeyResolver is not set.");
string? key = KeyResolver.ResolveKey(_handlingUpdate);
if (key is null)
throw new InvalidOperationException("Failed to resolve Update key");
await _stateMachine.Advance(_stateStorage, key, cancellationToken);
}
/// <inheritdoc cref="IStateMachine{TState}.Current(IStateStorage, string, CancellationToken)"/>
public async Task<TState?> Current(CancellationToken cancellationToken = default)
{
if (KeyResolver is null)
throw new InvalidOperationException("KeyResolver is not set.");
string? key = KeyResolver.ResolveKey(_handlingUpdate);
if (key is null)
throw new InvalidOperationException("Failed to resolve Update key");
return await _stateMachine.Current(_stateStorage, key, cancellationToken);
}
/// <inheritdoc cref="IStateMachine{TState}.Reset(IStateStorage, string, CancellationToken)"/>
public async Task Reset(CancellationToken cancellationToken = default)
{
if (KeyResolver is null)
throw new InvalidOperationException("KeyResolver is not set.");
string? key = KeyResolver.ResolveKey(_handlingUpdate);
if (key is null)
throw new InvalidOperationException("Failed to resolve Update key");
await _stateMachine.Reset(_stateStorage, key, cancellationToken);
}
/// <inheritdoc cref="IStateMachine{TState}.Retreat(IStateStorage, string, CancellationToken)"/>
public async Task Retreat(CancellationToken cancellationToken = default)
{
if (KeyResolver is null)
throw new InvalidOperationException("KeyResolver is not set.");
string? key = KeyResolver.ResolveKey(_handlingUpdate);
if (key is null)
throw new InvalidOperationException("Failed to resolve Update key");
await _stateMachine.Retreat(_stateStorage, key, cancellationToken);
}
}
+6 -2
View File
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<TargetFramework>netstandard2.1</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<LangVersion>latest</LangVersion>
@@ -14,7 +14,7 @@
<EnforceCodeStyleInBuild>True</EnforceCodeStyleInBuild>
<Title>Telegrator : Telegram.Bot mediator framework</Title>
<Version>1.16.1</Version>
<Version>1.16.4</Version>
<Authors>Rikitav Tim4ik</Authors>
<Company>Rikitav Tim4ik</Company>
<RepositoryUrl>https://github.com/Rikitav/Telegrator</RepositoryUrl>
@@ -44,4 +44,8 @@
<ProjectReference Include="..\..\dev\Telegrator.RoslynGenerators\Telegrator.RoslynGenerators.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
</ItemGroup>
<ItemGroup>
<Folder Include="Annotations\Targetted\" />
</ItemGroup>
</Project>
+3 -1
View File
@@ -4,6 +4,7 @@ using Telegrator.Core;
using Telegrator.Logging;
using Telegrator.Mediation;
using Telegrator.Providers;
using Telegrator.States;
namespace Telegrator
{
@@ -75,8 +76,9 @@ namespace Telegrator
HandlersProvider handlerProvider = new HandlersProvider(Handlers, Options);
AwaitingProvider awaitingProvider = new AwaitingProvider(Options);
DefaultStateStorage stateStorage = new DefaultStateStorage();
updateRouter = new UpdateRouter(handlerProvider, awaitingProvider, Options, BotInfo);
updateRouter = new UpdateRouter(handlerProvider, awaitingProvider, stateStorage, Options, BotInfo);
// Log startup
TelegratorLogging.LogInformation($"Telegrator bot starting up - BotId: {BotInfo.User.Id}, Username: {BotInfo.User.Username}, MaxParallelHandlers: {Options.MaximumParallelWorkingHandlers ?? -1}");
+94 -14
View File
@@ -3,14 +3,13 @@ using Telegram.Bot.Types;
using Telegram.Bot.Types.Enums;
using Telegram.Bot.Types.Payments;
using Telegrator.Annotations;
using Telegrator.Attributes;
using Telegrator.Core;
using Telegrator.Core.Descriptors;
using Telegrator.Core.Handlers;
using Telegrator.Core.Handlers.Building;
using Telegrator.Core.StateKeeping;
using Telegrator.Core.States;
using Telegrator.Handlers.Building;
using Telegrator.StateKeeping;
using Telegrator.States;
namespace Telegrator
{
@@ -187,17 +186,6 @@ namespace Telegrator
/// <returns>An awaiter builder for callback query updates.</returns>
public static IAwaiterHandlerBuilder<CallbackQuery> AwaitCallbackQuery(this IHandlerContainer container)
=> container.AwaitUpdate<CallbackQuery>(UpdateType.CallbackQuery);
/// <summary>
/// Gets a state keeper instance for the specified types.
/// </summary>
/// <typeparam name="TKey">The type of the state key.</typeparam>
/// <typeparam name="TState">The type of the state value.</typeparam>
/// <typeparam name="TKeeper">The type of the state keeper.</typeparam>
/// <param name="_">The handler container (unused).</param>
/// <returns>The state keeper instance.</returns>
public static TKeeper GetStateKeeper<TKey, TState, TKeeper>(this IHandlerContainer _) where TKey : notnull where TState : IEquatable<TState> where TKeeper : StateKeeperBase<TKey, TState>, new()
=> StateKeeperAttribute<TKey, TState, TKeeper>.Shared;
}
/// <summary>
@@ -296,6 +284,98 @@ namespace Telegrator
}
}
/// <summary>
/// Provides extension methods for <see cref="IStateStorage"/> to easily initialize state machines.
/// </summary>
public static class StateStorageExtensions
{
/// <summary>
/// Initializes a state machine using the default <see cref="EnumStateMachine{TState}"/> for the specified update.
/// </summary>
/// <typeparam name="TState">The enum type representing the state.</typeparam>
/// <param name="stateStorage">The storage mechanism used to persist the state.</param>
/// <param name="handlingUpdate">The update context to resolve the state key from.</param>
/// <returns>A new instance of <see cref="StateMachine{TMachine, TState}"/>.</returns>
public static StateMachine<EnumStateMachine<TState>, TState> GetStateMachine<TState>(this IStateStorage stateStorage, Update handlingUpdate)
where TState : struct, Enum, IEquatable<TState>
=> new StateMachine<EnumStateMachine<TState>, TState>(stateStorage, handlingUpdate);
/// <summary>
/// Initializes a specific custom state machine for the specified update.
/// </summary>
/// <typeparam name="TMachine">The type of the state machine logic implementation.</typeparam>
/// <typeparam name="TState">The type of the state.</typeparam>
/// <param name="stateStorage">The storage mechanism used to persist the state.</param>
/// <param name="handlingUpdate">The update context to resolve the state key from.</param>
/// <returns>A new instance of <see cref="StateMachine{TMachine, TState}"/>.</returns>
public static StateMachine<TMachine, TState> GetStateMachine<TMachine, TState>(this IStateStorage stateStorage, Update handlingUpdate)
where TMachine : IStateMachine<TState>, new()
where TState : IEquatable<TState>
=> new StateMachine<TMachine, TState>(stateStorage, handlingUpdate);
/// <summary>
/// Initializes a state machine and explicitly configures it to resolve keys by the chat ID.
/// </summary>
/// <typeparam name="TMachine">The type of the state machine logic implementation.</typeparam>
/// <typeparam name="TState">The type of the state.</typeparam>
/// <param name="stateStorage">The storage mechanism used to persist the state.</param>
/// <param name="handlingUpdate">The update context to resolve the state key from.</param>
/// <returns>A configured instance of <see cref="StateMachine{TMachine, TState}"/>.</returns>
public static StateMachine<TMachine, TState> ByChatId<TMachine, TState>(this IStateStorage stateStorage, Update handlingUpdate)
where TMachine : IStateMachine<TState>, new()
where TState : IEquatable<TState>
=> new StateMachine<TMachine, TState>(stateStorage, handlingUpdate).ByChatId();
/// <summary>
/// Initializes a state machine and explicitly configures it to resolve keys by the sender (user) ID.
/// </summary>
/// <typeparam name="TMachine">The type of the state machine logic implementation.</typeparam>
/// <typeparam name="TState">The type of the state.</typeparam>
/// <param name="stateStorage">The storage mechanism used to persist the state.</param>
/// <param name="handlingUpdate">The update context to resolve the state key from.</param>
/// <returns>A configured instance of <see cref="StateMachine{TMachine, TState}"/>.</returns>
public static StateMachine<TMachine, TState> BySenderId<TMachine, TState>(this IStateStorage stateStorage, Update handlingUpdate)
where TMachine : IStateMachine<TState>, new()
where TState : IEquatable<TState>
=> new StateMachine<TMachine, TState>(stateStorage, handlingUpdate).BySenderId();
}
/// <summary>
/// Provides fluent extension methods for configuring <see cref="StateMachine{TMachine, TState}"/> instances.
/// </summary>
public static class StateMachineExtensions
{
/// <summary>
/// Configures the state machine to use a <see cref="ChatIdResolver"/> for state key resolution.
/// </summary>
/// <typeparam name="TMachine">The type of the state machine logic implementation.</typeparam>
/// <typeparam name="TState">The type of the state.</typeparam>
/// <param name="stateMachine">The state machine instance to configure.</param>
/// <returns>The same state machine instance for method chaining.</returns>
public static StateMachine<TMachine, TState> ByChatId<TMachine, TState>(this StateMachine<TMachine, TState> stateMachine)
where TMachine : IStateMachine<TState>, new()
where TState : IEquatable<TState>
{
stateMachine.KeyResolver = new ChatIdResolver();
return stateMachine;
}
/// <summary>
/// Configures the state machine to use a <see cref="SenderIdResolver"/> for state key resolution.
/// </summary>
/// <typeparam name="TMachine">The type of the state machine logic implementation.</typeparam>
/// <typeparam name="TState">The type of the state.</typeparam>
/// <param name="stateMachine">The state machine instance to configure.</param>
/// <returns>The same state machine instance for method chaining.</returns>
public static StateMachine<TMachine, TState> BySenderId<TMachine, TState>(this StateMachine<TMachine, TState> stateMachine)
where TMachine : IStateMachine<TState>, new()
where TState : IEquatable<TState>
{
stateMachine.KeyResolver = new SenderIdResolver();
return stateMachine;
}
}
/// <summary>
/// Extension methods for handlers collections.
/// Provides convenient methods for creating implicit handlers.
@@ -29,7 +29,7 @@ public class FilterTests
{
// Arrange (Given) - подготовка тестовых данных
var anyFilter = Filter<Update>.Any();
var context = new FilterExecutionContext<Update>(new TelegramBotInfo(null), new Update(), new Update(), new Dictionary<string, object>(), new CompletedFiltersList());
var context = new FilterExecutionContext<Update>(null, new TelegramBotInfo(null), new Update(), new Update(), new Dictionary<string, object>(), new CompletedFiltersList());
// Act (When) - выполнение тестируемого действия
var result = anyFilter.CanPass(context);
@@ -49,7 +49,7 @@ public class FilterTests
// Arrange
var alwaysTrueFilter = Filter<Update>.Any();
var reverseFilter = alwaysTrueFilter.Not();
var context = new FilterExecutionContext<Update>(new TelegramBotInfo(null), new Update(), new Update(), new Dictionary<string, object>(), new CompletedFiltersList());
var context = new FilterExecutionContext<Update>(null, new TelegramBotInfo(null), new Update(), new Update(), new Dictionary<string, object>(), new CompletedFiltersList());
// Act
var result = reverseFilter.CanPass(context);
@@ -74,7 +74,7 @@ public class FilterTests
var firstFilter = Filter<Update>.If(_ => firstResult);
var secondFilter = Filter<Update>.If(_ => secondResult);
var andFilter = firstFilter.And(secondFilter);
var context = new FilterExecutionContext<Update>(new TelegramBotInfo(null), new Update(), new Update(), new Dictionary<string, object>(), new CompletedFiltersList());
var context = new FilterExecutionContext<Update>(null, new TelegramBotInfo(null), new Update(), new Update(), new Dictionary<string, object>(), new CompletedFiltersList());
// Act
var result = andFilter.CanPass(context);
@@ -99,7 +99,7 @@ public class FilterTests
var firstFilter = Filter<Update>.If(_ => firstResult);
var secondFilter = Filter<Update>.If(_ => secondResult);
var orFilter = firstFilter.Or(secondFilter);
var context = new FilterExecutionContext<Update>(new TelegramBotInfo(null), new Update(), new Update(), new Dictionary<string, object>(), new CompletedFiltersList());
var context = new FilterExecutionContext<Update>(null, new TelegramBotInfo(null), new Update(), new Update(), new Dictionary<string, object>(), new CompletedFiltersList());
// Act
var result = orFilter.CanPass(context);
@@ -122,7 +122,7 @@ public class FilterTests
var filter3 = Filter<Update>.If(_ => false);
var compiledFilter = new CompiledFilter<Update>(filter1, filter2, filter3);
var context = new FilterExecutionContext<Update>(new TelegramBotInfo(null), new Update(), new Update(), new Dictionary<string, object>(), new CompletedFiltersList());
var context = new FilterExecutionContext<Update>(null, new TelegramBotInfo(null), new Update(), new Update(), new Dictionary<string, object>(), new CompletedFiltersList());
// Act
var result = compiledFilter.CanPass(context);
@@ -164,7 +164,7 @@ public class FilterTests
wasCalled = true;
return true;
});
var context = new FilterExecutionContext<Update>(new TelegramBotInfo(null), new Update(), new Update(), new Dictionary<string, object>(), new CompletedFiltersList());
var context = new FilterExecutionContext<Update>(null, new TelegramBotInfo(null), new Update(), new Update(), new Dictionary<string, object>(), new CompletedFiltersList());
// Act
var result = functionFilter.CanPass(context);