From ff2060e25095f1534fdea2ed13b08976c51879ee Mon Sep 17 00:00:00 2001 From: Rikitav Date: Sat, 7 Mar 2026 23:22:40 +0400 Subject: [PATCH] incrementedd version --- docs/GETTING_STARTED.md | 719 ++++++++++++++++++ .../Hosting.Web/TelegramBotWebHostBuilder.cs | 4 - .../Telegrator.Hosting.Web.csproj | 15 +- .../Telegrator.Hosting.csproj | 12 +- .../Descriptors/DescribedHandlerDescriptor.cs | 1 - src/Telegrator/Mediation/UpdateRouter.cs | 28 +- src/Telegrator/Telegrator.csproj | 2 +- 7 files changed, 759 insertions(+), 22 deletions(-) create mode 100644 docs/GETTING_STARTED.md diff --git a/docs/GETTING_STARTED.md b/docs/GETTING_STARTED.md new file mode 100644 index 0000000..b593316 --- /dev/null +++ b/docs/GETTING_STARTED.md @@ -0,0 +1,719 @@ +# Getting Started with Telegrator + +--- + +This guide will walk you through the core concepts and advanced features of **Telegrator** — a modern, aspect-oriented, mediator-based framework for building powerful and maintainable Telegram bots in C#. + +- [1. Installation](#1-installation) +- [2. Framework Mechanics Overview](#2-framework-mechanics-overview) + - [2.1. Basic Concepts](#21-basic-concepts) + - [2.2. Practice: Minimal Bot](#22-practice-minimal-bot) + - [2.3. Working with Filters](#23-working-with-filters) + - [2.4. State Management](#24-state-management) + - [2.5. Concurrency & Awaiting](#25-concurrency--awaiting) + - [2.6. Extensibility](#26-extensibility) + - [2.7. Integration](#27-integration) +- [3. Step-by-Step Tutorials](#3-step-by-step-tutorials) + - [3.1. Minimal Bot Creation](#31-minimal-bot-creation) + - [3.2. Command Filtering](#32-command-filtering) + - [3.3. State Management Wizard](#33-state-management-wizard) + - [3.4. Awaiting CallbackQuery](#34-awaiting-callbackquery) + - [3.5. Adding a Custom Filter](#35-adding-a-custom-filter) +- [4. Advanced Topics](#4-advanced-topics) + - [4.1. Handler Priority](#41-handler-priority) + - [4.2. Dependency Injection (DI)](#42-dependency-injection-di) + - [4.3. Custom State Keepers](#43-custom-state-keepers) + - [4.4. Automatic Handler Discovery](#44-automatic-handler-discovery) + - [4.5. Hosting Integration](#45-hosting-integration) + - [4.6. Error Handling and Logging](#46-error-handling-and-logging) + - [4.7. Performance Optimization](#47-performance-optimization) + - [4.8. Best Practices](#48-best-practices) +- [5. FAQ & Best Practices](#5-faq--best-practices) + - [Q: My handler is not being triggered. What should I do?](#q-my-handler-is-not-being-triggered-what-should-i-do) + - [Q: How can I access the `ITelegramBotClient` or the original `Update` object inside a handler?](#q-how-can-i-access-the-itelegrambotclient-or-the-original-update-object-inside-a-handler) + - [Q: How do I handle errors?](#q-how-do-i-handle-errors) + - [Q: How can I organize my code for a large bot?](#q-how-can-i-organize-my-code-for-a-large-bot) +- [6. Links](#6-links) + +--- + +## 1. Installation + +**Telegrator** is distributed as a NuGet package. You can install it using the .NET CLI, the NuGet Package Manager Console, or by managing NuGet packages in Visual Studio. + +### Prerequisites +- .NET >= 5.0 `or` .NET Core >= 2.0 `or` Framework >= 4.6.1 (.NET Standart 2.1 compatible) +- A Telegram Bot Token from [@BotFather](https://t.me/BotFather). + +### .NET CLI +```shell +dotnet add package Telegrator +``` + +### Package Manager Console +```shell +Install-Package Telegrator +``` + +### Hosting Integrations +- .NET Core >= 8.0 +- `Telegrator.Hosting`: For console/background services +- `Telegrator.Hosting.Web`: For ASP.NET Core/Webhook (WIP) + +--- + +## 2. Framework Mechanics Overview + +### 2.1. Basic Concepts + +Telegrator is built around several core ideas: + +- **Aspect-Oriented Handlers**: Each handler is a focused, reusable class that reacts to a specific type of update (message, command, callback, etc.). +- **Mediator Pattern**: All updates are routed through a central `UpdateRouter`, which dispatches them to the appropriate handlers based on filters and priorities. +- **Filters as Attributes**: Handler classes are decorated with filter attributes that declaratively specify when the handler should run. +- **State Management**: Built-in mechanisms for managing user/chat state without external storage. +- **Concurrency Control**: Fine-grained control over how many handlers run in parallel, both globally and per-handler. + +### 2.2. Practice: Minimal Bot + +Here's how to create a minimal bot that replies to any private message containing "hello": + +```csharp +using Telegrator; +using Telegrator.Handlers; +using Telegrator.Annotations; +using Telegram.Bot.Types; +using Telegram.Bot.Types.Enums; + +[MessageHandler] +[ChatType(ChatType.Private)] +[TextContains("hello", StringComparison.InvariantCultureIgnoreCase)] +public class HelloHandler : MessageHandler +{ + public override async Task Execute(IHandlerContainer container, CancellationToken cancellation) + { + await Reply("Hello! Nice to meet you!", cancellationToken: cancellation); + return Ok; + } +} + +class Program +{ + static void Main(string[] args) + { + TelegratorClient bot = new TelegratorClient(""); + bot.Handlers.AddHandler(); + + bot.StartReceiving(); + Console.ReadLine(); + } +} +``` + +> **How is it working?** +> 1. **`[MessageHandler]`**: This attribute marks `HelloHandler` as a handler for `Message` updates. +> 2. **`[ChatType(ChatType.Private)]`**: This filter ensures the handler only runs for private chat messages. +> 3. **`[TextContains("hello")]`**: This filter checks if the message contains "hello" (case-insensitive). +> 4. **`TelegratorClient`**: The main bot client that manages the connection to Telegram and the update processing pipeline. +> 5. **`bot.Handlers.AddHandler()`**: Registers the handler with the bot. +> 6. **`bot.StartReceiving()`**: Starts the long-polling loop to fetch updates from Telegram. +> 7. **`Reply(...)`**: A helper method that sends a reply to the original message. + +### 2.3. Working with Filters + +Filters are the gatekeepers of your bot logic. They are applied as attributes to handler classes and determine when a handler should be executed. + +**Common Filters:** +- `[CommandAlias("start")]` — Only for the `/start` command +- `[TextContains("hello")]` — Message contains "hello" +- `[ChatType(ChatType.Private)]` — Only private chats +- `[FromUserId(123456789)]` — Only from a specific user +- `[HasReply]` — Only if the message is a reply + +**Combining Filters:** +You can combine filters using logical modifiers: +- Multiple filters on a handler, by default, are combined with logical AND +- `Modifiers = FilterModifier.OrNext` - will combine this and next filter with OR logic +- `Modifiers = FilterModifier.Not` - Inverts the filter +- This flags can be combined using bit OR (`Modifiers = FilterModifier.Not | FilterModifier.OrNext`) + +**Example:** +```csharp +[MessageHandler] +[ChatType(ChatType.Private)] +[TextContains("hello", Modifiers = FilterModifier.Not)] +public class NotHelloHandler : MessageHandler +{ + // Runs for private messages that do NOT contain "hello" +} + +[MessageHandler] +[TextContains("bot", Modifiers = FilterModifier.OrNext)] +[Mentioned()] +public class NotHelloHandler : MessageHandler +{ + // Runs for messages that contains "bot" or if bot was mentioned using @ +} +``` + +> **How is it working?** +> 1. **Multiple Filters**: The handler has two filters that work together with logical AND. +> 2. **`[ChatType(ChatType.Private)]`**: Ensures only private chat messages are processed. +> 3. **`[TextContains("hello", Modifiers = FilterModifier.Not)]`**: The `Not` modifier inverts the filter, so it matches messages that do NOT contain "hello". +> 4. **Combined Logic**: The handler will only run for private messages that don't contain "hello". + +### 2.4. State Management + +Telegrator provides built-in state management for multi-step conversations (wizards, forms, quizzes) without a database. + +> [!NOTE] +> Each type of `StateKeeper`'s (EnumStateKeeper, NumericStateKeeper) is shared beetwen **EVERY** handler in project. + +**Types of State:** +- **NumericState**: Integer-based steps +- **StringState**: Named steps +- **EnumState**: Enum-based scenarios + +**How to Use:** +1. Define your state (enum/int/string) +2. Use a state filter attribute on your handler: + - `[EnumState(MyEnum.Step1)]` + - `[NumericState(1)]` +3. Change state inside the handler using extension methods: + - `container.ForwardEnumState()` + - `container.ForwardNumericState()` + - `container.DeleteEnumState()` + +**Example:** +```csharp +public enum QuizState +{ + Start = SpecialState.NoState, Q1, Q2 +} + +[CommandHandler] +[CommandAlias("quiz")] +[EnumState(QuizState.Start)] +public class StartQuizHandler : CommandHandler +{ + public override async Task Execute(IHandlerContainer container, CancellationToken cancellation) + { + container.ForwardEnumState(); + await Reply("Quiz started! Question 1: What is the capital of France?"); + return Ok; + } +} + +[MessageHandler] +[EnumState(QuizState.Q1)] +public class Q1Handler : MessageHandler +{ + public override async Task Execute(IHandlerContainer container, CancellationToken cancellation) + { + if (Input.Text.Trim().Equals("Paris", StringComparison.InvariantCultureIgnoreCase)) + await Reply("Correct!"); + else + await Reply("Incorrect. The answer is Paris."); + + container.ForwardEnumState(); + await Reply("Question 2: What is 2 + 2?"); + return Ok; + } +} +``` + +> **How is it working?** +> 1. **Enum State Definition**: `QuizState` enum defines the conversation flow with `Start = SpecialState.NoState` indicating no initial state. +> 2. **State Filter**: `[EnumState(QuizState.Start)]` ensures the handler only runs when the user is in the "Start" state. +> 3. **State Transition**: `container.ForwardEnumState()` moves the user to the next state (Q1). +> 4. **Next Handler**: The `Q1Handler` will only run when the user is in state `QuizState.Q1`. +> 5. **State Management**: Each handler manages its own state transition, creating a clear conversation flow. + +### 2.5. Concurrency & Awaiting + +**Concurrency Control:** +- Limit the number of concurrent executions globally using `MaximumParallelWorkingHandlers` in `TelegramBotOptions` + +**Awaiting Other Updates:** +- Use `AwaitingProvider` to wait for a user's next update (message or callback) inside a handler: + +```csharp +[CommandHandler] +[CommandAlias("ask")] +public class AskHandler : CommandHandler +{ + public override async Task Execute(IHandlerContainer container, CancellationToken cancellation) + { + await Reply("What is your name?"); + var nextMessage = await container.AwaitMessage().BySenderId(cancellation); + await Reply($"Hello, {nextMessage.Text}!"); + return Ok; + } +} +``` + +> **How is it working?** +> 1. **Awaiting Provider**: `container.AwaitMessage()` creates a temporary handler that waits for the next message. +> 2. **Sender Filter**: `.BySenderId(cancellation)` ensures only messages from the same user are captured. +> 3. **Async Flow**: The handler pauses execution until the user responds, then continues with the conversation. +> 4. **Context Preservation**: The original handler context is maintained during the awaiting process. + +### 2.6. Extensibility + +You can extend Telegrator by creating custom filters, attributes, and state keepers. + +**Custom Filter Attribute Example:** +```csharp +using Telegram.Bot.Types; +using Telgrator.Attributes; +using Telegrator.Handlers; + +public class AdminOnlyAttribute() : FilterAnnotation +{ + private readonly List _adminIds = []; + + public void AddAdmin(long id) => _adminIds.Add(id); + public void RemoveAdmin(long id) => _adminIds.Remove(id); + + public override bool CanPass(FilterExecutionContext context) + => _adminIds.Contains(context.Input.From?.Id); +} + +[MessageHandler] +[AdminOnly] +public class AdminHandler : MessageHandler +{ + public override async Task Execute(IHandlerContainer container, CancellationToken cancellation) + { + await Reply("Hello, admin!"); + return Ok; + } +} + +// ... +AdminOnlyAttribute.AddAdmin(123456789); +bot.StartReceiving(); +``` + +> **How is it working?** +> 1. **Custom Filter**: `AdminOnlyAttribute` inherits from `FilterAnnotation` to create a reusable filter attribute. +> 2. **Filter Logic**: `CanPass()` method checks if the message sender's ID matches the admin ID. +> 4. **Usage**: The filter is applied as an attribute `[AdminOnly]` to restrict access users that not registered as admins. + +### 2.7. Integration + +Telegrator works in console, hosted applications, --and ASP.NET Core (webhook)-- (WIP) projects. + +**Console App Example:** +```csharp +var bot = new TelegratorClient(""); +bot.Handlers.CollectHandlersDomainWide(); +bot.StartReceiving(); +``` + +**Hosting Example:** +```csharp +using Telegrator.Hosting; + +var builder = TelegramBotHost.CreateBuilder(); +builder.Handlers.AddHandler(); +var host = builder.Build(); +await host.StartAsync(); +``` + +> **How is it working?** +> 1. **Console Integration**: `TelegratorClient` provides a simple way to create bots in console applications. +> 2. **Domain-Wide Collection**: `CollectHandlersDomainWide()` automatically discovers and registers all handlers in the current assembly. +> 3. **ASP.NET Core Integration**: `TelegramBotHost.CreateBuilder()` provides a builder pattern for hosting bots in ASP.NET Core applications. +> 4. **Dependency Injection**: Handlers and their dependencies are automatically registered with the DI container. + +--- + +## 3. Step-by-Step Tutorials + +### 3.1. Minimal Bot Creation +See [Practice: Minimal Bot](#practice-minimal-bot). + +### 3.2. Command Filtering +Message is considered command if is start with '/' and has not null or empty name. +Instead of using the `MessageHandler` for command (such as `/start`) you should use `CommandHandler`. + +```csharp +[CommandHandler] +[CommandAlias("start")] +public class StartHandler : CommandHandler +{ + public override async Task Execute(IHandlerContainer container, CancellationToken cancellation) + { + await Reply("Welcome! Use /help to see available commands."); + return Ok; + } +} + +[CommandHandler] +[CommandAlias("help")] +public class HelpHandler : CommandHandler +{ + public override async Task Execute(IHandlerContainer container, CancellationToken cancellation) + { + await Reply("Available commands:\n/start - Start the bot\n/help - Show this help"); + return Ok; + } +} + +[MessageHandler] +[TextStartsWith("/", Modifiers = FilterModifier.Not)] +public class EchoHandler : MessageHandler +{ + public override async Task Execute(IHandlerContainer container, CancellationToken cancellation) + { + await Reply($"You said: \"{Input.Text}\""); + return Ok; + } +} +``` + +> **How is it working?** +> 1. **Command Handlers**: `[CommandHandler]` and `[CommandAlias]` work together to handle specific commands like `/start` and `/help`. +> 2. **Echo Handler**: `[TextStartsWith("/", Modifiers = FilterModifier.Not)]` catches all messages that don't start with "/" (non-commands). +> 3. **Handler Separation**: Each command has its own dedicated handler, making the code modular and maintainable. +> 4. **Filter Modifiers**: The `Not` modifier inverts the filter logic to exclude command messages. + +### 3.3. State Management Wizard +```csharp +[CommandHandler] +[CommandAlias("wizard")] +[NumericState(SpecialState.NoState)] +public class StartWizardHandler : CommandHandler +{ + public override async Task Execute(IHandlerContainer container, CancellationToken cancellation) + { + container.CreateNumericState(); // This code is not necesary, as "Forward" method can automatically creates state if needed, but its recomended to use + container.ForwardNumericState(); + await Reply("What is your name?"); + return Ok; + } +} + +[MessageHandler] +[NumericState(1)] +public class NameHandler : MessageHandler +{ + public override async Task Execute(IHandlerContainer container, CancellationToken cancellation) + { + container.ForwardNumericState(); + await Reply($"Nice to meet you, {Input.Text}! How old are you?"); + return Ok; + } +} + +[MessageHandler] +[NumericState(2)] +public class AgeHandler : MessageHandler +{ + public override async Task Execute(IHandlerContainer container, CancellationToken cancellation) + { + if (int.TryParse(Input.Text, out int age)) + { + await Reply($"Thank you! You are {age} years old. Wizard completed!"); + container.DeleteNumericState(); + } + else + { + await Reply("Please enter a valid age (number)."); + } + + return Ok; + } +} +``` + +> **How is it working?** +> 1. **Numeric State**: `[NumericState(SpecialState.NoState)]` starts the wizard when no state exists. +> 2. **State Creation**: `container.CreateNumericState()` initializes the numeric state for the user. +> 3. **State Progression**: `container.ForwardNumericState()` moves to the next step (1, then 2). +> 4. **State Cleanup**: `container.DeleteNumericState()` removes the state when the wizard completes. +> 5. **Input Validation**: The age handler validates numeric input and provides feedback. + +### 3.4. Awaiting CallbackQuery +```csharp +[CommandHandler] +[CommandAlias("menu")] +public class MenuHandler : CommandHandler +{ + public override async Task Execute(IHandlerContainer container, CancellationToken cancellation) + { + var keyboard = new InlineKeyboardMarkup(new[] + { + InlineKeyboardButton.WithCallbackData("Option 1", "option1"), + InlineKeyboardButton.WithCallbackData("Option 2", "option2") + }); + + await Reply("Choose an option:", replyMarkup: keyboard, cancellationToken: cancellation); + return Ok; + } +} + +[CallbackQueryHandler] +[CallbackData("option1")] +public class Option1Handler : CallbackQueryHandler +{ + public override async Task Execute(IHandlerContainer container, CancellationToken cancellation) + { + await AnswerCallbackQuery("You selected Option 1!", cancellationToken: cancellation); + await EditMessageText("You selected Option 1!"); + return Ok; + } +} + +[CallbackQueryHandler] +[CallbackData("option2")] +public class Option2Handler : CallbackQueryHandler +{ + public override async Task Execute(IHandlerContainer container, CancellationToken cancellation) + { + await AnswerCallbackQuery("You selected Option 2!", cancellationToken: cancellation); + await EditMessageText("You selected Option 2!"); + return Ok; + } +} +``` + +> **How is it working?** +> 1. **Inline Keyboard**: `InlineKeyboardMarkup` creates interactive buttons with `CallbackData` identifiers. +> 2. **CallbackQuery Handlers**: `[CallbackQueryHandler]` and `[CallbackData]` work together to handle button clicks. +> 3. **Response Methods**: `AnswerCallbackQuery()` provides immediate feedback, while `EditMessageText()` updates the message. +> 4. **Handler Separation**: Each button option has its own dedicated handler for clean code organization. + +### 3.5. Adding a Custom Filter +```csharp +public class PremiumUserAttribute : UpdateFilterAttribute +{ + public override Message? GetFilterringTarget(Update update) + => update.Message; + + public override bool CanPass(FilterExecutionContext context) + => context.Input.From?.IsPremium == true; +} + +[MessageHandler] +[PremiumUser] +public class PremiumFeatureHandler : MessageHandler +{ + public override async Task Execute(IHandlerContainer container, CancellationToken cancellation) + { + await Reply("This feature is only available for premium users!"); + return Ok; + } +} +``` + +> **How is it working?** +> 1. **Custom Filter**: `PremiumUserAttribute` inherits from `UpdateFilterAttribute` to create a reusable filter. +> 2. **Premium Check**: `context.Input.From?.IsPremium == true` checks if the user has Telegram Premium. +> 3. **Target Extraction**: `GetFilterringTarget()` extracts the `Message` from the `Update` object. +> 4. **Usage**: The filter is applied as `[PremiumUser]` to restrict features to premium users only. + +--- + +## 4. Advanced Topics + +### 4.1. Handler Priority +By default, handlers are processed in the order they are added. However, you can control the execution order using the `Priority` property in the handler attribute. A greater number means higher priority. + +```csharp +[MessageHandler(Priority = 1)] // Runs before default priority (0) +public class HighPriorityHandler : MessageHandler +{ + public override async Task Execute(IHandlerContainer container, CancellationToken cancellation) + { + await Reply("This handler runs first!"); + return Ok; + } +} + +[MessageHandler(Priority = 0)] // Default priority +public class NormalPriorityHandler : MessageHandler +{ + public override async Task Execute(IHandlerContainer container, CancellationToken cancellation) + { + await Reply("This handler runs second!"); + return Ok; + } +} +``` + +> **How is it working?** +> 1. **Priority System**: The `Priority` property in handler attributes controls execution order. +> 2. **Higher Priority First**: Handlers with higher priority numbers (1) run before those with lower numbers (0). +> 3. **Default Priority**: When not specified, handlers have priority 0. +> 4. **Execution Order**: This ensures critical handlers (like admin commands) run before general handlers. + +### 4.2. Dependency Injection (DI) +Telegrator is designed to work seamlessly with DI containers (e.g., ASP.NET Core). Handlers and their dependencies are automatically registered. + +```csharp +[MessageHandler] +public class MyHandler : MessageHandler +{ + private readonly IMyService _myService; + private readonly ILogger _logger; + + public MyHandler(IMyService myService, ILogger logger) + { + _myService = myService; + _logger = logger; + } + + public override async Task Execute(IHandlerContainer container, CancellationToken cancellation) + { + _logger.LogInformation("MyHandler executed!"); + var result = _myService.DoSomething(); + await Reply(result); + return Ok; + } +} +``` + +> **How is it working?** +> 1. **Constructor Injection**: Dependencies (`IMyService`, `ILogger`) are automatically injected by the DI container. +> 2. **Service Registration**: When using `Telegrator.Hosting`, services are automatically registered with the DI container. +> 3. **Handler Instantiation**: Telegrator creates handler instances through the DI container, resolving all dependencies. +> 4. **Logging Integration**: Built-in logging support allows for comprehensive debugging and monitoring. + +### 4.3. Custom State Keepers +You can implement your own state keeper by inheriting from `StateKeeperBase`. This allows for advanced scenarios (e.g., per-message state, custom key resolution). + +### 4.4. Automatic Handler Discovery +Telegrator provides automatic discovery and registration of handlers across your entire application domain using the `CollectHandlersDomainWide()` method. + +**How it works:** +- Scans all loaded assemblies in the current domain +- Automatically discovers classes decorated with handler attributes +- Registers them with the bot without manual registration +- + +**Example:** +```csharp +var bot = new TelegratorClient(""); +bot.Handlers.CollectHandlersDomainWide(); // Automatically finds and registers all handlers +bot.StartReceiving(); +``` + +**Benefits:** +- No need to manually register each handler +- Reduces boilerplate code +- Ensures all handlers are discovered automatically +- Perfect for large applications with many handlers + +> **How is it working?** +> 1. **Domain Scanning**: `CollectHandlersDomainWide()` scans all assemblies loaded in the current AppDomain. +> 2. **Reflection Discovery**: Uses reflection to find all classes decorated with handler attributes. +> 3. **Automatic Registration**: Each discovered handler is automatically registered with the `HandlersCollection`. +> 4. **Handler Types**: Supports all handler types: `MessageHandler`, `CommandHandler`, `CallbackQueryHandler`, etc. + +### 4.5. Hosting Integration +Telegrator provides seamless integration with .NET's generic host through the `Telegrator.Hosting` package, making it easy to build production-ready bot applications. + +**Installation:** +```shell +dotnet add package Telegrator.Hosting +``` + +**Dependencies:** +- `Microsoft.Extensions.Hosting` - .NET Generic Host +- `Microsoft.Extensions.DependencyInjection` - Dependency Injection +- `Microsoft.Extensions.Configuration` - Configuration management +- `Microsoft.Extensions.Logging` - Logging infrastructure + +**Core Components:** +- `TelegramBotHost` - The main hosted service that manages the bot lifecycle +- `TelegramBotHostBuilder` - Builder pattern for configuring the bot host +- `TelegramBotOptions` - Configuration options for the bot + +**Basic Example:** +```csharp +var builder = TelegramBotHost.CreateBuilder(); + +// Configure services +builder.Services.AddSingleton(); + +// Configure handlers +builder.Handlers.CollectHandlersDomainWide(); + +// Building host +var host = builder.Build(); +await host.Run(); +``` + +> **How is it working?** +> 1. **Generic Host Integration**: `TelegramBotHost` implements `IHost` and integrates with .NET's generic host. +> 2. **Lifecycle Management**: The host manages the bot's startup, shutdown, and graceful termination. +> 3. **Dependency Injection**: All handlers and services are automatically registered with the DI container. +> 4. **Configuration**: Supports standard .NET configuration patterns (appsettings.json, environment variables, etc.). +> 5. **Logging**: Integrates with .NET's logging infrastructure for comprehensive monitoring. +> 6. **Health Checks**: Can be integrated with .NET's health check system for production monitoring. + +### 4.6. Error Handling and Logging +You can subscribe to error events or set a custom exception handler: + +```csharp +bot.UpdateRouter.ExceptionHandler = new DefaultRouterExceptionHandler((client, exception, source, cancellationToken) => +{ + Console.WriteLine($"An error occurred: {exception.Message}"); + return Task.CompletedTask; +}); +``` + +> **How is it working?** +> 1. **Exception Handler**: `ExceptionHandler` property allows you to set a custom exception handler for the entire bot. +> 2. **Error Context**: The handler receives the bot client, exception, source information, and cancellation token. +> 3. **Global Error Handling**: This provides a centralized way to handle all exceptions that occur during update processing. +> 4. **Logging Integration**: Perfect place to log errors or send notifications to administrators. + +### 4.7. Performance Optimization +- Use appropriate concurrency limits for resource-intensive operations +- Avoid thread-blocking operations in handlers +- Use state management for multi-step processes +- Use `AwaitingProvider` for complex conversation flows + +### 4.8. Best Practices +- Organize handlers, filters, and state keepers in separate folders +- Use feature modules for large bots +- Prefer declarative filters over manual `if` statements +- Keep handlers focused and single-responsibility + +--- + +## 5. FAQ & Best Practices + +### 5.1. Q: My handler is not being triggered. What should I do? +- Check handler registration (use `bot.Handlers.AddHandler()` or domain-wide collection) +- Check filter attributes and update types +- Enable debug logging + +### 5.2. Q: How can I access the `ITelegramBotClient` or the original `Update` object inside a handler? +- Use `Client`, `Update`, and `Input` properties in your handler + +### 5.3. Q: How do I handle errors? +- Set a custom exception handler or subscribe to error events + +### 5.4. Q: How can I organize my code for a large bot? +- Use folders, feature modules, and namespaces +- Keep handlers focused and modular + +--- + +## 6. Links + +- [API Reference](./TelegramReactive_Api.md) +- [Main Repository](https://github.com/Rikitav/Telegrator) +- [Wiki & Examples](https://github.com/Rikitav/Telegrator/wiki/) +- [NuGet Package](https://www.nuget.org/packages/Telegrator) +- [Issues & Discussions](https://github.com/Rikitav/Telegrator/issues) + +--- + +> **Feel free to contribute, ask questions, or open issues!** + +дыкий сишарп \ No newline at end of file diff --git a/src/Telegrator.Hosting.Web/Hosting.Web/TelegramBotWebHostBuilder.cs b/src/Telegrator.Hosting.Web/Hosting.Web/TelegramBotWebHostBuilder.cs index ecbc4c0..152496b 100644 --- a/src/Telegrator.Hosting.Web/Hosting.Web/TelegramBotWebHostBuilder.cs +++ b/src/Telegrator.Hosting.Web/Hosting.Web/TelegramBotWebHostBuilder.cs @@ -3,11 +3,7 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; -using Telegram.Bot; -using Telegrator; using Telegrator.Core; -using Telegrator.Providers; #pragma warning disable IDE0001 namespace Telegrator.Hosting.Web diff --git a/src/Telegrator.Hosting.Web/Telegrator.Hosting.Web.csproj b/src/Telegrator.Hosting.Web/Telegrator.Hosting.Web.csproj index ad541fa..63bcbf4 100644 --- a/src/Telegrator.Hosting.Web/Telegrator.Hosting.Web.csproj +++ b/src/Telegrator.Hosting.Web/Telegrator.Hosting.Web.csproj @@ -15,7 +15,7 @@ True Telegrator.Hosting.Web - 1.16.0 + 1.16.1 Rikitav Tim4ik Rikitav Tim4ik https://github.com/Rikitav/Telegrator @@ -25,22 +25,19 @@ README.md LICENSE - - - - - - - + + - + + + diff --git a/src/Telegrator.Hosting/Telegrator.Hosting.csproj b/src/Telegrator.Hosting/Telegrator.Hosting.csproj index 016cd34..740fda4 100644 --- a/src/Telegrator.Hosting/Telegrator.Hosting.csproj +++ b/src/Telegrator.Hosting/Telegrator.Hosting.csproj @@ -15,7 +15,7 @@ True Telegrator.Hosting - 1.16.0 + 1.16.1 Rikitav Tim4ik Rikitav Tim4ik https://github.com/Rikitav/Telegrator @@ -26,19 +26,19 @@ LICENSE + + + + + - - - - - diff --git a/src/Telegrator/Core/Descriptors/DescribedHandlerDescriptor.cs b/src/Telegrator/Core/Descriptors/DescribedHandlerDescriptor.cs index 3f85684..08cf0dd 100644 --- a/src/Telegrator/Core/Descriptors/DescribedHandlerDescriptor.cs +++ b/src/Telegrator/Core/Descriptors/DescribedHandlerDescriptor.cs @@ -1,6 +1,5 @@ using Telegram.Bot; using Telegram.Bot.Types; -using Telegrator.Core; using Telegrator.Core.Filters; using Telegrator.Core.Handlers; diff --git a/src/Telegrator/Mediation/UpdateRouter.cs b/src/Telegrator/Mediation/UpdateRouter.cs index f78d2e3..7dc8fa1 100644 --- a/src/Telegrator/Mediation/UpdateRouter.cs +++ b/src/Telegrator/Mediation/UpdateRouter.cs @@ -98,6 +98,11 @@ namespace Telegrator.Mediation /// The cancellation token. /// A task representing the asynchronous update handling operation. public virtual async Task HandleUpdateAsync(ITelegramBotClient botClient, Update update, CancellationToken cancellationToken) + { + _ = HandleUpdateAsyncInternal(botClient, update, cancellationToken); + } + + private async Task HandleUpdateAsyncInternal(ITelegramBotClient botClient, Update update, CancellationToken cancellationToken) { // Logging LogUpdate(update); @@ -135,7 +140,28 @@ namespace Telegrator.Mediation } // Queuing reagular handlers for execution - await HandlersPool.Enqueue(GetHandlers(HandlersProvider, botClient, update, cancellationToken)); + foreach (DescribedHandlerDescriptor handlerInfo in GetHandlers(HandlersProvider, botClient, update, cancellationToken)) + { + if (lastResult?.NextType != null) + { + if (lastResult.NextType != handlerInfo.From.HandlerType) + continue; + } + + // Enqueuing found handlers + await HandlersPool.Enqueue(handlerInfo); + await handlerInfo.AwaitResult(cancellationToken).ConfigureAwait(false); + + lastResult = handlerInfo.Result; + if (lastResult == null) + break; // Smth went horribly wrong, better to stop routing + + if (lastResult != null && !lastResult.RouteNext) + break; + + TelegratorLogging.LogTrace("Handler '{0}' requested route continuation (Update {1})", handlerInfo.DisplayString, handlerInfo.HandlingUpdate.Id); + } + TelegratorLogging.LogTrace("Receiving Update ({0}) finished", update.Id); } catch (OperationCanceledException) diff --git a/src/Telegrator/Telegrator.csproj b/src/Telegrator/Telegrator.csproj index 7f6c051..bd677f9 100644 --- a/src/Telegrator/Telegrator.csproj +++ b/src/Telegrator/Telegrator.csproj @@ -14,7 +14,7 @@ True Telegrator : Telegram.Bot mediator framework - 1.16.0 + 1.16.1 Rikitav Tim4ik Rikitav Tim4ik https://github.com/Rikitav/Telegrator