29 декабря 2023
Тэги: Kotlin, Spring Boot, руководство.
В предыдущей статье Telegram-бот на Spring Boot мы написали telegram-бота на Kotlin и Spring Boot. Мы рассмотрели, как добавляются простые команды, команды с параметрами, а также научились отображать простые кнопки в клиентском приложении telegram.
Но обычные кнопки являются не более чем заранее подготовленными текстовыми сообщениями пользователя. Они не несут никакого контекста и потому не подходят для более сложных сценариев взаимодействия. Однако пришла пора добавить в наш бот чуть больше интерактива!
Предположим, вы решили написать онлайн-квиз в виде Telegram-бота. Бот должен выдать вопрос и какие-то варианты ответа к нему. Если это делать с помощью обычных кнопок, то довольно сложно сопоставить ответ пользователя с конкретным вопросом, т.к. обычные кнопки не хранят контекст. Но есть встраиваемые (inline) кнопки, которые относятся непосредственно к сообщению. Они добавляют гораздо больше интерактива и именно их вы видели, когда регистрировали бота в BotFather.
Встроенные кнопки взаимодействуют с ботом в фоновом режиме при помощи обратных вызовов (callback). Поэтому имеет смысл создать отдельный набор классов и интерфейсов для их поддержки.
HandlerName – это enum с именами обратных вызовов. Аналог CommandName.
В данном случае добавим имя для обработки ответа на вопросы из нашего квиза.
Далее создадим базовый интерфейс CallbackHandler (аналог BotCommand).
Теперь доработаем основной класс DevmarkBot для поддержки обратных вызовов. Аналогично командам, будем передавать в конструктор все реализации только что созданного интерфейса и в секции init будем строить маппинг имени обработчика на его реализацию с помощью метода associateBy(). Этот маппинг будем хранить в поле handlerMapping.
Также потребуется добавить новую ветку в метод processNonCommandUpdate():
Здесь мы добавили новую проверку hasCallbackQuery(), внутри которой извлекаем контекст callbackData. Технически он представляет собой строку и вы можете поместить туда всё что угодно. Это именно тот самый контекст, которого нам не хватает в функционале обычных кнопок.
Перед началом обработки запроса нам нужно отправить клиентскому приложению AnswerCallbackQuery. Мы как бы говорим, что запрос принят и мы начали его обработку. Если этого не сделать, то клиент будет видеть анимацию обработки запроса и подумает, что наше приложение «зависло».
Далее начинаем извлекать информацию из callbackData. Формат может быть любым, но при этом должен быть единым для всех ваших callback-ов. Я здесь разделяю параметры вертикальной чертой, причём первым параметром всегда ставлю текстовое имя из HandlerName. По этому имени я извлекаю нужный обработчик из handlerMapping и передаю все остальные аргументы кроме первого внутрь этого обработчика.
Теперь инфраструктура нашего бота полностью поддерживает обработку inline-кнопок. Сначала создадим обработчик для команды /quiz, который будет выводить вопрос викторины и варианты ответов. В нашем примере это хардкод, а в реальности мы считывали бы эти вопросы из базы данных.
Здесь мы используем метод createMessageWithInlineButtons(), который принимает не просто список списков строк, а принимает пары ключ-значение. Где ключом является то, что будет передано в callbackData, а значением – надпись на кнопке.
В данном случае callbackData формируется как имя обработки обратного вызова и буква варианта ответа, разделённые вертикальной чертой.
Утилитарные методы формирования inline-кнопок похожи на уже рассмотренные ранее.
Теперь осталось написать обработчик QuizAnswerHandler для проверки правильности ответа.
Здесь мы сначала отправляем EditMessageReplyMarkup с помощью absSender. Он убирает кнопки с вариантами ответа из конкретного сообщения, т.к. если один из вариантов выбран, то кнопки больше не нужны. Мы просто передаём в getInlineKeyboard() пустой список. Сходным образом можно отредактировать исходное сообщение. Например, зафиксировать вариант ответа.
Далее извлекаем из аргументов обратного вызова выбранный вариант ответа. Помним, что первым аргументом изначально было имя обратного вызова, но мы его отсекаем на уровне DevmarkBot. Проверяем вариант и отправляем новое сообщение в зависимости от правильности ответа.
В реальном квизе скорее всего потребуется также передавать ещё и номер вопроса.
Как видите, Spring Boot позволяет запустить чат-бот с минимальным количеством усилий. Я постарался предложить простую, но вполне рабочую заготовку для типового telegram-бота на основе собственного практического опыта. Вам же остаётся только добавить обработчики команд и обратных вызовов согласно вашим требованиям.
Подавляющее большинство чат-ботов работает именно с текстовой информацией. Помимо применения в бизнесе также известно немало успешных примеров реализации текстовых игр и даже MMORPG. Именно поэтому чат-боты Telegram сейчас используются повсеместно.
Kotlin, Java, Spring, Spring Boot, Spring Data, SQL, PostgreSQL, Oracle, Linux, Hibernate, Collections, Stream API, многопоточность, файлы, Nginx, Apache, maven, gradle, JUnit, YouTube, новости, руководство, ООП, алгоритмы, головоломки, rest, GraphQL, Excel, XML, json, yaml.