Введение

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

Основные особенности рассматриваемой реализации

  • Технические:

  • Функциональные:

    • Получение обновлений через Polling (Webhook не рассматривается в целях простоты).

    • Отправка пользователю информации о погоде в различных городах. Текущая температура генерируется случайным образом.

    • Поддержка пожертвований в виде Telegram Stars.

    • Ролевая система для управления доступом к командам.

    • Поддержка пользовательских настроек.

    • Локализация интерфейса и сообщений.

    • Inline-функции для быстрого доступа к информации.

Архитектура решения

Общая структура

Решение разделено на несколько слоев, каждый из которых отвечает за свою функциональность.

  1. Host/AppHost - отвечает за запуск приложения.

    • Содержит точку входа и конфигурацию всех необходимых сервисов, таких как MediatR, Entity Framework и OpenTelemetry.

    • Настраивает подключение к базе данных и инициализирует бота.

    • AppHost также позволяет запустить приложение и все зависимости с помощью .NET Aspire.

  2. Application - содержит бизнес-логику приложения.

    • Здесь находятся обработчики команд, которые обрабатывают запросы пользователей и выполняют соответствующие действия.

    • Реализует паттерны CQS и Mediator для разделения команд и запросов. Все команды и их обработчики сгруппированы в отдельные директории по функциональности, что упрощает их поиск и поддержку и несколько напоминает подход Vertical slice:

src
├── Application
│   ├── Features
│   │   ├── Bot
│   │   │   ├── StartBotCommand.cs
│   │   │   ├── StartBotCommandHandler.cs
...
│   │   ├── Weather
│   │   │   ├── WeatherBotCommand.cs
│   │   │   ├── WeatherBotCommandHandler.cs
  1. Data - отвечает за доступ к данным и взаимодействие с базой данных.

    • Реализует репозитории, использующие Entity Framework для выполнения операций с базой данных.

    • Содержит миграции базы данных и конфигурации сущностей.

  2. Domain - содержит основные сущности и интерфейсы, используемые в приложении.

    • Определяет модели данных, интерфейсы репозиториев и другие абстракции, которые помогают отделить бизнес-логику от деталей реализации.

  3. Framework - содержит вспомогательные библиотеки и утилиты, используемые в проекте.

    • Общие классы, расширения, обработчики исключений и другие компоненты, которые помогают упростить разработку и поддержку приложения.

Взаимодействие компонентов

  1. Запуск приложения: Проект Host инициализирует все необходимые сервисы и запускает приложение.

  2. Обработка команд: Когда пользователь отправляет команду боту, она попадает в слой Application, где соответствующий обработчик команды выполняет бизнес-логику.

  3. Доступ к данным: Если обработчику команды необходимо взаимодействовать с базой данных, он использует репозитории из слоя Data.

  4. Использование сущностей: Репозитории и обработчики команд работают с сущностями и интерфейсами из слоя Domain.

  5. Вспомогательные функции: В процессе работы приложения используются утилиты и библиотеки из слоя 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 в репозитории.

Дополнительные материалы

Комментарии (10)


  1. jackshrike
    01.11.2024 19:50

    есть 2 вопроса:

    1. если дуровграм запретят, что вы будете делать ?

    2. если дуровграм не запретят и окажется, что он сливал информацию именно из вашего бота - что вы будете делать ?


    1. alex_ozr Автор
      01.11.2024 19:50

      В контексте примера и данной статьи - ничего.

      А вы? :)


      1. jackshrike
        01.11.2024 19:50

        1. а если расширить контекст ?

        2. а я первый спросил. но от ответа в отличие от вас уходить не буду. я удалю телеграм со своего смартфона, посоветую сделать то же самое всем контактам и буду издеваться над ботостроителями. на замену телеграму возьму что-нибудь менее скомпроментированное типа delta chat.


        1. Cregennan
          01.11.2024 19:50

          А если выключат электричество в городе, интернет/электричество для смартфона откуда будете брать? А если мощная магнитная буря выжжет всю электронику на земле? А если метеорит упадет? Нужно что-то менее электрозависимое, например голуби


          1. jackshrike
            01.11.2024 19:50

            если выключат электричество в городе,

            сперва два UPS через АВР потом ДГУ (если они есть и в рабочем состоянии).

            интернет/электричество для смартфона откуда будете брать?

            на Украине откуда-то брали. нужно изучать их опыт.

            А если мощная магнитная буря выжжет всю электронику на земле?

            это фантастика.
            и наоборот, Дуров сливал и банил каких-то "террористов" до 202х и сотрудничает с органами прямо сейчас - это реальность, в каковую реальнсть я и предлагаю вернуться фанатам экококосистемы дуровграма.


  1. PaZiTiVe
    01.11.2024 19:50

    Благодарю за мануал. Попробую разобраться) Как раз сейчас учусь кодить и делаю TG бота, правда использую библиотеку Telegram.Bot


  1. Actor
    01.11.2024 19:50

    Это всё здорово, но основная проблема в боте не то как отправить сообщение или его принять. А то как управлять навигацией и стейтами, для ботов с вложенными иерархиями команд


    1. alex_ozr Автор
      01.11.2024 19:50

      Ну как же, колбэки поддерживаются и обрабатываются также, как и команды.

      Хранение состояний тоже есть - те же роли пользователя и его языковые предпочтения (которые устанавливаются отдельной командой). Для начала и как демонстрация - этого вполне должно хватить, как мне кажется.

      Но, вероятно, надо было чуть более подробно рассказать об этом в статье, да :)


    1. Vinegar
      01.11.2024 19:50

      Конечно же офигительно большим if/else и огроменной коллекцией флагов состояния!


  1. IamStalker
    01.11.2024 19:50

    Несколько вопросов:
    1. Если это туториал где все настройки и упущено много материала?
    2. Где следующая часть этого?
    Мне например нравятся такого типа статьи. Спасибо.