Skip to content

Commit e06429b

Browse files
committed
feat(framework): Enhance middleware pipeline and add engine logging
This commit introduces several improvements to the framework's core functionality. Middleware Pipeline: - Implemented conditional middleware execution with MiddlewareFilterAttribute, allowing middleware to run only for specific UpdateTypes. - Introduced IMiddlewareConfig to provide strongly-typed configuration to middleware. - Enhanced CommandContext with a Properties dictionary to enable data sharing and state management between middleware. Engine Logging: - Added detailed debug logs for the start and stop events of BotPollingEngine and BotWebhookEngine. - Added information logs to track the startup of each individual bot client in both polling and webhook modes.
1 parent debabe9 commit e06429b

15 files changed

Lines changed: 304 additions & 105 deletions

File tree

.serena/memories/commands.md

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
2+
# Adding a New Command
3+
4+
To add a new command, you can create a new class that inherits from `BotCommandController` and add methods with the `[Command]` attribute. For example:
5+
6+
```csharp
7+
public class SampleCommands : BotCommandController
8+
{
9+
[Command("ping")]
10+
public async Task Ping()
11+
{
12+
await SendMessageText("Pong!");
13+
}
14+
}
15+
```
16+
17+
## Command Attributes
18+
19+
The framework provides several attributes for defining commands:
20+
21+
- `[Command("...")]`: Defines a command that is triggered by a specific string.
22+
- `[TextCommand("...")]`: Defines a command that is triggered by a specific text message.
23+
- `[Callback("...")]`: Defines a command that is triggered by a callback query.
24+
- `[DefaultCommand]`: Defines a command that is executed when no other command matches.
25+
- `[InlineQuery]`: Defines a command that is triggered by an inline query.
26+
- `[UpdateCommand]`: Defines a command that is triggered by a specific update type.
27+
- `[TypedCommand]`: Defines a command that is triggered by a specific message type.
28+
29+
## Command Context
30+
31+
The `BotCommandController` base class provides a `Context` property that allows you to access the current request and send a response. The `CommandContext` object contains information about the incoming update, such as the message, callback query, or inline query.
32+
33+
## Sending Responses
34+
35+
The `BotCommandController` base class provides several methods for sending responses:
36+
37+
- `SendMessageText(...)`: Sends a text message.
38+
- `SendMessage(...)`: Sends a message with more options, such as a reply markup.
39+
- `AnswerCallbackQuery(...)`: Answers a callback query.
40+
- `AnswerInlineQuery(...)`: Answers an inline query.

.serena/memories/core.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,6 @@
1414
- `AddZiziBotTelegramBot()` does assembly scanning for controllers + middleware and selects engine based on `BotEngine.EngineMode`.
1515
- `UseZiziBotTelegramBot()` maps webhook endpoints (if `WebApplication`) and starts the selected `IBotEngine`.
1616
- Each update is processed in an async DI scope via `BotEngineHandler` -> `BotUpdateHandler`.
17-
- Docs artifact: `CODE_WIKI.md` at repo root (human-facing, comprehensive).
17+
- Docs artifact: `CODE_WIKI.md` at repo root (human-facing, comprehensive).
18+
- Project structure: `mem:structure`
19+
- Adding new commands: `mem:commands`

.serena/memories/structure.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
2+
# Project Structure
3+
4+
The solution (`ZiziBot.TelegramBot.sln`) contains two main projects:
5+
6+
1. **`ZiziBot.TelegramBot.Framework`**: A .NET library that provides the core framework for building command-based Telegram bots. It targets `net8.0`, `net9.0`, and `net10.0`.
7+
2. **`ZiziBot.TelegramBot.Sample`**: A .NET web project that serves as a sample implementation of the framework. It references the framework project and runs on `net10.0`.
8+
9+
## Framework Project
10+
11+
The `ZiziBot.TelegramBot.Framework` project contains the following key components:
12+
13+
- **`Attributes`**: Custom attributes used to define commands and their properties.
14+
- **`Delegates`**: Delegate types used for command execution.
15+
- **`Engines`**: The core polling and webhook engines for receiving updates from Telegram.
16+
- **`Extensions`**: Extension methods for client and endpoint setup.
17+
- **`Handlers`**: Handlers for processing bot updates.
18+
- **`Helpers`**: Helper classes for various tasks.
19+
- **`Interfaces`**: Interfaces for middleware and other components.
20+
- **`Models`**: Data models for configuration, context, and other objects.
21+
22+
## Sample Project
23+
24+
The `ZiziBot.TelegramBot.Sample` project demonstrates how to use the framework and includes the following:
25+
26+
- **`Commands`**: Sample command classes that inherit from `BotCommandController`.
27+
- **`Middlewares`**: Sample middleware implementations.
28+
- **`Program.cs`**: The main entry point of the application, where the bot is initialized and configured.
29+
- **`appsettings.json`**: Configuration file for the bot, including bot tokens and other settings.
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
using System;
2+
using Telegram.Bot.Types.Enums;
3+
4+
namespace ZiziBot.TelegramBot.Framework.Attributes;
5+
6+
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
7+
public class MiddlewareFilterAttribute(UpdateType updateType) : Attribute
8+
{
9+
public UpdateType UpdateType { get; } = updateType;
10+
}

ZiziBot.TelegramBot.Framework/Engines/BotPollingEngine.cs

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using Microsoft.Extensions.Logging;
1+
using Microsoft.Extensions.Logging;
22
using Telegram.Bot;
33
using Telegram.Bot.Polling;
44
using ZiziBot.TelegramBot.Framework.Handlers;
@@ -17,6 +17,7 @@ BotEngineHandler botEngineHandler
1717
{
1818
public async Task Start(BotClientItem clients)
1919
{
20+
logger.LogInformation("Starting polling for bot: {Name}", clients.Name);
2021
try
2122
{
2223
if (botClientCollection.Items.Exists(x => x.Name == clients.Name))
@@ -27,7 +28,11 @@ public async Task Start(BotClientItem clients)
2728

2829
await clients.Client.DeleteWebhook();
2930

30-
clients.Client.StartReceiving(botEngineHandler.UpdateHandler, ErrorHandler);
31+
clients.Client.StartReceiving(
32+
botEngineHandler.UpdateHandler,
33+
ErrorHandler,
34+
new ReceiverOptions(),
35+
clients.CancellationTokenSource?.Token ?? CancellationToken.None);
3136
botClientCollection.Items.Add(clients);
3237
}
3338
catch (Exception exception)
@@ -38,6 +43,7 @@ public async Task Start(BotClientItem clients)
3843

3944
public async Task Start()
4045
{
46+
logger.LogDebug("Starting polling engine...");
4147
var clients = botTokenConfigs.Select(x => BotClientItem.Create(x.Name, new TelegramBotClientOptions(x.Token)));
4248

4349
foreach (var client in clients)
@@ -65,6 +71,14 @@ public async Task Stop(IEnumerable<string> names)
6571
}
6672
}
6773

74+
public async Task StopEngine()
75+
{
76+
logger.LogDebug("Stopping polling engine...");
77+
78+
var botNames = botClientCollection.Items.Select(x => x.Name).ToList();
79+
await Stop(botNames);
80+
}
81+
6882
private Task ErrorHandler(ITelegramBotClient botClient, Exception exception, HandleErrorSource errorSource, CancellationToken token)
6983
{
7084
logger.LogError(exception, "Bot polling engine error. Source: {Source}", errorSource);

ZiziBot.TelegramBot.Framework/Engines/BotWebhookEngine.cs

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1-
using Microsoft.Extensions.Logging;
1+
using Microsoft.Extensions.Logging;
22
using Telegram.Bot;
33
using ZiziBot.TelegramBot.Framework.Interfaces;
44
using ZiziBot.TelegramBot.Framework.Models;
55
using ZiziBot.TelegramBot.Framework.Models.Configs;
66
using ZiziBot.TelegramBot.Framework.Models.Constants;
7+
using ZiziBot.TelegramBot.Framework.Models.Enums;
78

89
namespace ZiziBot.TelegramBot.Framework.Engines;
910

@@ -16,16 +17,23 @@ BotEngineConfig botEngineConfig
1617
{
1718
public async Task Start(BotClientItem clients)
1819
{
20+
logger.LogInformation("Starting webhook for bot: {Name}", clients.Name);
1921
try
2022
{
2123
if (botClientCollection.Items.Exists(x => x.Name == clients.Name))
2224
{
23-
logger.LogWarning("Bot polling engine is already running");
25+
logger.LogWarning("Bot webhook engine is already running for bot: {Name}", clients.Name);
2426
return;
2527
}
2628

2729
await clients.Client.DeleteWebhook();
2830

31+
if (string.IsNullOrWhiteSpace(botEngineConfig.WebhookUrl))
32+
{
33+
logger.LogError("WebhookUrl is not configured. Path: {ConfigPath}:WebhookUrl", BotEngineConfig.ConfigPath);
34+
return;
35+
}
36+
2937
var webhookUrl = botEngineConfig.WebhookUrl + "/" + ValueConst.WebHookPath + "/" + clients.BotToken;
3038
logger.LogDebug("Setting up Webhook url for Bot {BotId} to {WebhookUrl}", clients.Client.BotId, webhookUrl);
3139

@@ -35,12 +43,13 @@ public async Task Start(BotClientItem clients)
3543
}
3644
catch (Exception exception)
3745
{
38-
logger.LogError(exception, "Error starting Polling bot. Name: {Name}", clients.Name);
46+
logger.LogError(exception, "Error starting Webhook bot. Name: {Name}", clients.Name);
3947
}
4048
}
4149

4250
public async Task Start()
4351
{
52+
logger.LogDebug("Starting webhook engine...");
4453
var clients = botTokenConfigs.Select(x => BotClientItem.Create(x.Name, new TelegramBotClientOptions(x.Token)));
4554

4655
foreach (var client in clients)
@@ -51,11 +60,32 @@ public async Task Start()
5160

5261
public Task Stop(string name)
5362
{
54-
throw new NotImplementedException();
63+
var client = botClientCollection.Items.Find(x => x.Name == name);
64+
65+
if (client == null)
66+
return Task.CompletedTask;
67+
68+
botClientCollection.Items.Remove(client);
69+
70+
return client.Client.DeleteWebhook();
5571
}
5672

5773
public Task Stop(IEnumerable<string> names)
5874
{
59-
throw new NotImplementedException();
75+
return Task.WhenAll(names.Select(Stop));
76+
}
77+
78+
public async Task StopEngine()
79+
{
80+
logger.LogDebug("Stopping webhook engine...");
81+
82+
if (botEngineConfig.EngineMode != BotEngineMode.Webhook)
83+
{
84+
logger.LogInformation("Bot engine is not in webhook mode, skipping webhook engine shutdown");
85+
return;
86+
}
87+
88+
var botNames = botClientCollection.Items.Select(x => x.Name).ToList();
89+
await Stop(botNames);
6090
}
6191
}

0 commit comments

Comments
 (0)