Введение
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х и сотрудничает с органами прямо сейчас - это реальность, в каковую реальнсть я и предлагаю вернуться фанатам экококосистемы дуровграма.