Введение
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-репозиторий автора с примером реализации бота, который предоставляет информацию о погоде в различных городах.
Основные особенности рассматриваемой реализации
-
Технические:
-
Современная версия платформы .NET 8.
-
.NET Aspire для развёртывания проекта и управления зависимостями.
-
PostgreSQL для хранения и Entity Framework Core 8 для работы с данными.
-
MediatR для реализации паттерна CQS и уменьшения связанности.
-
OpenTelemetry для логирования, трассировки и мониторинга.
-
Unit-тесты с использованием xUnit, FluentAssertions и Moq.
-
Архитектурные тесты для проверки соответствия кода чистой архитектуре.
-
-
Функциональные:
-
Получение обновлений через Polling (Webhook не рассматривается в целях простоты).
-
Отправка пользователю информации о погоде в различных городах. Текущая температура генерируется случайным образом.
-
Поддержка пожертвований в виде Telegram Stars.
-
Ролевая система для управления доступом к командам.
-
Поддержка пользовательских настроек.
-
Локализация интерфейса и сообщений.
-
Inline-функции для быстрого доступа к информации.
-
Архитектура решения
Общая структура
Решение разделено на несколько слоев, каждый из которых отвечает за свою функциональность.
-
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<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; } }
-
Имя команды определяется статическим свойством
CommandName
.-
Соответствие имени и самой команды автоматически кешируется приложением для обеспечения быстрой инициализации команд.
-
Дополнительно можно переопределить свойства
AllowGroups
иRoles
для управления доступом к команде в разрезе групповых чатов и ролей пользователей.
-
-
Обработка команды происходит в соответствующем MediatR-обработчике, который выполняет некоторые вычисления и отправляет готовый результат пользователю. Например, 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; } }
Также можно заметить, что в обработчике реализована поддержка локализации сообщений (через стандартные возможности .NET и ресурсные файлы)), что позволяет отправлять сообщения на разных языках в зависимости от настроек пользователя. Информация о пользователе, его языковых предпочтениях и ролях также является частью команды — эти данные заполняются автоматически при создании экземпляра класса.
Опустим в данной статье ролевую систему, вопрос локализации и платежей, так как это не является ключевым аспектом рассматриваемого примера. При желании, вы можете изучить соответствующие классы и интерфейсы в репозитории и самостоятельно запустить приложение.
Заключение
Мы рассмотрели создание Telegram-бота на платформе .NET с использованием стека современных библиотек и технологий. Пример реализации демонстрирует архитектурные подходы, обеспечивающие модульность, гибкость и расширяемость решения. Это позволяет разработчикам сосредоточиться на бизнес-логике и легко добавлять новые команды и функции, минимизируя связанные изменения кода.
Если у вас есть вопросы или предложения по улучшению решения, не стесняйтесь обращаться к автору или создавать issue в репозитории.
Дополнительные материалы
-
GitHub-репозиторий с примером реализации.
ссылка на оригинал статьи https://habr.com/ru/articles/855236/
Добавить комментарий