Разработка бота для Telegram на платформе .NET
Введение
Telegram — один из самых популярных мессенджеров в мире, предлагающий такие функции, как групповые чаты, каналы, голосовые и видеозвонки, а также возможность создания ботов. В данной статье мы не будем ставить цель показать, как создать с нуля приложение a-la «Hello, World!», а изучим более сложный пример готовой реализации бота на платформе .NET с использованием современных технологий и практик разработки.
Выбор библиотеки для создания бота
Существует несколько способов создания Telegram-ботов на платформе .NET, включая использование сторонних библиотек и фреймворков. Рассмотрим самые популярные варианты, предлагаемые самим Telegram.
Telegram.Bot от TelegramBots
Наиболее популярная библиотека для создания Telegram-ботов на платформе .NET.
Поддерживает все возможности Telegram Bot API.
Регулярно обновляется и поддерживается сообществом.
Обладает подробной документацией и примерами использования.
Поддерживает .NET Standard 2.0 и .NET 6+.
Telegram.BotAPI от Eptagone
Еще одна популярная библиотека для создания Telegram-ботов на платформе .NET.
Также поддерживает все возможности Telegram Bot API, регулярно обновляется и имеет хорошие примеры использования.
Поддерживает .NET Standard 2.0, .NET Framework 4.6.2+ и .NET 6, 8.
TelegramBotFramework от MajMcCloud
Библиотека с простым интерфейсом для создания ботов, напоминающая разработку приложений на Windows Forms.
Имеет хорошую документацию и примеры использования.
Обновляется реже, чем Telegram.Bot и Telegram.BotAPI, и не поддерживает все возможности Telegram Bot API.
Поддерживает .NET Standard 2.0+ и .NET 6, 7.
Для примера реализации была выбрана библиотека Telegram.BotAPI, так как она обладает актуальными возможностями и предоставляет лучшие примеры использования.
Данный выбор — субъективный и не является строгой рекомендацией, так как первые две библиотеки одинаково хороши. TelegramBotFramework не был выбран из-за специфичного подхода к архитектуре и отсутствия поддержки новых возможностей Telegram Bot API.
Практический пример реализации
Рассмотрим GitHub-репозиторий автора с примером реализации бота, который предоставляет информацию о погоде в различных городах.
Основные особенности рассматриваемой реализации
Архитектура решения
Общая структура
Решение разделено на несколько слоев, каждый из которых отвечает за свою функциональность.
Host/AppHost — отвечает за запуск приложения.
Содержит точку входа и конфигурацию всех необходимых сервисов, таких как MediatR, Entity Framework и OpenTelemetry.
Настраивает подключение к базе данных и инициализирует бота.
AppHost также позволяет запустить приложение и все зависимости с помощью .NET Aspire.
Application — содержит бизнес-логику приложения.
Здесь находятся обработчики команд, которые обрабатывают запросы пользователей и выполняют соответствующие действия.
Реализует паттерны CQS и Mediator для разделения команд и запросов. Все команды и их обработчики сгруппированы в отдельные директории по функциональности, что упрощает их поиск и поддержку и несколько напоминает подход Vertical slice:
src
├── Application
│ ├── Features
│ │ ├── Bot
│ │ │ ├── StartBotCommand.cs
│ │ │ ├── StartBotCommandHandler.cs
...
│ │ ├── Weather
│ │ │ ├── WeatherBotCommand.cs
│ │ │ ├── WeatherBotCommandHandler.cs
Data — отвечает за доступ к данным и взаимодействие с базой данных.
Реализует репозитории, использующие Entity Framework для выполнения операций с базой данных.
Содержит миграции базы данных и конфигурации сущностей.
Domain — содержит основные сущности и интерфейсы, используемые в приложении.
Определяет модели данных, интерфейсы репозиториев и другие абстракции, которые помогают отделить бизнес-логику от деталей реализации.
Framework — содержит вспомогательные библиотеки и утилиты, используемые в проекте.
Общие классы, расширения, обработчики исключений и другие компоненты, которые помогают упростить разработку и поддержку приложения.
Взаимодействие компонентов
Запуск приложения: Проект
Host
инициализирует все необходимые сервисы и запускает приложение.Обработка команд: Когда пользователь отправляет команду боту, она попадает в слой
Application
, где соответствующий обработчик команды выполняет бизнес-логику.Доступ к данным: Если обработчику команды необходимо взаимодействовать с базой данных, он использует репозитории из слоя
Data
.Использование сущностей: Репозитории и обработчики команд работают с сущностями и интерфейсами из слоя
Domain
.Вспомогательные функции: В процессе работы приложения используются утилиты и библиотеки из слоя
Framework
.
Данная архитектура позволяет легко масштабировать и поддерживать приложение, разделяя ответственность между различными слоями и обеспечивая гибкость и модульность кода.
Зависимости между слоями выстроены по правилам чистой архитектуры. Например, слой Application
зависит от слоя Domain
, но не зависит от слоя Data
.
Инфраструктура бота
Конфигурирование
Для полноценной работы бота необходимо настроить токен API Telegram (получение самого токена опустим, т.к. данная тема хорошо раскрыта в официальной документации) и список основных команд. Этой цели служит класс TelegramBotSetup, исполняемый как hosted-сервис при запуске приложения.
Для каждого поддерживаемого языка определяются команды, которые будут отображаться в списке доступных команд бота непосредственно в приложении Telegram.
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
}
}
Получение обновлений от Telegram
Для получения обновлений от Telegram используется подход Polling, который позволяет боту регулярно проверять наличие новых сообщений и обновлений. Такой подход удобен для небольших проектов и не требует настройки веб-хуков. Тем не менее, реализовать полноценный веб-хук тоже не составит труда (см пример).
За получение обновлений от Telegram отвечает hosted-сервис UpdateReceiver, который регулярно запрашивает обновления через API Telegram, инициализирует экземпляр класса WeatherBot и передает ему полученные данные.
Обработка запросов
Центральной точкой функционирования бота является класс WeatherBot, наследующий библиотечный класс SimpleTelegramBotBase.
Именно он отвечает за обработку команд, callback’ов и биллинга. Для простоты восприятия, данный класс разбит на несколько частей, каждая из которых отвечает за определенный функционал:
Преобразование обновления или callback’а из Telegram в команду и её отправка в MediatR
Обработка ошибок
Обработка платежей
т.д.
Обработка команд
Основная задача бота — это обработка сообщений/команд, которые пользователи отправляют боту для выполнения определенных действий. В рассматриваемом примере команды обрабатываются с использованием паттерна CQS и MediatR.
Диаграмма последовательности обработки команд
Каждая команда Telegram или её callback имеют соответствующий класс, реализующий интерфейс IBotCommand или ICallbackCommand. Например, StartBotCommand:
public sealed record StartBotCommand(Message Message, UserInfo UserInfo) : IBotCommand
{
public static string CommandName => "start";
}
public interface IBotCommand : IRequest
{
static abstract string CommandName { get; }
static virtual bool AllowGroups => true;
static virtual IReadOnlyList Roles { get; } = Array.Empty();
public Message Message { get; init; }
public UserInfo UserInfo { get; init; }
}
Имя команды определяется статическим свойством
CommandName
.Соответствие имени и самой команды автоматически кешируется приложением для обеспечения быстрой инициализации команд.
Дополнительно можно переопределить свойства
AllowGroups
иRoles
для управления доступом к команде в разрезе групповых чатов и ролей пользователей.
Обработка команды происходит в соответствующем MediatR-обработчике, который выполняет некоторые вычисления и отправляет готовый результат пользователю. Например, StartBotCommandHandler:
internal sealed class StartBotCommandHandler : IRequestHandler
{
private readonly ITelegramBotClient _telegramBotClient;
private readonly IBotMessageLocalizer _botMessageLocalizer;
public StartBotCommandHandler(
ITelegramBotClient telegramBotClient,
IBotMessageLocalizer botMessageLocalizer)
{
_telegramBotClient = telegramBotClient;
_botMessageLocalizer = botMessageLocalizer;
}
public async Task 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;
}
}
Также можно заметить, что в обработчике реализована поддержка локализации сообщений (через стандартные возможности .NET и ресурсные файлы)), что позволяет отправлять сообщения на разных языках в зависимости от настроек пользователя. Информация о пользователе, его языковых предпочтениях и ролях также является частью команды — эти данные заполняются автоматически при создании экземпляра класса.
Опустим в данной статье ролевую систему, вопрос локализации и платежей, так как это не является ключевым аспектом рассматриваемого примера. При желании, вы можете изучить соответствующие классы и интерфейсы в репозитории и самостоятельно запустить приложение.
Заключение
Мы рассмотрели создание Telegram-бота на платформе .NET с использованием стека современных библиотек и технологий. Пример реализации демонстрирует архитектурные подходы, обеспечивающие модульность, гибкость и расширяемость решения. Это позволяет разработчикам сосредоточиться на бизнес-логике и легко добавлять новые команды и функции, минимизируя связанные изменения кода.
Если у вас есть вопросы или предложения по улучшению решения, не стесняйтесь обращаться к автору или создавать issue в репозитории.