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 # Fody - auto-generated XML schema
FodyWeavers.xsd 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) ![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 ## 🚀 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.Handlers;
using Telegrator.Annotations; using Telegrator.Annotations;
[CommandHandler, CommandAlias("first"), NumericState(SpecialState.NoState)] [CommandHandler, CommandAlias("first"), State<SetupWizard>(null)]
public class StateKeepFirst : CommandHandler public class StateKeepFirst : CommandHandler
{ {
public override async Task<Result> Execute(IHandlerContainer<Message> container, CancellationToken cancellation) public override async Task<Result> Execute(IHandlerContainer<Message> container, CancellationToken cancellation)
{ {
container.CreateNumericState(); StateStorage.GetStateMachine<SetupWizard>().BysenderId().Advance();
container.ForwardNumericState();
await Reply("first state moved (1)", cancellationToken: cancellation); await Reply("first state moved (1)", cancellationToken: cancellation);
return Result.Ok(); return Result.Ok();
} }
+1
View File
@@ -1,5 +1,6 @@
<Solution> <Solution>
<Project Path="dev/Telegrator.RoslynGenerators/Telegrator.RoslynGenerators.csproj" /> <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.Analyzers/Telegrator.Analyzers.csproj" />
<Project Path="src/Telegrator.Hosting.Web/Telegrator.Hosting.Web.csproj" /> <Project Path="src/Telegrator.Hosting.Web/Telegrator.Hosting.Web.csproj" />
<Project Path="src/Telegrator.Hosting/Telegrator.Hosting.csproj" /> <Project Path="src/Telegrator.Hosting/Telegrator.Hosting.csproj" />
@@ -5,10 +5,6 @@ using System.Collections.Immutable;
using System.Text; using System.Text;
using Telegrator.RoslynGenerators.RoslynExtensions; using Telegrator.RoslynGenerators.RoslynExtensions;
#if DEBUG
using System.Diagnostics;
#endif
namespace Telegrator.RoslynGenerators; namespace Telegrator.RoslynGenerators;
[Generator(LanguageNames.CSharp)] [Generator(LanguageNames.CSharp)]
@@ -72,6 +68,9 @@ public class ImplicitHandlerBuilderExtensionsGenerator : IIncrementalGenerator
if (className == "FilterAnnotation") if (className == "FilterAnnotation")
continue; continue;
if (className == "StateAttribute")
continue;
MethodDeclarationSyntax? targeter = classDeclaration.Members.OfType<MethodDeclarationSyntax>().SingleOrDefault(IsTargeterMethod); MethodDeclarationSyntax? targeter = classDeclaration.Members.OfType<MethodDeclarationSyntax>().SingleOrDefault(IsTargeterMethod);
if (targeter != null) if (targeter != null)
{ {
@@ -101,7 +100,13 @@ public class ImplicitHandlerBuilderExtensionsGenerator : IIncrementalGenerator
continue; continue;
usings.UnionAdd(classDeclaration.FindAncestor<CompilationUnitSyntax>().Usings, UsingEqualityComparer); 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) if (classDeclaration.ParameterList != null && classDeclaration.BaseList != null)
{ {
@@ -140,16 +145,15 @@ public class ImplicitHandlerBuilderExtensionsGenerator : IIncrementalGenerator
{ {
ClassDeclarationSyntax extensionsClass = SyntaxFactory.ClassDeclaration("HandlerBuilderExtensions") ClassDeclarationSyntax extensionsClass = SyntaxFactory.ClassDeclaration("HandlerBuilderExtensions")
.WithModifiers(Modifiers(SyntaxKind.PublicKeyword, SyntaxKind.StaticKeyword, SyntaxKind.PartialKeyword)) .WithModifiers(Modifiers(SyntaxKind.PublicKeyword, SyntaxKind.StaticKeyword, SyntaxKind.PartialKeyword))
.AddMembers([.. targetters.Values, .. extensions]) .AddMembers([.. targetters.Values, .. extensions]);
.DecorateType(1);
NamespaceDeclarationSyntax namespaceDeclaration = SyntaxFactory.NamespaceDeclaration(SyntaxFactory.ParseName("Telegrator")) NamespaceDeclarationSyntax namespaceDeclaration = SyntaxFactory.NamespaceDeclaration(SyntaxFactory.ParseName("Telegrator"))
.WithMembers([extensionsClass]) .WithMembers([extensionsClass]);
.Decorate();
CompilationUnitSyntax compilationUnit = SyntaxFactory.CompilationUnit() CompilationUnitSyntax compilationUnit = SyntaxFactory.CompilationUnit()
.WithUsings([.. usings]) .WithUsings([.. usings])
.WithMembers([namespaceDeclaration]); .WithMembers([namespaceDeclaration])
.NormalizeWhitespace();
context.AddSource("GeneratedHandlerBuilderExtensions.cs", compilationUnit.ToFullString()); context.AddSource("GeneratedHandlerBuilderExtensions.cs", compilationUnit.ToFullString());
} }
@@ -175,7 +179,7 @@ public class ImplicitHandlerBuilderExtensionsGenerator : IIncrementalGenerator
if (targetterMethod.ExpressionBody != null) if (targetterMethod.ExpressionBody != null)
method = method.WithExpressionBody(targetterMethod.ExpressionBody).WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken)); 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) 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) MethodDeclarationSyntax method = SyntaxFactory.MethodDeclaration(returnType, identifier)
.WithParameterList(parameters) .WithParameterList(parameters)
.WithBody(body.DecorateBlock(2)) .WithBody(body)
.WithTypeParameterList(typeParameters) .WithTypeParameterList(typeParameters)
.WithModifiers(Modifiers(SyntaxKind.PublicKeyword, SyntaxKind.StaticKeyword)) .WithModifiers(Modifiers(SyntaxKind.PublicKeyword, SyntaxKind.StaticKeyword))
.WithConstraintClauses([typeParameterConstraint]) .WithConstraintClauses([typeParameterConstraint])
.DecorateMember(2)
.WithLeadingTrivia(xmlDoc); .WithLeadingTrivia(xmlDoc);
return method; return method;
@@ -230,7 +233,7 @@ public class ImplicitHandlerBuilderExtensionsGenerator : IIncrementalGenerator
private static IEnumerable<ConstructorDeclarationSyntax> GetConstructors(ClassDeclarationSyntax classDeclaration) private static IEnumerable<ConstructorDeclarationSyntax> GetConstructors(ClassDeclarationSyntax classDeclaration)
=> classDeclaration.Members.OfType<ConstructorDeclarationSyntax>().Where(ctor => ctor.Modifiers.HasModifiers("public")); => 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)) if (targeters.TryGetValue(classDeclaration.Identifier.ValueText, out MethodDeclarationSyntax targeter))
return 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)) if (classDeclaration.BaseList != null && targeters.TryGetValue(classDeclaration.BaseList.Types.ElementAt(0).Type.ToString(), out targeter))
return targeter; return targeter;
throw new TargteterNotFoundException(); return null;
} }
private static SyntaxTriviaList BuildExtensionXmlDocTrivia(ClassDeclarationSyntax classDeclaration, ParameterListSyntax methodParameters) 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. **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 ### 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). - A Telegram Bot Token from [@BotFather](https://t.me/BotFather).
### .NET CLI ### .NET CLI
@@ -61,7 +61,7 @@ Install-Package Telegrator
``` ```
### Hosting Integrations ### Hosting Integrations
- .NET Core >= 8.0 - .NET Core >= 10.0
- `Telegrator.Hosting`: For console/background services - `Telegrator.Hosting`: For console/background services
- `Telegrator.Hosting.Web`: For ASP.NET Core/Webhook - `Telegrator.Hosting.Web`: For ASP.NET Core/Webhook
@@ -325,7 +325,7 @@ builder.Handlers.AddMethod<CallbackQuery>(Option1Handler);
```csharp ```csharp
public enum UserState public enum UserState
{ {
Start = SpecialState.NoState, Start,
WaitingForName, WaitingForName,
WaitingForAge WaitingForAge
} }
@@ -333,39 +333,40 @@ public enum UserState
// Start conversation // Start conversation
[CommandHandler] [CommandHandler]
[CommandAlias("register")] [CommandAlias("register")]
[EnumState<UserState>(UserState.Start)] [State<UserState>(UserState.Start)]
private static async Task<Result> StartRegistration(IHandlerContainer<Message> container, CancellationToken cancellationToken) 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); await container.Reply("Please enter your name:", cancellationToken: cancellationToken);
return Ok; return Ok;
} }
// Handle name input // Handle name input
[MessageHandler] [MessageHandler]
[EnumState<UserState>(UserState.WaitingForName)] [State<UserState>(UserState.WaitingForName)]
private static async Task<Result> HandleName(IHandlerContainer<Message> container, CancellationToken cancellationToken) private static async Task<Result> HandleName(IHandlerContainer<Message> container, CancellationToken cancellationToken)
{ {
var name = container.Input.Text; var name = container.Input.Text;
container.ForwardEnumState<UserState>(); StateStorage.GetStateMachine<UserState>().BySenderId().Advance();
await container.Reply($"Hello {name}! Please enter your age:", cancellationToken: cancellationToken); await container.Reply($"Hello {name}! Please enter your age:", cancellationToken: cancellationToken);
return Ok; return Ok;
} }
// Handle age input // Handle age input
[MessageHandler] [MessageHandler]
[EnumState<UserState>(UserState.WaitingForAge)] [State<UserState>(UserState.WaitingForAge)]
private static async Task<Result> HandleAge(IHandlerContainer<Message> container, CancellationToken cancellationToken) private static async Task<Result> HandleAge(IHandlerContainer<Message> container, CancellationToken cancellationToken)
{ {
if (int.TryParse(container.Input.Text, out int age)) 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); await container.Reply($"Registration complete! Name: {name}, Age: {age}", cancellationToken: cancellationToken);
} }
else else
{ {
await container.Reply("Please enter a valid age (number):", cancellationToken: cancellationToken); await container.Reply("Please enter a valid age (number):", cancellationToken: cancellationToken);
} }
return Ok; return Ok;
} }
@@ -497,50 +498,43 @@ public class RestrictedHandler : MessageHandler
### 3.3. State Management ### 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] > [!NOTE]
> Each type of `StateKeeper`'s (EnumStateKeeper, NumericStateKeeper) is shared between **EVERY** handler in project. > Each type of `StateKeeper`'s keys and states are shared between **EVERY** handler in project.
**Types of State:**
- **NumericState**: Integer-based steps
- **StringState**: Named steps
- **EnumState**: Enum-based scenarios
**How to Use:** **How to Use:**
1. Define your state (enum/int/string) 1. Define your state (enum/int/string)
2. Use a state filter attribute on your handler: 2. Use a state filter attribute on your handler:
- `[EnumState<MyEnum>(MyEnum.Step1)]` - `[State<MyEnum>(MyEnum.Step1)]`
- `[NumericState(1)]`
- `[StringState("waiting_input")]`
3. Change state inside the handler using extension methods: 3. Change state inside the handler using extension methods:
- `container.ForwardEnumState<MyEnum>()` - `StateStorage.GetStateMachine<MyEnum>().BySenderId().Current()`
- `container.ForwardNumericState()` - `StateStorage.GetStateMachine<MyEnum>().BySenderId().Advance()`
- `container.ForwardStringState()` - `StateStorage.GetStateMachine<MyEnum>().BySenderId().Retreat()`
- `container.DeleteEnumState<MyEnum>()` - `StateStorage.GetStateMachine<MyEnum>().BySenderId().Reset()`
**Example:** **Example:**
```csharp ```csharp
public enum QuizState public enum QuizState
{ {
Start = SpecialState.NoState, Q1, Q2 Start, Q1, Q2
} }
[CommandHandler] [CommandHandler]
[CommandAlias("quiz")] [CommandAlias("quiz")]
[EnumState<QuizState>(QuizState.Start)] [State<QuizState>(QuizState.Start)]
public class StartQuizHandler : CommandHandler public class StartQuizHandler : CommandHandler
{ {
public override async Task<Result> Execute(IHandlerContainer<Message> container, CancellationToken cancellation) 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?"); await Reply("Quiz started! Question 1: What is the capital of France?");
return Ok; return Ok;
} }
} }
[MessageHandler] [MessageHandler]
[EnumState<QuizState>(QuizState.Q1)] [State<QuizState>(QuizState.Q1)]
public class Q1Handler : MessageHandler public class Q1Handler : MessageHandler
{ {
public override async Task<Result> Execute(IHandlerContainer<Message> container, CancellationToken cancellation) public override async Task<Result> Execute(IHandlerContainer<Message> container, CancellationToken cancellation)
@@ -550,7 +544,7 @@ public class Q1Handler : MessageHandler
else else
await Reply("Incorrect. The answer is Paris."); await Reply("Incorrect. The answer is Paris.");
container.ForwardEnumState<QuizState>(); StateStorage.GetStateMachine<QuizState>().BySenderId().Advance();
await Reply("Question 2: What is 2 + 2?"); await Reply("Question 2: What is 2 + 2?");
return Ok; return Ok;
} }
@@ -559,8 +553,8 @@ public class Q1Handler : MessageHandler
> **How is it working?** > **How is it working?**
> 1. **Enum State Definition**: `QuizState` enum defines the conversation flow with `Start = SpecialState.NoState` indicating no initial state. > 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. > 2. **State Filter**: `[State<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). > 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`. > 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. > 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(); 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?** > **How is it working?**
> 1. **Console Integration**: `TelegratorClient` provides a simple way to create bots in console applications. > 1. **Console Integration**: `TelegratorClient` provides a simple way to create bots in console applications.
@@ -1425,28 +1419,28 @@ public enum UserState
// Start conversation // Start conversation
[CommandHandler] [CommandHandler]
[CommandAlias("register")] [CommandAlias("register")]
[EnumState<UserState>(UserState.Start)] [State<UserState>(UserState.Start)]
private static async Task<Result> StartRegistration(IHandlerContainer<Message> container, CancellationToken cancellationToken) 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); await container.Reply("Please enter your name:", cancellationToken: cancellationToken);
return Ok; return Ok;
} }
// Handle name input // Handle name input
[MessageHandler] [MessageHandler]
[EnumState<UserState>(UserState.WaitingForName)] [State<UserState>(UserState.WaitingForName)]
private static async Task<Result> HandleName(IHandlerContainer<Message> container, CancellationToken cancellationToken) private static async Task<Result> HandleName(IHandlerContainer<Message> container, CancellationToken cancellationToken)
{ {
var name = container.Input.Text; var name = container.Input.Text;
container.ForwardEnumState<UserState>(); StateStorage.GetStateMachine<UserState>().BySenderId().Advance();
await container.Reply($"Hello {name}! Please enter your age:", cancellationToken: cancellationToken); await container.Reply($"Hello {name}! Please enter your age:", cancellationToken: cancellationToken);
return Ok; return Ok;
} }
// Handle age input // Handle age input
[MessageHandler] [MessageHandler]
[EnumState<UserState>(UserState.WaitingForAge)] [State<UserState>(UserState.WaitingForAge)]
private static async Task<Result> HandleAge(IHandlerContainer<Message> container, CancellationToken cancellationToken) private static async Task<Result> HandleAge(IHandlerContainer<Message> container, CancellationToken cancellationToken)
{ {
if (int.TryParse(container.Input.Text, out int age)) if (int.TryParse(container.Input.Text, out int age))
@@ -1723,12 +1717,11 @@ dotnet add package Telegrator.Hosting.Web
**Core Components:** **Core Components:**
- `TelegramBotWebHost` - The main web hosted service for webhook handling - `TelegramBotWebHost` - The main web hosted service for webhook handling
- `TelegramBotWebHostBuilder` - Builder pattern for configuring the web host - `TelegramBotWebHostBuilder` - Builder pattern for configuring the web host
- `TelegramBotWebOptions` - Configuration options for web application settings - `WebhookerOptions` - Configuration options for webhook settings
- `TelegratorWebOptions` - Configuration options for webhook settings
**Configuration Requirements:** **Configuration Requirements:**
- `TelegramBotClientOptions` must be configured as it contains the bot token - `TelegratorOptions` must be configured as it contains the bot token
- `TelegratorWebOptions` must be configured through external sources (appsettings.json) for webhook settings - `WebhookerOptions` must be configured through external sources (appsettings.json) for webhook settings
**Basic Example:** **Basic Example:**
```csharp ```csharp
@@ -1750,11 +1743,11 @@ await host.StartAsync();
**Configuration via appsettings.json:** **Configuration via appsettings.json:**
```json ```json
{ {
"TelegramBotClientOptions": { "TelegratorOptions": {
"Token": "YOUR_BOT_TOKEN" "Token": "YOUR_BOT_TOKEN"
}, },
"TelegratorWebOptions": { "WebhookerOptions": {
"WebhookUri": "https://your-domain.com/webhook", "WebhookUri": "https://your-domain.com/webhook",
"SecretToken": "your-secret-token", "SecretToken": "your-secret-token",
"MaxConnections": 40, "MaxConnections": 40,
@@ -1790,7 +1783,7 @@ The bot token must be configured either in `appsettings.json` or through environ
```json ```json
{ {
"TelegramBotClientOptions": { "TelegratorOptions": {
"Token": "YOUR_BOT_TOKEN" "Token": "YOUR_BOT_TOKEN"
} }
} }
@@ -1805,7 +1798,7 @@ export TelegramBotClientOptions__Token="YOUR_BOT_TOKEN"
**Advanced Configuration:** **Advanced Configuration:**
```json ```json
{ {
"TelegramBotClientOptions": { "TelegratorOptions": {
"Token": "YOUR_BOT_TOKEN", "Token": "YOUR_BOT_TOKEN",
"BaseUrl": "https://api.telegram.org" "BaseUrl": "https://api.telegram.org"
}, },
@@ -2158,10 +2151,11 @@ await host.StartAsync();
**Configuration (appsettings.json):** **Configuration (appsettings.json):**
```json ```json
{ {
"TelegramBotClientOptions": { "TelegratorOptions": {
"Token": "YOUR_BOT_TOKEN" "Token": "YOUR_BOT_TOKEN"
}, },
"TelegratorWebOptions": {
"WebhookerOptions": {
"WebhookUri": "https://your-domain.com/webhook", "WebhookUri": "https://your-domain.com/webhook",
"SecretToken": "your-secret-token", "SecretToken": "your-secret-token",
"MaxConnections": 40, "MaxConnections": 40,
@@ -2283,12 +2277,12 @@ public class StartWizardHandler : CommandHandler
### 7.5. Logging System ### 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 #### Overview
The logging system consists of: The logging system consists of:
- **Alligator** - Centralized static logging system - **TelegratorLogging** - Centralized static logging system
- **ITelegratorLogger** - Core logging interface - **ITelegratorLogger** - Core logging interface
- **NullLogger** - No-op logger - **NullLogger** - No-op logger
- **ConsoleLogger** - Simple console output - **ConsoleLogger** - Simple console output
@@ -2301,11 +2295,11 @@ The logging system consists of:
using Telegrator.Logging; using Telegrator.Logging;
// Add console adapter // Add console adapter
Alligator.AddAdapter(new ConsoleLoggerAdapter(LogLevel.Debug, includeTimestamp: true)); TelegratorLogging.AddAdapter(new ConsoleLoggerAdapter(LogLevel.Debug, includeTimestamp: true));
// Use logging // Use logging
Alligator.LogInformation("Bot started"); TelegratorLogging.LogInformation("Bot started");
Alligator.LogError("Something went wrong", exception); TelegratorLogging.LogError("Something went wrong", exception);
``` ```
**Custom Logger Adapter:** **Custom Logger Adapter:**
@@ -2322,7 +2316,7 @@ public class CustomLogger : ITelegratorLogger
} }
// Add custom adapter // Add custom adapter
Alligator.AddAdapter(new CustomLogger()); TelegratorLogging.AddAdapter(new CustomLogger());
``` ```
#### Hosting Integration #### Hosting Integration
@@ -2340,7 +2334,7 @@ var loggerFactory = LoggerFactory.Create(builder =>
// Add Microsoft.Extensions.Logging adapter // Add Microsoft.Extensions.Logging adapter
ILogger<Program> logger = loggerFactory.CreateLogger<Program>(); ILogger<Program> logger = loggerFactory.CreateLogger<Program>();
MicrosoftLoggingAdapter adapter = new MicrosoftLoggingAdapter(logger); MicrosoftLoggingAdapter adapter = new MicrosoftLoggingAdapter(logger);
Alligator.AddAdapter(adapter); TelegratorLogging.AddAdapter(adapter);
var bot = new TelegratorClient("<BOT_TOKEN>"); var bot = new TelegratorClient("<BOT_TOKEN>");
``` ```
@@ -2359,14 +2353,14 @@ All logging methods support simple message logging:
```csharp ```csharp
// In your handlers or aspects // In your handlers or aspects
Alligator.LogInformation("Handler executed"); TelegratorLogging.LogInformation("Handler executed");
Alligator.LogError("Something went wrong", exception); TelegratorLogging.LogError("Something went wrong", exception);
Alligator.LogWarning("User exceeded rate limit"); TelegratorLogging.LogWarning("User exceeded rate limit");
``` ```
#### Performance Considerations #### Performance Considerations
- **Alligator** has minimal overhead with thread-safe adapter management - **TelegratorLogging** has minimal overhead with thread-safe adapter management
- **NullLogger** has zero overhead - **NullLogger** has zero overhead
- **ConsoleLogger** is lightweight for development - **ConsoleLogger** is lightweight for development
- **MicrosoftLoggingAdapter** delegates to the underlying framework - **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; using Telegrator.Logging;
// Add console adapter for debugging // Add console adapter for debugging
Alligator.AddConsoleAdapter(LogLevel.Debug, includeTimestamp: true); TelegratorLogging.AddConsoleAdapter(LogLevel.Debug, includeTimestamp: true);
// Use logging // Use logging
Alligator.LogInformation("Bot started"); TelegratorLogging.LogInformation("Bot started");
Alligator.LogError("Something went wrong", exception); TelegratorLogging.LogError("Something went wrong", exception);
``` ```
#### Common Debugging Steps #### Common Debugging Steps
@@ -2599,9 +2593,9 @@ Alligator.LogError("Something went wrong", exception);
#### Simple Logging #### Simple Logging
```csharp ```csharp
// In your handlers or aspects // In your handlers or aspects
Alligator.LogInformation("Handler executed"); TelegratorLogging.LogInformation("Handler executed");
Alligator.LogError("Something went wrong", exception); TelegratorLogging.LogError("Something went wrong", exception);
Alligator.LogWarning("User exceeded rate limit"); 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!** > **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"> <member name="P:Telegrator.Hosting.Web.TelegramBotWebHostBuilder.Environment">
<inheritdoc/> <inheritdoc/>
</member> </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)"> <member name="M:Telegrator.Hosting.Web.TelegramBotWebHostBuilder.#ctor(Microsoft.AspNetCore.Builder.WebApplicationBuilder,Microsoft.AspNetCore.Builder.WebApplicationOptions)">
<summary> <summary>
Initializes a new instance of the <see cref="T:Telegrator.Hosting.Web.TelegramBotWebHostBuilder"/> class. Initializes a new instance of the <see cref="T:Telegrator.Hosting.Web.TelegramBotWebHostBuilder"/> class.
@@ -107,6 +113,14 @@
<param name="webApplicationBuilder"></param> <param name="webApplicationBuilder"></param>
<param name="settings"></param> <param name="settings"></param>
</member> </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)"> <member name="M:Telegrator.Hosting.Web.TelegramBotWebHostBuilder.#ctor(Microsoft.AspNetCore.Builder.WebApplicationBuilder,Telegrator.Core.IHandlersCollection,Microsoft.AspNetCore.Builder.WebApplicationOptions)">
<summary> <summary>
Initializes a new instance of the <see cref="T:Telegrator.Hosting.Web.TelegramBotWebHostBuilder"/> class. Initializes a new instance of the <see cref="T:Telegrator.Hosting.Web.TelegramBotWebHostBuilder"/> class.
@@ -115,12 +129,24 @@
<param name="handlers"></param> <param name="handlers"></param>
<param name="settings"></param> <param name="settings"></param>
</member> </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"> <member name="M:Telegrator.Hosting.Web.TelegramBotWebHostBuilder.Build">
<summary> <summary>
Builds the host. Builds the host.
</summary> </summary>
<returns></returns> <returns></returns>
</member> </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"> <member name="T:Telegrator.Hosting.Web.WebhookerOptions">
<summary> <summary>
Configuration options for Telegram bot behavior and execution settings. Configuration options for Telegram bot behavior and execution settings.
@@ -185,7 +211,7 @@
<member name="M:Telegrator.ServicesCollectionExtensions.get_Handlers(Microsoft.AspNetCore.Builder.WebApplicationBuilder)"> <member name="M:Telegrator.ServicesCollectionExtensions.get_Handlers(Microsoft.AspNetCore.Builder.WebApplicationBuilder)">
<inheritdoc cref="P:Telegrator.ServicesCollectionExtensions.&lt;G&gt;$41F16C2D39AF52899E745C9C9F42FF83.Handlers"/> <inheritdoc cref="P:Telegrator.ServicesCollectionExtensions.&lt;G&gt;$41F16C2D39AF52899E745C9C9F42FF83.Handlers"/>
</member> </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> <summary>
Replaces TelegramBotWebHostBuilder. Configures DI, options, and handlers. Replaces TelegramBotWebHostBuilder. Configures DI, options, and handlers.
</summary> </summary>
+29 -30
View File
@@ -13,7 +13,7 @@
<param name="services"></param> <param name="services"></param>
<param name="configuration"></param> <param name="configuration"></param>
</member> </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> <summary>
Implementation of <see cref="T:Telegrator.Core.ITelegramBotInfo"/> that provides bot information. 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 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 Provides access to configuration of this Hosted telegram bot
</summary> </summary>
</member> </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"> <member name="T:Telegrator.Hosting.TelegramBotHost">
<summary> <summary>
Represents a hosted telegram bot Represents a hosted telegram bot
@@ -133,6 +112,12 @@
<member name="P:Telegrator.Hosting.TelegramBotHostBuilder.Environment"> <member name="P:Telegrator.Hosting.TelegramBotHostBuilder.Environment">
<inheritdoc/> <inheritdoc/>
</member> </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)"> <member name="M:Telegrator.Hosting.TelegramBotHostBuilder.#ctor(Microsoft.Extensions.Hosting.HostApplicationBuilder,Microsoft.Extensions.Hosting.HostApplicationBuilderSettings)">
<summary> <summary>
Initializes a new instance of the <see cref="T:Telegrator.Hosting.TelegramBotHostBuilder"/> class. Initializes a new instance of the <see cref="T:Telegrator.Hosting.TelegramBotHostBuilder"/> class.
@@ -140,6 +125,14 @@
<param name="hostApplicationBuilder"></param> <param name="hostApplicationBuilder"></param>
<param name="settings"></param> <param name="settings"></param>
</member> </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)"> <member name="M:Telegrator.Hosting.TelegramBotHostBuilder.#ctor(Microsoft.Extensions.Hosting.HostApplicationBuilder,Telegrator.Core.IHandlersCollection,Microsoft.Extensions.Hosting.HostApplicationBuilderSettings)">
<summary> <summary>
Initializes a new instance of the <see cref="T:Telegrator.Hosting.TelegramBotHostBuilder"/> class. Initializes a new instance of the <see cref="T:Telegrator.Hosting.TelegramBotHostBuilder"/> class.
@@ -148,12 +141,24 @@
<param name="handlers"></param> <param name="handlers"></param>
<param name="settings"></param> <param name="settings"></param>
</member> </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"> <member name="M:Telegrator.Hosting.TelegramBotHostBuilder.Build">
<summary> <summary>
Builds the host. Builds the host.
</summary> </summary>
<returns></returns> <returns></returns>
</member> </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"> <member name="T:Telegrator.Logging.MicrosoftLoggingAdapter">
<summary> <summary>
Adapter for Microsoft.Extensions.Logging to work with Telegrator logging system. 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)"> <member name="M:Telegrator.Polling.HostedUpdateReceiver.ExecuteAsync(System.Threading.CancellationToken)">
<inheritdoc/> <inheritdoc/>
</member> </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"> <member name="T:Telegrator.Polling.HostUpdateRouter">
<inheritdoc/> <inheritdoc/>
</member> </member>
@@ -204,7 +203,7 @@
<see cref="T:Microsoft.Extensions.Logging.ILogger"/> of this router <see cref="T:Microsoft.Extensions.Logging.ILogger"/> of this router
</summary> </summary>
</member> </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/> <inheritdoc/>
</member> </member>
<member name="M:Telegrator.Polling.HostUpdateRouter.HandleUpdateAsync(Telegram.Bot.ITelegramBotClient,Telegram.Bot.Types.Update,System.Threading.CancellationToken)"> <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)"> <member name="M:Telegrator.HostBuilderExtensions.get_Handlers(Microsoft.Extensions.Hosting.IHostApplicationBuilder)">
<inheritdoc cref="P:Telegrator.HostBuilderExtensions.&lt;G&gt;$605D8CCF64349EA050C790D67C500BD9.Handlers"/> <inheritdoc cref="P:Telegrator.HostBuilderExtensions.&lt;G&gt;$605D8CCF64349EA050C790D67C500BD9.Handlers"/>
</member> </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> <summary>
Replaces TelegramBotWebHostBuilder. Configures DI, options, and handlers. Replaces TelegramBotWebHostBuilder. Configures DI, options, and handlers.
</summary> </summary>
+475 -900
View File
File diff suppressed because it is too large Load Diff
+13
View File
@@ -0,0 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Telegrator" Version="1.16.2"/>
</ItemGroup>
</Project>
+11
View File
@@ -0,0 +1,11 @@
using Telegrator;
namespace Telegrator.Examples;
public class EchoBot
{
public static void Main(string[] args)
{
var client = new TelegratorClient();
}
}
@@ -0,0 +1,57 @@
# Telegrator.RedisStateStorage
**Telegrator.RedisStateStorage** is an extension for the Telegrator framework that provides Redis powered IStateStorage implementation.
---
## Requirements
- .NET standart 2.1 or later
- [Telegrator](https://github.com/Rikitav/Telegrator)
---
## Installation
```shell
dotnet add package Telegrator.RedisStateStorage
```
---
## Quick Start Example
**Program.cs:**
```csharp
using Telegrator.Hosting;
// Creating builder
TelegramBotHostBuilder builder = TelegramBotHost.CreateBuilder(new HostApplicationBuilderSettings()
{
Args = args,
ApplicationName = "TelegramBotHost example",
});
// Registerring handlers
builder.Handlers.CollectHandlersAssemblyWide();
// Register your services and
builder.Services.AddService<IStateStorage, RedisStateStorage>(services =>
new RedisStateStorage(ConnectionMultiplexer.Connect("server1:6379, server2:6379")));
// Building and running application
TelegramBotHost telegramBot = builder.Build();
telegramBot.SetBotCommands();
telegramBot.Run();
```
---
## Documentation
- [Telegrator Main Docs](https://github.com/Rikitav/Telegrator)
- [Getting Started Guide](https://github.com/Rikitav/Telegrator/wiki/Getting-started)
- [Annotation Overview](https://github.com/Rikitav/Telegrator/wiki/Annotation-overview)
---
## License
GPLv3
@@ -0,0 +1,39 @@
using StackExchange.Redis;
using System.Text.Json;
using Telegrator.Core.States;
namespace Telegrator.States;
/// <summary>
/// Provides a Redis-based implementation of the <see cref="IStateStorage"/> interface.
/// Serializes state objects to JSON format before storing them in the Redis database.
/// </summary>
public class RedisStateStorage(IConnectionMultiplexer redis) : IStateStorage
{
private readonly IDatabase _db = redis.GetDatabase();
/// <inheritdoc/>
public async Task SetAsync<T>(string key, T state, CancellationToken cancellationToken)
{
string json = JsonSerializer.Serialize(state);
await _db.StringSetAsync(key, json);
}
/// <inheritdoc/>
public async Task<T?> GetAsync<T>(string key, CancellationToken cancellationToken)
{
RedisValue json = await _db.StringGetAsync(key);
string? jsonStr = json;
if (jsonStr is null)
return default;
return JsonSerializer.Deserialize<T?>(json: jsonStr);
}
/// <inheritdoc/>
public async Task DeleteAsync(string key, CancellationToken cancellationToken = default)
{
await _db.KeyDeleteAsync(key);
}
}
@@ -0,0 +1,43 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.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 // Building proxy application
_innerApp = webApplicationBuilder.Build(); _innerApp = webApplicationBuilder.Build();
_innerApp.UseTelegratorWeb();
// Reruesting services for this host // Reruesting services for this host
_updateRouter = Services.GetRequiredService<IUpdateRouter>(); _updateRouter = Services.GetRequiredService<IUpdateRouter>();
@@ -1,6 +1,7 @@
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Diagnostics.Metrics;
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Telegrator.Core; using Telegrator.Core;
@@ -11,7 +12,7 @@ namespace Telegrator.Hosting.Web
/// <summary> /// <summary>
/// Represents a web hosted telegram bots and services builder that helps manage configuration, logging, lifetime, and more. /// Represents a web hosted telegram bots and services builder that helps manage configuration, logging, lifetime, and more.
/// </summary> /// </summary>
public class TelegramBotWebHostBuilder : ITelegramBotHostBuilder public class TelegramBotWebHostBuilder : IHostApplicationBuilder, ICollectingProvider
{ {
private readonly WebApplicationBuilder _innerBuilder; private readonly WebApplicationBuilder _innerBuilder;
private readonly WebApplicationOptions _settings; private readonly WebApplicationOptions _settings;
@@ -32,17 +33,37 @@ namespace Telegrator.Hosting.Web
/// <inheritdoc/> /// <inheritdoc/>
public IHostEnvironment Environment => _innerBuilder.Environment; public IHostEnvironment Environment => _innerBuilder.Environment;
/// <inheritdoc/>
public IDictionary<object, object> Properties => ((IHostApplicationBuilder)_innerBuilder).Properties;
/// <inheritdoc/>
public IMetricsBuilder Metrics => _innerBuilder.Metrics;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="TelegramBotWebHostBuilder"/> class. /// Initializes a new instance of the <see cref="TelegramBotWebHostBuilder"/> class.
/// </summary> /// </summary>
/// <param name="webApplicationBuilder"></param> /// <param name="webApplicationBuilder"></param>
/// <param name="settings"></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)); _innerBuilder = webApplicationBuilder ?? throw new ArgumentNullException(nameof(webApplicationBuilder));
_settings = settings ?? throw new ArgumentNullException(nameof(settings)); _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> /// <summary>
@@ -56,7 +77,22 @@ namespace Telegrator.Hosting.Web
_innerBuilder = webApplicationBuilder ?? throw new ArgumentNullException(nameof(webApplicationBuilder)); _innerBuilder = webApplicationBuilder ?? throw new ArgumentNullException(nameof(webApplicationBuilder));
_settings = settings ?? throw new ArgumentNullException(nameof(settings)); _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> /// <summary>
@@ -69,5 +105,11 @@ namespace Telegrator.Hosting.Web
host.UseTelegrator(); host.UseTelegrator();
return host; 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.AspNetCore.Builder;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting;
using Telegrator.Hosting; using Telegrator.Hosting;
using Telegrator.Hosting.Web; using Telegrator.Hosting.Web;
@@ -43,10 +44,14 @@ internal class Program
public static void TelegramBotHostBuilder_Example(string[] args) public static void TelegramBotHostBuilder_Example(string[] args)
{ {
ConfigurationManager configuration = new ConfigurationManager();
configuration.AddJsonFile("appsettings.json");
TelegramBotHostBuilder builder = TelegramBotHost.CreateBuilder(new HostApplicationBuilderSettings() TelegramBotHostBuilder builder = TelegramBotHost.CreateBuilder(new HostApplicationBuilderSettings()
{ {
Args = args, Args = args,
ApplicationName = "TelegramBotHost example", ApplicationName = "TelegramBotHost example",
Configuration = configuration
}); });
builder.Handlers.CollectHandlersAssemblyWide(); builder.Handlers.CollectHandlersAssemblyWide();
@@ -15,7 +15,7 @@
<EnforceCodeStyleInBuild>True</EnforceCodeStyleInBuild> <EnforceCodeStyleInBuild>True</EnforceCodeStyleInBuild>
<Title>Telegrator.Hosting.Web</Title> <Title>Telegrator.Hosting.Web</Title>
<Version>1.16.1</Version> <Version>1.16.4</Version>
<Authors>Rikitav Tim4ik</Authors> <Authors>Rikitav Tim4ik</Authors>
<Company>Rikitav Tim4ik</Company> <Company>Rikitav Tim4ik</Company>
<RepositoryUrl>https://github.com/Rikitav/Telegrator</RepositoryUrl> <RepositoryUrl>https://github.com/Rikitav/Telegrator</RepositoryUrl>
@@ -7,6 +7,7 @@ using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using Telegram.Bot; using Telegram.Bot;
using Telegrator.Core; using Telegrator.Core;
using Telegrator.Hosting;
using Telegrator.Hosting.Web; using Telegrator.Hosting.Web;
using Telegrator.Mediation; using Telegrator.Mediation;
using Telegrator.Providers; using Telegrator.Providers;
@@ -12,7 +12,7 @@ namespace Telegrator.Hosting
/// <param name="client"></param> /// <param name="client"></param>
/// <param name="services"></param> /// <param name="services"></param>
/// <param name="configuration"></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/> /// <inheritdoc/>
public User User { get; } = client.GetMe().Result; public User User { get; } = client.GetMe().Result;
@@ -25,6 +25,6 @@ namespace Telegrator.Hosting
/// <summary> /// <summary>
/// Provides access to configuration of this Hosted telegram bot /// Provides access to configuration of this Hosted telegram bot
/// </summary> /// </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;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Telegrator.Core; using Telegrator.Core;
@@ -12,14 +11,13 @@ namespace Telegrator.Hosting
public class TelegramBotHost : IHost, ITelegratorBot public class TelegramBotHost : IHost, ITelegratorBot
{ {
private readonly IHost _innerHost; private readonly IHost _innerHost;
private readonly IServiceProvider _serviceProvider;
private readonly IUpdateRouter _updateRouter; private readonly IUpdateRouter _updateRouter;
private readonly ILogger<TelegramBotHost> _logger; private readonly ILogger<TelegramBotHost> _logger;
private bool _disposed; private bool _disposed;
/// <inheritdoc/> /// <inheritdoc/>
public IServiceProvider Services => _serviceProvider; public IServiceProvider Services => _innerHost.Services;
/// <inheritdoc/> /// <inheritdoc/>
public IUpdateRouter UpdateRouter => _updateRouter; public IUpdateRouter UpdateRouter => _updateRouter;
@@ -40,8 +38,6 @@ namespace Telegrator.Hosting
// Building proxy hoster // Building proxy hoster
_innerHost = hostApplicationBuilder.Build(); _innerHost = hostApplicationBuilder.Build();
_serviceProvider = _innerHost.Services;
_innerHost.UseTelegrator();
// Reruesting services for this host // Reruesting services for this host
_updateRouter = Services.GetRequiredService<IUpdateRouter>(); _updateRouter = Services.GetRequiredService<IUpdateRouter>();
@@ -56,9 +52,6 @@ namespace Telegrator.Hosting
{ {
HostApplicationBuilder innerBuilder = new HostApplicationBuilder(settings: null); HostApplicationBuilder innerBuilder = new HostApplicationBuilder(settings: null);
TelegramBotHostBuilder builder = new TelegramBotHostBuilder(innerBuilder, null); TelegramBotHostBuilder builder = new TelegramBotHostBuilder(innerBuilder, null);
builder.Services.AddTelegramBotHostDefaults();
builder.Services.AddTelegramReceiver();
return builder; return builder;
} }
@@ -70,9 +63,6 @@ namespace Telegrator.Hosting
{ {
HostApplicationBuilder innerBuilder = new HostApplicationBuilder(settings); HostApplicationBuilder innerBuilder = new HostApplicationBuilder(settings);
TelegramBotHostBuilder builder = new TelegramBotHostBuilder(innerBuilder, settings); TelegramBotHostBuilder builder = new TelegramBotHostBuilder(innerBuilder, settings);
builder.Services.AddTelegramBotHostDefaults();
builder.Services.AddTelegramReceiver();
return builder; return builder;
} }
@@ -1,7 +1,9 @@
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Diagnostics.Metrics;
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Telegrator.Core; using Telegrator.Core;
using Telegrator.Providers; using Telegrator.Providers;
@@ -11,7 +13,7 @@ namespace Telegrator.Hosting
/// <summary> /// <summary>
/// Represents a hosted telegram bots and services builder that helps manage configuration, logging, lifetime, and more. /// Represents a hosted telegram bots and services builder that helps manage configuration, logging, lifetime, and more.
/// </summary> /// </summary>
public class TelegramBotHostBuilder : ICollectingProvider public class TelegramBotHostBuilder : IHostApplicationBuilder, ICollectingProvider
{ {
private readonly HostApplicationBuilder _innerBuilder; private readonly HostApplicationBuilder _innerBuilder;
private readonly HostApplicationBuilderSettings _settings; private readonly HostApplicationBuilderSettings _settings;
@@ -32,6 +34,12 @@ namespace Telegrator.Hosting
/// <inheritdoc/> /// <inheritdoc/>
public IHostEnvironment Environment => _innerBuilder.Environment; public IHostEnvironment Environment => _innerBuilder.Environment;
/// <inheritdoc/>
public IDictionary<object, object> Properties => ((IHostApplicationBuilder)_innerBuilder).Properties;
/// <inheritdoc/>
public IMetricsBuilder Metrics => _innerBuilder.Metrics;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="TelegramBotHostBuilder"/> class. /// Initializes a new instance of the <see cref="TelegramBotHostBuilder"/> class.
/// </summary> /// </summary>
@@ -42,8 +50,21 @@ namespace Telegrator.Hosting
_innerBuilder = hostApplicationBuilder ?? throw new ArgumentNullException(nameof(hostApplicationBuilder)); _innerBuilder = hostApplicationBuilder ?? throw new ArgumentNullException(nameof(hostApplicationBuilder));
_settings = settings ?? new HostApplicationBuilderSettings(); _settings = settings ?? new HostApplicationBuilderSettings();
_innerBuilder.AddTelegrator(); this.AddTelegrator();
_innerBuilder.Logging.ClearProviders(); }
/// <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> /// <summary>
@@ -52,13 +73,27 @@ namespace Telegrator.Hosting
/// <param name="hostApplicationBuilder"></param> /// <param name="hostApplicationBuilder"></param>
/// <param name="handlers"></param> /// <param name="handlers"></param>
/// <param name="settings"></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)); _innerBuilder = hostApplicationBuilder ?? throw new ArgumentNullException(nameof(hostApplicationBuilder));
_settings = settings ?? new HostApplicationBuilderSettings(); _settings = settings ?? new HostApplicationBuilderSettings();
_innerBuilder.AddTelegrator(null, handlers); this.AddTelegrator(null, handlers);
_innerBuilder.Logging.ClearProviders(); }
/// <summary>
/// Initializes a new instance of the <see cref="TelegramBotHostBuilder"/> class.
/// </summary>
/// <param name="hostApplicationBuilder"></param>
/// <param name="handlers"></param>
/// <param name="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> /// <summary>
@@ -71,5 +106,11 @@ namespace Telegrator.Hosting
host.UseTelegrator(); host.UseTelegrator();
return host; 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.Polling;
using Telegram.Bot.Types; using Telegram.Bot.Types;
using Telegrator.Core; using Telegrator.Core;
using Telegrator.Core.States;
using Telegrator.Mediation; using Telegrator.Mediation;
namespace Telegrator.Polling namespace Telegrator.Polling
@@ -20,10 +21,10 @@ namespace Telegrator.Polling
public HostUpdateRouter( public HostUpdateRouter(
IHandlersProvider handlersProvider, IHandlersProvider handlersProvider,
IAwaitingProvider awaitingProvider, IAwaitingProvider awaitingProvider,
IStateStorage stateStorage,
IOptions<TelegratorOptions> options, IOptions<TelegratorOptions> options,
IUpdateHandlersPool handlersPool,
ITelegramBotInfo botInfo, ITelegramBotInfo botInfo,
ILogger<HostUpdateRouter> logger) : base(handlersProvider, awaitingProvider, options.Value, handlersPool, botInfo) ILogger<HostUpdateRouter> logger) : base(handlersProvider, awaitingProvider, stateStorage, options.Value, botInfo)
{ {
Logger = logger; Logger = logger;
ExceptionHandler = new DefaultRouterExceptionHandler(HandleException); ExceptionHandler = new DefaultRouterExceptionHandler(HandleException);
@@ -15,7 +15,7 @@
<EnforceCodeStyleInBuild>True</EnforceCodeStyleInBuild> <EnforceCodeStyleInBuild>True</EnforceCodeStyleInBuild>
<Title>Telegrator.Hosting</Title> <Title>Telegrator.Hosting</Title>
<Version>1.16.1</Version> <Version>1.16.4</Version>
<Authors>Rikitav Tim4ik</Authors> <Authors>Rikitav Tim4ik</Authors>
<Company>Rikitav Tim4ik</Company> <Company>Rikitav Tim4ik</Company>
<RepositoryUrl>https://github.com/Rikitav/Telegrator</RepositoryUrl> <RepositoryUrl>https://github.com/Rikitav/Telegrator</RepositoryUrl>
@@ -120,7 +120,6 @@ public static class ServicesCollectionExtensions
public static IServiceCollection AddTelegramBotHostDefaults(this IServiceCollection services) public static IServiceCollection AddTelegramBotHostDefaults(this IServiceCollection services)
{ {
services.AddLogging(builder => builder.AddConsole().AddDebug()); services.AddLogging(builder => builder.AddConsole().AddDebug());
services.AddSingleton<IUpdateHandlersPool, HostUpdateHandlersPool>();
services.AddSingleton<IAwaitingProvider, HostAwaitingProvider>(); services.AddSingleton<IAwaitingProvider, HostAwaitingProvider>();
services.AddSingleton<IHandlersProvider, HostHandlersProvider>(); services.AddSingleton<IHandlersProvider, HostHandlersProvider>();
services.AddSingleton<IUpdateRouter, HostUpdateRouter>(); services.AddSingleton<IUpdateRouter, HostUpdateRouter>();
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework> <TargetFramework>netstandard2.1</TargetFramework>
<LangVersion>latest</LangVersion> <LangVersion>latest</LangVersion>
</PropertyGroup> </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 Telegram.Bot.Types.Enums;
using Telegrator.Filters; using Telegrator.Filters;
namespace Telegrator.Annotations.Targetted namespace Telegrator.Annotations
{ {
/// <summary> /// <summary>
/// Attribute for filtering message with command "start" in bot's private chats. /// 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) public MethodHandlerDescriptor(AbstractHandlerAction<TUpdate> action) : base(DescriptorType.General, typeof(MethodHandler), true)
{ {
UpdateHandlerAttributeBase handlerAttribute = HandlerInspector.GetHandlerAttribute(action.Method); 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(); IFilter<Update>[] filters = HandlerInspector.GetFilterAttributes(action.Method, handlerAttribute.Type).ToArray();
UpdateType = handlerAttribute.Type; UpdateType = handlerAttribute.Type;
@@ -2,6 +2,7 @@
using Telegram.Bot.Types; using Telegram.Bot.Types;
using Telegrator.Core.Filters; using Telegrator.Core.Filters;
using Telegrator.Core.Handlers; using Telegrator.Core.Handlers;
using Telegrator.Core.States;
namespace Telegrator.Core.Descriptors namespace Telegrator.Core.Descriptors
{ {
@@ -26,6 +27,11 @@ namespace Telegrator.Core.Descriptors
/// The awaiting provider to fetch new updates inside handler /// The awaiting provider to fetch new updates inside handler
/// </summary> /// </summary>
public IAwaitingProvider AwaitingProvider { get; } public IAwaitingProvider AwaitingProvider { get; }
/// <summary>
/// The state storage to handling state machines
/// </summary>
public IStateStorage StateStorage { get; }
/// <summary> /// <summary>
/// The Telegram bot client used for this handler. /// The Telegram bot client used for this handler.
@@ -73,6 +79,7 @@ namespace Telegrator.Core.Descriptors
/// <param name="fromDescriptor">The descriptor from which this handler was described.</param> /// <param name="fromDescriptor">The descriptor from which this handler was described.</param>
/// <param name="updateRouter">The update router.</param> /// <param name="updateRouter">The update router.</param>
/// <param name="awaitingProvider">The awaiting provider.</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="client">The Telegram bot client.</param>
/// <param name="handlerInstance">The handler instance.</param> /// <param name="handlerInstance">The handler instance.</param>
/// <param name="filterContext">The filter execution context.</param> /// <param name="filterContext">The filter execution context.</param>
@@ -81,6 +88,7 @@ namespace Telegrator.Core.Descriptors
HandlerDescriptor fromDescriptor, HandlerDescriptor fromDescriptor,
IUpdateRouter updateRouter, IUpdateRouter updateRouter,
IAwaitingProvider awaitingProvider, IAwaitingProvider awaitingProvider,
IStateStorage stateStorage,
ITelegramBotClient client, ITelegramBotClient client,
UpdateHandlerBase handlerInstance, UpdateHandlerBase handlerInstance,
FilterExecutionContext<Update> filterContext, FilterExecutionContext<Update> filterContext,
@@ -89,6 +97,7 @@ namespace Telegrator.Core.Descriptors
From = fromDescriptor; From = fromDescriptor;
UpdateRouter = updateRouter; UpdateRouter = updateRouter;
AwaitingProvider = awaitingProvider; AwaitingProvider = awaitingProvider;
StateStorage = stateStorage;
Client = client; Client = client;
HandlerInstance = handlerInstance; HandlerInstance = handlerInstance;
ExtraData = filterContext.Data; ExtraData = filterContext.Data;
@@ -114,7 +123,7 @@ namespace Telegrator.Core.Descriptors
/// <param name="result">The execution result.</param> /// <param name="result">The execution result.</param>
public void ReportResult(Result? result) public void ReportResult(Result? result)
{ {
if (result != null) if (Result != null)
throw new InvalidOperationException("Result already reported"); throw new InvalidOperationException("Result already reported");
Result = result; Result = result;
@@ -167,7 +167,7 @@ namespace Telegrator.Core.Descriptors
if (handlerAttribute.ExpectingHandlerType != null && !handlerAttribute.ExpectingHandlerType.Contains(handlerType.BaseType)) 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()))); 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(); IFilter<Update>[] filters = HandlerInspector.GetFilterAttributes(handlerType, handlerAttribute.Type).ToArray();
UpdateType = handlerAttribute.Type; UpdateType = handlerAttribute.Type;
@@ -2,6 +2,7 @@
using System.Reflection; using System.Reflection;
using Telegram.Bot.Types; using Telegram.Bot.Types;
using Telegram.Bot.Types.Enums; using Telegram.Bot.Types.Enums;
using Telegrator.Annotations;
using Telegrator.Aspects; using Telegrator.Aspects;
using Telegrator.Core.Attributes; using Telegrator.Core.Attributes;
using Telegrator.Core.Filters; using Telegrator.Core.Filters;
@@ -42,13 +43,13 @@ namespace Telegrator.Core.Descriptors
/// </summary> /// </summary>
/// <param name="handlerType">The member info representing the handler type.</param> /// <param name="handlerType">The member info representing the handler type.</param>
/// <returns>The state keeper attribute, or null if not present.</returns> /// <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 // 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> /// <summary>
@@ -9,6 +9,11 @@ namespace Telegrator.Core.Filters
/// <typeparam name="T">The type of the input for the filter.</typeparam> /// <typeparam name="T">The type of the input for the filter.</typeparam>
public class FilterExecutionContext<T> where T : class public class FilterExecutionContext<T> where T : class
{ {
/// <summary>
/// Gets the <see cref="ITelegramBotInfo"/> for the current context.
/// </summary>
public IUpdateRouter UpdateRouter { get; }
/// <summary> /// <summary>
/// Gets the <see cref="ITelegramBotInfo"/> for the current context. /// Gets the <see cref="ITelegramBotInfo"/> for the current context.
/// </summary> /// </summary>
@@ -42,13 +47,15 @@ namespace Telegrator.Core.Filters
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="FilterExecutionContext{T}"/> class with all parameters. /// Initializes a new instance of the <see cref="FilterExecutionContext{T}"/> class with all parameters.
/// </summary> /// </summary>
/// <param name="router">The router, that invoked filter.</param>
/// <param name="botInfo">The bot info.</param> /// <param name="botInfo">The bot info.</param>
/// <param name="update">The update.</param> /// <param name="update">The update.</param>
/// <param name="input">The input object.</param> /// <param name="input">The input object.</param>
/// <param name="data">The additional data dictionary.</param> /// <param name="data">The additional data dictionary.</param>
/// <param name="completedFilters">The list of completed filters.</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; BotInfo = botInfo;
Data = data; Data = data;
CompletedFilters = completedFilters; CompletedFilters = completedFilters;
@@ -60,11 +67,12 @@ namespace Telegrator.Core.Filters
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="FilterExecutionContext{T}"/> class with default data and filters. /// Initializes a new instance of the <see cref="FilterExecutionContext{T}"/> class with default data and filters.
/// </summary> /// </summary>
/// <param name="router">The router, that invoked filter.</param>
/// <param name="botInfo">The bot info.</param> /// <param name="botInfo">The bot info.</param>
/// <param name="update">The update.</param> /// <param name="update">The update.</param>
/// <param name="input">The input object.</param> /// <param name="input">The input object.</param>
public FilterExecutionContext(ITelegramBotInfo botInfo, Update update, T input) public FilterExecutionContext(IUpdateRouter router, ITelegramBotInfo botInfo, Update update, T input)
: this(botInfo, update, input, [], []) { } : this(router, botInfo, update, input, [], []) { }
/// <summary> /// <summary>
/// Creates a child context for a different input type, sharing the same data and completed filters. /// 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> /// <param name="input">The new input object.</param>
/// <returns>A new <see cref="FilterExecutionContext{C}"/> instance.</returns> /// <returns>A new <see cref="FilterExecutionContext{C}"/> instance.</returns>
public FilterExecutionContext<C> CreateChild<C>(C input) where C : class 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 Telegram.Bot.Types.Enums;
using Telegrator.Core.Descriptors; using Telegrator.Core.Descriptors;
using Telegrator.Core.Filters; using Telegrator.Core.Filters;
using Telegrator.Core.States;
using Telegrator.Handlers; using Telegrator.Handlers;
namespace Telegrator.Core.Handlers namespace Telegrator.Core.Handlers
@@ -47,6 +48,11 @@ namespace Telegrator.Core.Handlers
/// </summary> /// </summary>
protected IAwaitingProvider AwaitingProvider => Container.AwaitingProvider; protected IAwaitingProvider AwaitingProvider => Container.AwaitingProvider;
/// <summary>
/// Storage of bot states.
/// </summary>
protected IStateStorage StateStorage => Container.StateStorage;
/// <summary> /// <summary>
/// Initializes a new instance and checks that the update type matches <typeparamref name="TUpdate"/>. /// Initializes a new instance and checks that the update type matches <typeparamref name="TUpdate"/>.
/// </summary> /// </summary>
@@ -1,9 +1,9 @@
using Telegram.Bot.Types; using Telegram.Bot.Types;
using Telegram.Bot.Types.Enums; using Telegram.Bot.Types.Enums;
using Telegrator.Annotations.StateKeeping;
using Telegrator.Core.Descriptors; using Telegrator.Core.Descriptors;
using Telegrator.Core.Filters; using Telegrator.Core.Filters;
using Telegrator.Core.StateKeeping; using Telegrator.Core.States;
using Telegrator.Filters;
namespace Telegrator.Core.Handlers.Building namespace Telegrator.Core.Handlers.Building
{ {
@@ -140,35 +140,15 @@ namespace Telegrator.Core.Handlers.Building
/// <summary> /// <summary>
/// Sets a state keeper for the handler using a specific state and key resolver. /// Sets a state keeper for the handler using a specific state and key resolver.
/// </summary> /// </summary>
/// <typeparam name="TKey">The type of the key.</typeparam> /// <typeparam name="TKey">The key resolver.</typeparam>
/// <typeparam name="TState">The type of the state.</typeparam> /// <typeparam name="TValue">The state value.</typeparam>
/// <typeparam name="TKeeper">The type of the state keeper.</typeparam> /// <param name="state">The state value.</param>
/// <param name="myState">The state value.</param>
/// <param name="keyResolver">The key resolver.</param>
/// <returns>The builder instance.</returns> /// <returns>The builder instance.</returns>
public void SetStateKeeper<TKey, TState, TKeeper>(TState myState, IStateKeyResolver<TKey> keyResolver) public void SetState<TKey, TValue>(TValue? state)
where TKey : notnull where TKey : IStateKeyResolver, new()
where TState : IEquatable<TState> where TValue : IEquatable<TValue>
where TKeeper : StateKeeperBase<TKey, TState>, new()
{ {
StateKeeper = new StateKeepFilter<TKey, TState, TKeeper>(myState, keyResolver); StateKeeper = new StateKeyFilter<TKey, TValue>(state);
}
/// <summary>
/// Sets a state keeper for the handler using a special state and key resolver.
/// </summary>
/// <typeparam name="TKey">The type of the key.</typeparam>
/// <typeparam name="TState">The type of the state.</typeparam>
/// <typeparam name="TKeeper">The type of the state keeper.</typeparam>
/// <param name="specialState">The special state value.</param>
/// <param name="keyResolver">The key resolver.</param>
/// <returns>The builder instance.</returns>
public void SetStateKeeper<TKey, TState, TKeeper>(SpecialState specialState, IStateKeyResolver<TKey> keyResolver)
where TKey : notnull
where TState : IEquatable<TState>
where TKeeper : StateKeeperBase<TKey, TState>, new()
{
StateKeeper = new StateKeepFilter<TKey, TState, TKeeper>(specialState, keyResolver);
} }
/// <summary> /// <summary>
@@ -1,4 +1,4 @@
using Telegrator.Core.StateKeeping; using Telegrator.Core.States;
namespace Telegrator.Core.Handlers.Building namespace Telegrator.Core.Handlers.Building
{ {
@@ -11,9 +11,9 @@ namespace Telegrator.Core.Handlers.Building
/// <summary> /// <summary>
/// Awaits an update using the specified key resolver and cancellation token. /// Awaits an update using the specified key resolver and cancellation token.
/// </summary> /// </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> /// <param name="cancellationToken">The cancellation token.</param>
/// <returns>A <see cref="Task{TUpdate}"/> representing the awaited update.</returns> /// <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 Telegram.Bot.Types;
using Telegrator.Annotations.StateKeeping;
using Telegrator.Core.Filters; using Telegrator.Core.Filters;
using Telegrator.Core.StateKeeping; using Telegrator.Core.States;
namespace Telegrator.Core.Handlers.Building namespace Telegrator.Core.Handlers.Building
{ {
@@ -56,30 +55,13 @@ namespace Telegrator.Core.Handlers.Building
/// <summary> /// <summary>
/// Sets a state keeper for the handler using a specific state and key resolver. /// Sets a state keeper for the handler using a specific state and key resolver.
/// </summary> /// </summary>
/// <typeparam name="TKey">The type of the key.</typeparam> /// <typeparam name="TKey">The key resolver.</typeparam>
/// <typeparam name="TState">The type of the state.</typeparam> /// <typeparam name="TValue">The state value.</typeparam>
/// <typeparam name="TKeeper">The type of the state keeper.</typeparam> /// <param name="state">The state value.</param>
/// <param name="myState">The state value.</param>
/// <param name="keyResolver">The key resolver.</param>
/// <returns>The builder instance.</returns> /// <returns>The builder instance.</returns>
public void SetStateKeeper<TKey, TState, TKeeper>(TState myState, IStateKeyResolver<TKey> keyResolver) public void SetState<TKey, TValue>(TValue? state)
where TKey : notnull where TKey : IStateKeyResolver, new()
where TState : IEquatable<TState> where TValue : IEquatable<TValue>;
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();
/// <summary> /// <summary>
/// Adds a targeted filter for a specific filter target type. /// 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;
using Telegram.Bot.Types; using Telegram.Bot.Types;
using Telegrator.Core.Filters; using Telegrator.Core.Filters;
using Telegrator.Core.States;
namespace Telegrator.Core.Handlers namespace Telegrator.Core.Handlers
{ {
@@ -23,5 +24,8 @@ namespace Telegrator.Core.Handlers
/// <inheritdoc/> /// <inheritdoc/>
public IAwaitingProvider AwaitingProvider => throw new NotImplementedException(); public IAwaitingProvider AwaitingProvider => throw new NotImplementedException();
/// <inheritdoc/>
public IStateStorage StateStorage => throw new NotImplementedException();
} }
} }
@@ -1,6 +1,7 @@
using Telegram.Bot; using Telegram.Bot;
using Telegram.Bot.Types; using Telegram.Bot.Types;
using Telegrator.Core.Filters; using Telegrator.Core.Filters;
using Telegrator.Core.States;
namespace Telegrator.Core.Handlers namespace Telegrator.Core.Handlers
{ {
@@ -34,5 +35,10 @@ namespace Telegrator.Core.Handlers
/// Gets the <see cref="IAwaitingProvider"/> for awaiting operations. /// Gets the <see cref="IAwaitingProvider"/> for awaiting operations.
/// </summary> /// </summary>
public IAwaitingProvider AwaitingProvider { get; } 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) if (this is IHandlerContainerFactory handlerDefainedContainerFactory)
return handlerDefainedContainerFactory.CreateContainer(handlerInfo); 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 Telegram.Bot.Polling;
using Telegrator.Core.Handlers; using Telegrator.Core.Handlers;
using Telegrator.Core.States;
namespace Telegrator.Core namespace Telegrator.Core
{ {
@@ -7,7 +8,7 @@ namespace Telegrator.Core
/// Interface for update routers that handle incoming updates and manage handler execution. /// Interface for update routers that handle incoming updates and manage handler execution.
/// Combines update handling capabilities with polling provider functionality and exception handling. /// Combines update handling capabilities with polling provider functionality and exception handling.
/// </summary> /// </summary>
public interface IUpdateRouter : IUpdateHandler, IPollingProvider public interface IUpdateRouter : IUpdateHandler
{ {
/// <summary> /// <summary>
/// Gets the <see cref="TelegratorOptions"/> for the router. /// Gets the <see cref="TelegratorOptions"/> for the router.
@@ -19,6 +20,21 @@ namespace Telegrator.Core
/// </summary> /// </summary>
public IUpdateHandlersPool HandlersPool { get; } 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> /// <summary>
/// Gets or sets the <see cref="IRouterExceptionHandler"/> for handling exceptions. /// Gets or sets the <see cref="IRouterExceptionHandler"/> for handling exceptions.
/// </summary> /// </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 Telegram.Bot.Types;
using Telegrator.Core.Filters; using Telegrator.Core.Filters;
using Telegrator.Core.StateKeeping; using Telegrator.Core.States;
namespace Telegrator.Filters namespace Telegrator.Filters
{ {
/// <summary> /// <summary>
/// Filters updates by comparing a resolved state key with a target key. /// Filters updates by comparing a resolved state key with a target key.
/// </summary> /// </summary>
/// <typeparam name="TKey">The type of the key used for state resolution.</typeparam> /// <typeparam name="TKey">The type of the key resolver used to get state key.</typeparam>
public class StateKeyFilter<TKey> : Filter<Update> where TKey : IEquatable<TKey> /// <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 TValue? TargetKey;
private readonly TKey TargetKey;
/// <summary> /// <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> /// </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> /// <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; TargetKey = targetKey;
} }
/// <inheritdoc/> /// <inheritdoc/>
public override bool CanPass(FilterExecutionContext<Update> context) 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;
using Telegram.Bot.Types.Enums; using Telegram.Bot.Types.Enums;
using Telegrator.Filters;
using Telegrator.StateKeeping;
using Telegrator.Core; using Telegrator.Core;
using Telegrator.Core.Handlers.Building;
using Telegrator.Core.Descriptors; 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 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="keyResolver">The state key resolver to use for filtering updates.</param>
/// <param name="cancellationToken">The cancellation token to cancel the wait operation.</param> /// <param name="cancellationToken">The cancellation token to cancel the wait operation.</param>
/// <returns>The awaited update of type TUpdate.</returns> /// <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); AwaiterHandler handlerInstance = new AwaiterHandler(UpdateType);
HandlerDescriptor descriptor = BuildImplicitDescriptor(handlerInstance); HandlerDescriptor descriptor = BuildImplicitDescriptor(handlerInstance);
@@ -1,9 +1,7 @@
using Telegram.Bot.Types; using Telegram.Bot.Types;
using Telegrator.Annotations.StateKeeping;
using Telegrator.Core.Filters; using Telegrator.Core.Filters;
using Telegrator.Core.Handlers.Building; using Telegrator.Core.Handlers.Building;
using Telegrator.Core.StateKeeping; using Telegrator.Core.States;
using Telegrator.StateKeeping;
namespace Telegrator.Handlers.Building namespace Telegrator.Handlers.Building
{ {
@@ -61,25 +59,13 @@ namespace Telegrator.Handlers.Building
return handlerBuilder; return handlerBuilder;
} }
/// <inheritdoc cref="HandlerBuilderBase.SetStateKeeper{TKey, TState, TKeeper}(TState, IStateKeyResolver{TKey})"/> /// <inheritdoc cref="HandlerBuilderBase.SetState{TKey, TValue}(TValue?)"/>
public static TBuilder SetStateKeeper<TBuilder, TKey, TState, TKeeper>(this TBuilder handlerBuilder, TState myState, IStateKeyResolver<TKey> keyResolver) public static TBuilder SetState<TBuilder, TKey, TValue>(this TBuilder handlerBuilder, TValue? myState)
where TBuilder : HandlerBuilderBase where TBuilder : HandlerBuilderBase
where TKey : notnull where TKey : IStateKeyResolver, new()
where TState : IEquatable<TState> where TValue : IEquatable<TValue>
where TKeeper : StateKeeperBase<TKey, TState>, new()
{ {
handlerBuilder.SetStateKeeper<TKey, TState, TKeeper>(myState, keyResolver); handlerBuilder.SetState<TKey, TValue>(myState);
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);
return handlerBuilder; return handlerBuilder;
} }
@@ -116,129 +102,5 @@ namespace Telegrator.Handlers.Building
handlerBuilder.AddTargetedFilters(getFilterringTarget, filters); handlerBuilder.AddTargetedFilters(getFilterringTarget, filters);
return handlerBuilder; 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('@'); string[] split = ReceivedCommand.Split('@');
ReceivedCommand = split[0]; ReceivedCommand = split[0];
if (!split.ElementAtOrDefault(1).Equals(context.BotInfo.User.Username))
return false;
} }
return true; return true;
+12 -5
View File
@@ -3,6 +3,7 @@ using Telegram.Bot.Types;
using Telegrator.Core; using Telegrator.Core;
using Telegrator.Core.Descriptors; using Telegrator.Core.Descriptors;
using Telegrator.Core.Filters; using Telegrator.Core.Filters;
using Telegrator.Core.States;
namespace Telegrator.Handlers namespace Telegrator.Handlers
{ {
@@ -33,6 +34,9 @@ namespace Telegrator.Handlers
/// <inheritdoc/> /// <inheritdoc/>
public IAwaitingProvider AwaitingProvider { get; } public IAwaitingProvider AwaitingProvider { get; }
/// <inheritdoc/>
public IStateStorage StateStorage { get; }
/// <summary> /// <summary>
/// Initializes new instance of <see cref="HandlerContainer{TUpdate}"/> /// Initializes new instance of <see cref="HandlerContainer{TUpdate}"/>
/// </summary> /// </summary>
@@ -45,6 +49,7 @@ namespace Telegrator.Handlers
ExtraData = handlerInfo.ExtraData; ExtraData = handlerInfo.ExtraData;
CompletedFilters = handlerInfo.CompletedFilters; CompletedFilters = handlerInfo.CompletedFilters;
AwaitingProvider = handlerInfo.AwaitingProvider; AwaitingProvider = handlerInfo.AwaitingProvider;
StateStorage = handlerInfo.StateStorage;
} }
/// <summary> /// <summary>
@@ -56,7 +61,8 @@ namespace Telegrator.Handlers
/// <param name="extraData"></param> /// <param name="extraData"></param>
/// <param name="filters"></param> /// <param name="filters"></param>
/// <param name="awaitingProvider"></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; ActualUpdate = actualUpdate;
HandlingUpdate = handlingUpdate; HandlingUpdate = handlingUpdate;
@@ -64,6 +70,7 @@ namespace Telegrator.Handlers
ExtraData = extraData; ExtraData = extraData;
CompletedFilters = filters; CompletedFilters = filters;
AwaitingProvider = awaitingProvider; AwaitingProvider = awaitingProvider;
StateStorage = stateStorage;
} }
/// <summary> /// <summary>
@@ -75,8 +82,8 @@ namespace Telegrator.Handlers
{ {
return new HandlerContainer<QUpdate>( return new HandlerContainer<QUpdate>(
HandlingUpdate.GetActualUpdateObject<QUpdate>(), HandlingUpdate.GetActualUpdateObject<QUpdate>(),
HandlingUpdate, Client, ExtraData, HandlingUpdate, Client, ExtraData, CompletedFilters,
CompletedFilters, AwaitingProvider); AwaitingProvider, StateStorage);
} }
/// <summary> /// <summary>
@@ -89,8 +96,8 @@ namespace Telegrator.Handlers
{ {
return new HandlerContainer<TUpdate>( return new HandlerContainer<TUpdate>(
other.HandlingUpdate.GetActualUpdateObject<TUpdate>(), other.HandlingUpdate.GetActualUpdateObject<TUpdate>(),
other.HandlingUpdate, other.Client, other.ExtraData, other.HandlingUpdate, other.Client, other.ExtraData, other.CompletedFilters,
other.CompletedFilters, other.AwaitingProvider); other.AwaitingProvider, other.StateStorage);
} }
} }
} }
@@ -28,7 +28,7 @@ namespace Telegrator.Mediation
{ {
_handler.Invoke(botClient, exception, source, cancellationToken); _handler.Invoke(botClient, exception, source, cancellationToken);
} }
finally catch
{ {
_ = 0xBAD + 0xC0DE; _ = 0xBAD + 0xC0DE;
} }
+10 -20
View File
@@ -7,6 +7,7 @@ using Telegrator.Core;
using Telegrator.Core.Descriptors; using Telegrator.Core.Descriptors;
using Telegrator.Core.Filters; using Telegrator.Core.Filters;
using Telegrator.Core.Handlers; using Telegrator.Core.Handlers;
using Telegrator.Core.States;
using Telegrator.Handlers.Diagnostics; using Telegrator.Handlers.Diagnostics;
using Telegrator.Logging; using Telegrator.Logging;
@@ -21,6 +22,7 @@ namespace Telegrator.Mediation
private readonly TelegratorOptions _options; private readonly TelegratorOptions _options;
private readonly IHandlersProvider _handlersProvider; private readonly IHandlersProvider _handlersProvider;
private readonly IAwaitingProvider _awaitingProvider; private readonly IAwaitingProvider _awaitingProvider;
private readonly IStateStorage _stateStorage;
private readonly IUpdateHandlersPool _HandlersPool; private readonly IUpdateHandlersPool _HandlersPool;
private readonly ITelegramBotInfo _botInfo; private readonly ITelegramBotInfo _botInfo;
@@ -30,6 +32,9 @@ namespace Telegrator.Mediation
/// <inheritdoc/> /// <inheritdoc/>
public IAwaitingProvider AwaitingProvider => _awaitingProvider; public IAwaitingProvider AwaitingProvider => _awaitingProvider;
/// <inheritdoc/>
public IStateStorage StateStorage => _stateStorage;
/// <inheritdoc/> /// <inheritdoc/>
public TelegratorOptions Options => _options; public TelegratorOptions Options => _options;
@@ -47,34 +52,19 @@ namespace Telegrator.Mediation
/// </summary> /// </summary>
/// <param name="handlersProvider">The provider for regular handlers.</param> /// <param name="handlersProvider">The provider for regular handlers.</param>
/// <param name="awaitingProvider">The provider for awaiting 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="options">The bot configuration options.</param>
/// <param name="botInfo"></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; _options = options;
_handlersProvider = handlersProvider; _handlersProvider = handlersProvider;
_awaitingProvider = awaitingProvider; _awaitingProvider = awaitingProvider;
_stateStorage = stateStorage;
_HandlersPool = new UpdateHandlersPool(this, _options, _options.GlobalCancellationToken); _HandlersPool = new UpdateHandlersPool(this, _options, _options.GlobalCancellationToken);
_botInfo = botInfo; _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> /// <summary>
/// Handles errors that occur during update processing. /// Handles errors that occur during update processing.
/// </summary> /// </summary>
@@ -252,7 +242,7 @@ namespace Telegrator.Mediation
}; };
UpdateHandlerBase handlerInstance = provider.GetHandlerInstance(descriptor, cancellationToken); 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) 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> /// <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 Telegram.Bot.Types;
using Telegrator.Core.StateKeeping; using Telegrator.Core.States;
namespace Telegrator.StateKeeping namespace Telegrator.States
{ {
/// <summary> /// <summary>
/// Resolves chat ID from Telegram updates for state management purposes. /// 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. /// Extracts the chat identifier from various types of updates to provide a consistent key for state operations.
/// </summary> /// </summary>
public class ChatIdResolver : IStateKeyResolver<long> public class ChatIdResolver : IStateKeyResolver
{ {
/// <summary> /// <summary>
/// Resolves the chat ID from a Telegram update. /// 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> /// <param name="keySource">The Telegram update to extract the chat ID from.</param>
/// <returns>The chat ID as a long value.</returns> /// <returns>The chat ID as a long value.</returns>
/// <exception cref="ArgumentException">Thrown when the update does not contain a valid chat ID.</exception> /// <exception cref="ArgumentException">Thrown when the update does not contain a valid chat ID.</exception>
public long ResolveKey(Update keySource) public string? ResolveKey(Update keySource)
=> keySource.GetChatId() ?? throw new ArgumentException("Cannot resolve ChatID for this Update"); => 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"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework> <TargetFramework>netstandard2.1</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<LangVersion>latest</LangVersion> <LangVersion>latest</LangVersion>
@@ -14,7 +14,7 @@
<EnforceCodeStyleInBuild>True</EnforceCodeStyleInBuild> <EnforceCodeStyleInBuild>True</EnforceCodeStyleInBuild>
<Title>Telegrator : Telegram.Bot mediator framework</Title> <Title>Telegrator : Telegram.Bot mediator framework</Title>
<Version>1.16.1</Version> <Version>1.16.4</Version>
<Authors>Rikitav Tim4ik</Authors> <Authors>Rikitav Tim4ik</Authors>
<Company>Rikitav Tim4ik</Company> <Company>Rikitav Tim4ik</Company>
<RepositoryUrl>https://github.com/Rikitav/Telegrator</RepositoryUrl> <RepositoryUrl>https://github.com/Rikitav/Telegrator</RepositoryUrl>
@@ -44,4 +44,8 @@
<ProjectReference Include="..\..\dev\Telegrator.RoslynGenerators\Telegrator.RoslynGenerators.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" /> <ProjectReference Include="..\..\dev\Telegrator.RoslynGenerators\Telegrator.RoslynGenerators.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<Folder Include="Annotations\Targetted\" />
</ItemGroup>
</Project> </Project>
+3 -1
View File
@@ -4,6 +4,7 @@ using Telegrator.Core;
using Telegrator.Logging; using Telegrator.Logging;
using Telegrator.Mediation; using Telegrator.Mediation;
using Telegrator.Providers; using Telegrator.Providers;
using Telegrator.States;
namespace Telegrator namespace Telegrator
{ {
@@ -75,8 +76,9 @@ namespace Telegrator
HandlersProvider handlerProvider = new HandlersProvider(Handlers, Options); HandlersProvider handlerProvider = new HandlersProvider(Handlers, Options);
AwaitingProvider awaitingProvider = new AwaitingProvider(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 // Log startup
TelegratorLogging.LogInformation($"Telegrator bot starting up - BotId: {BotInfo.User.Id}, Username: {BotInfo.User.Username}, MaxParallelHandlers: {Options.MaximumParallelWorkingHandlers ?? -1}"); 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.Enums;
using Telegram.Bot.Types.Payments; using Telegram.Bot.Types.Payments;
using Telegrator.Annotations; using Telegrator.Annotations;
using Telegrator.Attributes;
using Telegrator.Core; using Telegrator.Core;
using Telegrator.Core.Descriptors; using Telegrator.Core.Descriptors;
using Telegrator.Core.Handlers; using Telegrator.Core.Handlers;
using Telegrator.Core.Handlers.Building; using Telegrator.Core.Handlers.Building;
using Telegrator.Core.StateKeeping; using Telegrator.Core.States;
using Telegrator.Handlers.Building; using Telegrator.Handlers.Building;
using Telegrator.StateKeeping; using Telegrator.States;
namespace Telegrator namespace Telegrator
{ {
@@ -187,17 +186,6 @@ namespace Telegrator
/// <returns>An awaiter builder for callback query updates.</returns> /// <returns>An awaiter builder for callback query updates.</returns>
public static IAwaiterHandlerBuilder<CallbackQuery> AwaitCallbackQuery(this IHandlerContainer container) public static IAwaiterHandlerBuilder<CallbackQuery> AwaitCallbackQuery(this IHandlerContainer container)
=> container.AwaitUpdate<CallbackQuery>(UpdateType.CallbackQuery); => 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> /// <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> /// <summary>
/// Extension methods for handlers collections. /// Extension methods for handlers collections.
/// Provides convenient methods for creating implicit handlers. /// Provides convenient methods for creating implicit handlers.
@@ -29,7 +29,7 @@ public class FilterTests
{ {
// Arrange (Given) - подготовка тестовых данных // Arrange (Given) - подготовка тестовых данных
var anyFilter = Filter<Update>.Any(); 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) - выполнение тестируемого действия // Act (When) - выполнение тестируемого действия
var result = anyFilter.CanPass(context); var result = anyFilter.CanPass(context);
@@ -49,7 +49,7 @@ public class FilterTests
// Arrange // Arrange
var alwaysTrueFilter = Filter<Update>.Any(); var alwaysTrueFilter = Filter<Update>.Any();
var reverseFilter = alwaysTrueFilter.Not(); 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 // Act
var result = reverseFilter.CanPass(context); var result = reverseFilter.CanPass(context);
@@ -74,7 +74,7 @@ public class FilterTests
var firstFilter = Filter<Update>.If(_ => firstResult); var firstFilter = Filter<Update>.If(_ => firstResult);
var secondFilter = Filter<Update>.If(_ => secondResult); var secondFilter = Filter<Update>.If(_ => secondResult);
var andFilter = firstFilter.And(secondFilter); 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 // Act
var result = andFilter.CanPass(context); var result = andFilter.CanPass(context);
@@ -99,7 +99,7 @@ public class FilterTests
var firstFilter = Filter<Update>.If(_ => firstResult); var firstFilter = Filter<Update>.If(_ => firstResult);
var secondFilter = Filter<Update>.If(_ => secondResult); var secondFilter = Filter<Update>.If(_ => secondResult);
var orFilter = firstFilter.Or(secondFilter); 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 // Act
var result = orFilter.CanPass(context); var result = orFilter.CanPass(context);
@@ -122,7 +122,7 @@ public class FilterTests
var filter3 = Filter<Update>.If(_ => false); var filter3 = Filter<Update>.If(_ => false);
var compiledFilter = new CompiledFilter<Update>(filter1, filter2, filter3); 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 // Act
var result = compiledFilter.CanPass(context); var result = compiledFilter.CanPass(context);
@@ -164,7 +164,7 @@ public class FilterTests
wasCalled = true; wasCalled = true;
return 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 // Act
var result = functionFilter.CanPass(context); var result = functionFilter.CanPass(context);