Введение
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-репозиторий с примером реализации.
Комментарии (10)
PaZiTiVe
01.11.2024 19:50Благодарю за мануал. Попробую разобраться) Как раз сейчас учусь кодить и делаю TG бота, правда использую библиотеку Telegram.Bot
Actor
01.11.2024 19:50Это всё здорово, но основная проблема в боте не то как отправить сообщение или его принять. А то как управлять навигацией и стейтами, для ботов с вложенными иерархиями команд
alex_ozr Автор
01.11.2024 19:50Ну как же, колбэки поддерживаются и обрабатываются также, как и команды.
Хранение состояний тоже есть - те же роли пользователя и его языковые предпочтения (которые устанавливаются отдельной командой). Для начала и как демонстрация - этого вполне должно хватить, как мне кажется.
Но, вероятно, надо было чуть более подробно рассказать об этом в статье, да :)
Vinegar
01.11.2024 19:50Конечно же офигительно большим if/else и огроменной коллекцией флагов состояния!
IamStalker
01.11.2024 19:50Несколько вопросов:
1. Если это туториал где все настройки и упущено много материала?
2. Где следующая часть этого?
Мне например нравятся такого типа статьи. Спасибо.
jackshrike
есть 2 вопроса:
если дуровграм запретят, что вы будете делать ?
если дуровграм не запретят и окажется, что он сливал информацию именно из вашего бота - что вы будете делать ?
alex_ozr Автор
В контексте примера и данной статьи - ничего.
А вы? :)
jackshrike
а если расширить контекст ?
а я первый спросил. но от ответа в отличие от вас уходить не буду. я удалю телеграм со своего смартфона, посоветую сделать то же самое всем контактам и буду издеваться над ботостроителями. на замену телеграму возьму что-нибудь менее скомпроментированное типа delta chat.
Cregennan
А если выключат электричество в городе, интернет/электричество для смартфона откуда будете брать? А если мощная магнитная буря выжжет всю электронику на земле? А если метеорит упадет? Нужно что-то менее электрозависимое, например голуби
jackshrike
сперва два UPS через АВР потом ДГУ (если они есть и в рабочем состоянии).
на Украине откуда-то брали. нужно изучать их опыт.
это фантастика.
и наоборот, Дуров сливал и банил каких-то "террористов" до 202х и сотрудничает с органами прямо сейчас - это реальность, в каковую реальнсть я и предлагаю вернуться фанатам экококосистемы дуровграма.