Development of a bot for Telegram on the .NET platform
Introduction
Telegram is one of the most popular instant messengers in the world, offering features such as group chats, channels, voice and video calls, and the ability to create bots. In this article, we will not aim to show how to create a “Hello, World!” application from scratch, but will study a more complex example of a ready-made bot implementation on the .NET platform using modern technologies and development practices.
Choosing a library to create a bot
There are several ways to create Telegram bots on the .NET platform, including using third-party libraries and frameworks. Let's look at the most popular options, offered by Telegram itself.
Telegram.Bot by TelegramBots
The most popular library for creating Telegram bots on the .NET platform.
Supports all features of the Telegram Bot API.
Regularly updated and supported by the community.
Has detailed documentation and examples of use.
Supports .NET Standard 2.0 and .NET 6+.
Telegram.BotAPI by Eptagone
Another popular library for creating Telegram bots on the .NET platform.
It also supports all the features of the Telegram Bot API, is regularly updated and has good use cases.
Supports .NET Standard 2.0, .NET Framework 4.6.2+ and .NET 6, 8.
TelegramBotFramework by MajMcCloud
A library with a simple interface for creating bots, reminiscent of developing applications on Windows Forms.
Has good documentation and examples of use.
It is updated less frequently than Telegram.Bot and Telegram.BotAPI, and does not support all the features of the Telegram Bot API.
Supports .NET Standard 2.0+ and .NET 6, 7.
For the implementation example, the library was chosen Telegram.BotAPIas it has up-to-date capabilities and provides the best use cases.
This choice is subjective and is not a strict recommendation, since the first two libraries are equally good. TelegramBotFramework was not chosen due to a specific approach to the architecture and lack of support for new features of the Telegram Bot API.
Practical implementation example
Let's consider Author's GitHub repository with an example of the implementation of a bot that provides information about the weather in various cities.
Main features of the implementation under consideration
Solution architecture
General structure
The solution is divided into several layers, each of which is responsible for its own functionality.
Host/AppHost – is responsible for launching the application.
Contains the entry point and configuration of all necessary services such as MediatR, Entity Framework and OpenTelemetry.
Sets up a connection to the database and initializes the bot.
AppHost also allows you to run your application and all dependencies using .NET Aspire.
Application – contains the business logic of the application.
Here are command handlers that process user requests and perform appropriate actions.
Implements CQS and Mediator patterns to separate commands and queries. All commands and their handlers are grouped into separate directories by functionality, which simplifies their search and support and is somewhat reminiscent of the approach vertical slice:
src
├── Application
│ ├── Features
│ │ ├── Bot
│ │ │ ├── StartBotCommand.cs
│ │ │ ├── StartBotCommandHandler.cs
...
│ │ ├── Weather
│ │ │ ├── WeatherBotCommand.cs
│ │ │ ├── WeatherBotCommandHandler.cs
Data – Responsible for accessing data and interacting with the database.
Implements repositories that use the Entity Framework to perform database operations.
Contains database migrations and entity configurations.
Domain – contains the main entities and interfaces used in the application.
Defines data models, repository interfaces, and other abstractions that help separate business logic from implementation details.
Framework – contains auxiliary libraries and utilities used in the project.
Common classes, extensions, exception handlers, and other components that help simplify application development and maintenance.
Component Interaction
Launching the application: Project
Host
initializes all necessary services and starts the application.Command Processing: When the user sends a command to the bot, it goes into the layer
Application
where the corresponding command handler executes the business logic.Data access: If the command handler needs to interact with the database, it uses the repositories from the layer
Data
.Using Entities: Repositories and command handlers work with entities and interfaces from the layer
Domain
.Auxiliary functions: While the application is running, utilities and libraries from the layer are used
Framework
.
This architecture makes it easy to scale and maintain the application by dividing responsibilities between different layers and providing flexibility and modularity to the code.
Dependencies between layers are built according to the rules clean architecture. For example, layer Application
depends on the layer Domain
but does not depend on the layer Data
.
Bot infrastructure
Configuration
For the bot to work fully, you need to configure the Telegram API token (we will omit obtaining the token itself, since this topic is well covered in official documentation) and a list of basic commands. This class serves this purpose. TelegramBotSetupexecuted as a hosted service when the application starts.
For each supported language, commands are defined that will be displayed in the list of available bot commands directly in the Telegram application.
internal sealed class TelegramBotSetup : IHostedService
{
// ctor
public async Task StartAsync(CancellationToken cancellationToken)
{
//...
await SetCommands(cancellationToken).ConfigureAwait(false);
//...
}
private async Task SetCommands(CancellationToken cancellationToken)
{
await _client.DeleteMyCommandsAsync(cancellationToken: cancellationToken).ConfigureAwait(false);
// default (en)
await _client.SetMyCommandsAsync(
[
new(WeatherBotCommand.CommandName,
_botMessageLocalizer.GetLocalizedString(nameof(BotMessages.WeatherCommandDescription), BotLanguage.English)),
new(HelpBotCommand.CommandName,
_botMessageLocalizer.GetLocalizedString(nameof(BotMessages.HelpCommandDescription), BotLanguage.English)),
],
cancellationToken: cancellationToken).ConfigureAwait(false);
// other languages
}
}
Receiving updates from Telegram
To receive updates from Telegram, the Polling approach is used, which allows the bot to regularly check for new messages and updates. This approach is convenient for small projects and does not require setting up webhooks. However, implementing a full-fledged webhook is also not difficult (see example).
The hosted service is responsible for receiving updates from Telegram UpdateReceiverwhich regularly requests updates via the Telegram API, initializes an instance of the class WeatherBot and transmits the received data to him.
Processing requests
The central point of the bot's functioning is the class WeatherBotinheriting library class SimpleTelegramBotBase.
It is he who is responsible for processing commands, callbacks and billing. For ease of perception, this class is divided into several parts, each of which is responsible for a specific functionality:
Converting an update or callback from Telegram into a command and sending it to MediatR
Error Handling
Payment Processing
etc.
Command Processing
The main task of the bot is to process messages/commands that users send to the bot to perform certain actions. In this example, commands are processed using the CQS pattern and MediatR.
Each Telegram command or its callback has a corresponding class that implements the interface IBotCommand or ICallbackCommand. For example, StartBotCommand:
public sealed record StartBotCommand(Message Message, UserInfo UserInfo) : IBotCommand
{
public static string CommandName => "start";
}
public interface IBotCommand : IRequest<Unit>
{
static abstract string CommandName { get; }
static virtual bool AllowGroups => true;
static virtual IReadOnlyList<string> Roles { get; } = Array.Empty<string>();
public Message Message { get; init; }
public UserInfo UserInfo { get; init; }
}
The command name is determined by a static property
CommandName
.Match the name and the command itself automatically cached by the application to ensure fast command initialization.
Additionally, you can override properties
AllowGroups
AndRoles
to control access to the team in the context of group chats and user roles.
The command is processed in the corresponding MediatR handler, which performs some calculations and sends the finished result to the user. For example, StartBotCommandHandler:
internal sealed class StartBotCommandHandler : IRequestHandler<StartBotCommand, Unit>
{
private readonly ITelegramBotClient _telegramBotClient;
private readonly IBotMessageLocalizer _botMessageLocalizer;
public StartBotCommandHandler(
ITelegramBotClient telegramBotClient,
IBotMessageLocalizer botMessageLocalizer)
{
_telegramBotClient = telegramBotClient;
_botMessageLocalizer = botMessageLocalizer;
}
public async Task<Unit> Handle(StartBotCommand request, CancellationToken cancellationToken)
{
var message = request.Message;
var text = _botMessageLocalizer.GetLocalizedString(nameof(BotMessages.HelpCommand), request.UserInfo.Language);
await _telegramBotClient.SendMessageAsync(
message.Chat.Id,
text,
parseMode: FormatStyles.HTML,
linkPreviewOptions: DefaultLinkPreviewOptions.Value,
cancellationToken: cancellationToken)
.ConfigureAwait(false);
return Unit.Value;
}
}
You can also notice that the handler implements support for message localization (via standard .NET capabilities and resource files)), which allows you to send messages in different languages depending on the user settings. Information about the user, his language preferences and roles is also part of the command – this data is filled in automatically when an instance of the class is created.
In this article, we will omit the role-playing system, the issue of localization and payments, since this is not a key aspect of the example under consideration. If you wish, you can study the corresponding classes and interfaces in the repository and run the application yourself.
Conclusion
We looked at creating a Telegram bot on the .NET platform using a stack of modern libraries and technologies. The implementation example demonstrates architectural approaches that provide modularity, flexibility, and extensibility of the solution. This allows developers to focus on business logic and easily add new commands and features while minimizing associated code changes.
If you have questions or suggestions for improving the solution, do not hesitate to contact the author or create an issue in the repository.