NJ Soft

Используем Symfony для создания Telegram-бота

Используем Symfony для создания Telegram-бота

В предыдущей статье «Краткий обзор подсистемы команд в Symfony», мы рассмотрели возможности «команд» и на «синтетических примерах» рассказали об удобстве применения.

В этой статье покажем, как ещё могут быть использованы команды. Для живого примера возьмём задачу создания telegram-бота. Функционал бота будет простой, всего на пару команд: отреагировать на команду /start (Запустить), запросив код подтверждения, и обработать ввод кода подтверждения, проверив корректность формата и актуальность кода.

Пример сценария:

> (пользователь) /start (Запустить)
> (бот) Привет! Введите ваш код подтверждения в формате XXX-XXX.
> (пользователь) 123-0
> (бот) Неверный формат, введите код в формате XXX-XXX.
> (пользователь) 123-123
> (бот) Спасибо! Проверяю ваш код!
> (бот) Код верный!

Главная цель – показать, как используется Symfony Framework и его компоненты (подсистемы).

Этап № 1 – получаем API token для телеграм бота

Для этого нам надо зайти в чат с @BotFather и выполнить команду /newbot.

Используем Symfony для создания Telegram-бота - изображение 1

Затем ввести имя бота и его никнэйм (username).

После этого в сообщении получим HTTP API token вида

Используем Symfony для создания Telegram-бота - изображение 0

Этап № 2 – решаем, как хранить HTTP API token внутри Symfony

Если хотим в будущем менять токен или задавать его через интерфейс (веб-интерфейс или интерфейс командой строки), то лучше хранить токен в базе данных или в локальном файле (если ваш проект не является распределённой системой и представляет собой единственный экземпляр на сервере). Если токен будет очень редко меняться, и при этом за его изменение ответственным будет назначаться программист, и при этом проект не является распределённой системой, то хранить переменные окружения нужно в .env файле.

Для упрощения будем использовать .env и .env.local. Их различие в том, что в нашем случае .env будет попадать в git-репозиторий, а вот .env.local будет прописан в .gitignore и не будет попадать в git-репозиторий. Поэтому локально у нас может быть один токен, а когда выложим проект на сервер, в нём в .env.local пропишем другой токен.

Итого: в любом случае начинаем с хранения токена в .env.local, а дальше действуем по обстоятельствам, при этом в .env прописываем пустое значение.

Используем Symfony для создания Telegram-бота - изображение 2

Доставать токен в самом простом случае будем через ParameterBagInterface, для этого в config/services.yaml пропишем параметр:

Используем Symfony для создания Telegram-бота - изображение 3

Этап № 3 – создаём отправитель сообщений и проверяем отправку сообщений в Telegram.

Теперь, когда у нас есть токен, следует его проверить и отправить сообщение куда-либо, например себе в личные сообщения. Но как узнать свой chat_id?

В самом простом случае можно вместо chat_id указать свой username (никнэйм) и сообщение будет доставлено. Но если хотим всё унифицировать по integer chat_id, то пробуем узнать этот параметр из API телеграма через HTTP-запрос.

  1. Запускаем общение с ботом через кнопку «Запустить» и отправляем ему любое текстовое сообщение.
  2. Идём по HTTP API ссылке – можно через браузер, но я в примере буду использовать curl + jq и терминал.

Используем Symfony для создания Telegram-бота - изображение 4

И ищу своё сообщение (text: «моё сообщение»), в данном примере это будет слово «test».

Используем Symfony для создания Telegram-бота - изображение 5

Отсюда получаем нужный нам «chat.id», где полный json-путь до него это - «message.chat.id».
Этот chat.id будем использовать для тестирования, а в самом телеграме будем проверять получение сообщения и его формат.

Теперь идём в Symfony и создаём «сервис отправки сообщений в телеграм».

Создаём файл src/Service/TelegramSender.php с кодом:

Используем Symfony для создания Telegram-бота - изображение 6

Представленный код умеет логировать ошибки настройки проекта или ошибку аргумента метода , а также ошибки получения ответа != 200 ОК. Такое логирование полезно в режиме production – можно быстрее понять, где ошибка, и что она вообще есть. Исключительные и непредвиденные ошибки тоже будем отслеживать, но чуть ниже.

На данном этапе нужно убедиться только в возможности отправки сообщений.

Создаём код («Symfony команду»), в которой протестируем отправку сообщений и затем будем использовать эту команду для обработки запуска бота (/start).

Используем Symfony для создания Telegram-бота - изображение 7

Идём в терминал и вызываем команду:

bin/console app:telegram:command:start {message.chat.id}

Если сообщение отправляется, то необходимо «допилить код» так, чтобы при передаче «параметра эмуляции отправки сообщения» сообщение не уходило по-настоящему, а лишь выводило результат в терминал. Это поможет нам отлаживать и тестировать приложение на различных условиях без спама в настоящего бота.

Для этого допишем код так:
src/Service/TelegramSender.php – изменения отмечены красными точками:

Используем Symfony для создания Telegram-бота - изображение 8

И дорабатываем команду:

Используем Symfony для создания Telegram-бота - изображение 9

Теперь выполняем команду и проверяем что сообщение не отправляется в телеграм, а выводится в терминал.

bin/console app:telegram:command:start --simulate=1 {message.chat.id}

Мы должны увидеть одно жёлтое сообщение и строку из константы MESSAGE_START.

Если всё это происходит, то переходим к следующему этапу.

Этап № 4 – создаём и задаём webhook для обработки диалогов с телеграм ботом

Для начала надо зарегистрировать URL, на который телеграм будет «перенаправлять сообщения к боту» (или «сообщения чата в котором состоит бот») на этот URL.

Картинка для понимания:

Используем Symfony для создания Telegram-бота - изображение 10

Предположим, что наш URL будет такой /api/pub/telegram/webhook/

Значит вызываем метод:

https://api.telegram.org/bot{HTTP_API_TOKEN}/setWebhook?url=/api/pub/telegram/webhook/

Рекомендуется обратить внимание на параметры этого метода: https://core.telegram.org/bots/api#setwebhook

Там есть полезные параметры, такие как: secret_token (позволяет защитить обращения к URL секретным словом, которое будет приходить в заголовках запроса), а также max_connections (снижает нагрузку на сервер) и ip_address (защищает от подмены DNS).

Теперь в Symfony создаём маршрут (роут) /api/pub/telegram/webhook/ , который должен принимать POST-запросы.

Используем Symfony для создания Telegram-бота - изображение 11

В данном коде мы используем возможность Symfony вызывать команду из контроллера, передавая параметры, сформированные из HTTP-запроса. В данном примере параметры мы получаем из JSON объекта «Update»:
https://core.telegram.org/bots/api#update

С помощью десериализации JSON→PPO читаем параметры JSON. Если вам не нужна десериалзиация в PHP-объекты, то можно воспользоваться обычным json_decode. Вот так:

Используем Symfony для создания Telegram-бота - изображение 12

Минус такого подхода в том, что не получится провести валидацию каких-либо значений параметров запроса при десериализации (точнее получится, но это будет набор if else isset и т. д.).

На этом этапе наш бот сможет уже отвечать на команду /start , и при этом мы сможем тестировать наш код локально без взаимодействия с API телеграм.

Этап № 5 – обработка ввода и контекст общения

Теперь добавим команду обработки ввода кода, проверки его формата и проверки его актуальности. Для этого создадим Symfony-команду, чтобы её можно было вызывать вот так:

bin/console app:telegram:command:dialog {chat_id} {Текст_сообщения}

Команду назовём DialogCommand, потому что она будет реагировать на любой ввод пользователя. Для этого добавим обработку этой команды в контроллере-обработчике веб-хука /api/pub/telegram/webhook/.

Используем Symfony для создания Telegram-бота - изображение 13

Теперь любое сообщение попадает в app:telegram:command:dialog.
* Стоит уделить внимание тому, что сообщения в телеграме могут быть в виде изображений, тогда в getText() будет пусто. Т.к это лишь пример, то тут пропускается обработка медиа-сообщений и т.д.

Основной код DialogCommand:

Используем Symfony для создания Telegram-бота - изображение 14

На что тут следует обратить внимание?

  • В switch идёт обработка «контекста диалога», то есть его состояния
  • По умолчанию на любой ввод пользователя, если «контекст диалога» прошёл проверку кода, выводится сообщение «Реакция на сообщение {исходное_сообщение}»
  • Появился TelegramDialogContext – это сущность (запись в таблице БД), которую мы создаём для chatId, если её нет, и, если есть, вытягиваем по chatId. Создаём мы её в состоянии STATE_START

Используем Symfony для создания Telegram-бота - изображение 15

Используем Symfony для создания Telegram-бота - изображение 16

В этой сущности можно хранить состояние диалога. Она должна быть уникальна на chatId. С помощью этой сущности (записи в БД) можно отслеживать и менять состояние диалога.

Итоги

С помощью представленного выше подхода создания ботов можно покрывать различные сценарии и различные мессенджеры, а также API внешних систем. Какие могут быть альтернативы?

В некоторых случаях, когда нужно набросать «MVP-версию бота» под разные мессенджеры (именно мессенджеры), то можно воспользоваться библиотекой «BotMan» ( https://botman.io/ ) . Но нужно понимать, что если у вас уже есть Symfony-проект, то интеграция библиотеки и работа с ней потребует знаний и времени на её изучение и построение архитектуры внутри вашего проекта конкретно под библиотеку BotMan.

Поэтому представленный подход через Symfony-команды имеет преимущества в быстром старте и понятном коде. Вам понадобится обладать знаниями только для Symfony Framework и API-нужного мессенджера, который, как правило, представлен в отдельных библиотеках. К тому же когда API обновляется или функции внутри мессенджера поменяются, то BotMan придёться форкать и дорабатывать отдельно.

P.S. Приходите к нам в NJ Soft за автоматизацией ваших проектов!

  • Приглашаем подписаться на наши страницы в социальных сетях:

    Telegram
    Вконтакте
Все статьи →
Приглашаем к сотрудничеству
130+
завершённых проектов
20+ лет
помогаем клиентам расти
127473, Москва, улица Селезневская, дом 32
Заполните форму и мы свяжемся с вами в ближайшее время!
Согласен с обработкой моих персональных данных
в соответствии с политикой конфиденциальности
127473, Россия, Москва, улица Селезневская, дом 32, офис 302
+7 (495) 477-60-74