Глава 3 Как добавить боту поддержку клавиатуры
В этой главе мы познакомимся с важным аспектом разработки Telegram-ботов - добавлением поддержки клавиатуры для более удобного взаимодействия пользователей с ботом.
Из этой главы вы узнаете:
- О двух основных типах клавиатур в Telegram: Reply и Inline
- Как создавать и настраивать Reply-клавиатуру под панелью ввода текста
- Как реализовать Inline-клавиатуру, привязанную к конкретным сообщениям бота
- Как обрабатывать нажатия на кнопки клавиатур с помощью пакета telegram.bot для R
- Как структурировать код бота для работы с клавиатурами
- Практические примеры реализации ботов с клавиатурами для различных задач
Я подробно разберу процесс создания обоих типов клавиатур, приведу примеры кода для реализации нескольких полезных ботов. Вы познакомитесь с ботом для экспресс-тестирования на COVID-19, ботом для получения информации о погоде и ботом для вывода списка свежих статей с Хабра. Я дам детальные объяснения структуры кода, функций для создания кнопок и обработки нажатий, а также рекомендации по построению логики работы бота с клавиатурами.
Освоив материал этой главы, вы сможете значительно улучшить удобство использования своих Telegram-ботов, сделав взаимодействие с ними более простым и интуитивно понятным для пользователей.
3.2 Какие типы клавиатур поддерживает телеграм бот
На момент написания книги telegram.bot
позволяет вам создать клавиатуры двух типов:
- Reply - Основная, обычная клавиатура, которая находится под панелью ввода текста сообщения. Такая клавиатура просто отправляет боту текстовое сообщение, и в качестве текста отправит тот текст, который написан на самой кнопке.
- Inline - Клавиатура привязанная к конкретному сообщению бота. Данная клавиатура отправляет боту данные, привязанные к нажатой кнопке, эти данные могут отличаться от текста, написанного на самой кнопке. И обрабатываются такие кнопки через CallbackQueryHandler.
Для того, что бы бот открыл клавиатуру необходимо при отправке сообщения через метод sendMessage()
, передать созданную ранее клавиатуру в аргумент reply_markup
.
Ниже мы разберём несколько примеров.
3.4 Inline клавиатура
Как я уже писал выше, Inline клавиатура привязана к конкретному сообщению. С ней работать несколько сложнее чем с основной клавиатурой.
Изначально вам необходимо добавить боту метод, для вызова Inline клавиатуры.
Для ответа на нажатие Inline кнопки также можно использовать метод бота answerCallbackQuery()
, который может вывести уведомление в интерфейсе telegram, пользователю нажавшему Inline кнопку.
Данные отправленные с Inline кнопки не являются текстом, поэтому для их обработки необходимо создать специальный обработчик с помощью команды CallbackQueryHandler()
.
Код построения Inline клавиатуры который приводится в официальной справке пакета telegram.bot
.
# Initialize bot
bot <- Bot(token = "TOKEN")
chat_id <- "CHAT_ID"
# Create Inline Keyboard
text <- "Could you type their phone number, please?"
IKM <- InlineKeyboardMarkup(
inline_keyboard = list(
list(
InlineKeyboardButton(1),
InlineKeyboardButton(2),
InlineKeyboardButton(3)
),
list(
InlineKeyboardButton(4),
InlineKeyboardButton(5),
InlineKeyboardButton(6)
),
list(
InlineKeyboardButton(7),
InlineKeyboardButton(8),
InlineKeyboardButton(9)
),
list(
InlineKeyboardButton("*"),
InlineKeyboardButton(0),
InlineKeyboardButton("#")
)
)
)
# Send Inline Keyboard
bot$sendMessage(chat_id, text, reply_markup = IKM)
Строить Inline клавиатуру необходимо с помощью команды InlineKeyboardMarkup()
, по такому же принципу, как и Reply клавиатуру. В InlineKeyboardMarkup()
необходимо передать список, списков Inline кнопок, каждая отдельная кнопка создаётся функцией InlineKeyboardButton()
.
Inline кнопка может либо передавать боту какие-то данные с помощью аргумента callback_data
, либо открывать какую-либо HTML страницу, заданную с помощью аргумента url
.
В результате будет список, в котором каждый элемент так же является списком Inline кнопок, которые необходимо объединить в один ряд.
Далее мы рассмотрим несколько примеров ботов с Inline кнопками.
3.4.1 Пример простейшего бота с поддержкой InLine кнопок
Для начала мы напишем бота для экспресс тестирования на covid-19. По команде /test
, он будет отправлять вам клавиатуру с двумя кнопками, в зависимости от нажатой кнопки он будет присылать вам сообщение с результатами вашего тестирования.
library(telegram.bot)
# создаём экземпляр класса Updater
updater <- Updater('ТОКЕН ВАШЕГО БОТА')
# метод для отправки InLine клавиатуры
test <- function(bot, update) {
# создаём InLine клавиатуру
IKM <- InlineKeyboardMarkup(
inline_keyboard = list(
list(
InlineKeyboardButton("Да", callback_data = 'yes'),
InlineKeyboardButton("Нет", callback_data = 'no')
)
)
)
# Отправляем клавиатуру в чат
bot$sendMessage(update$from_chat_id(),
text = "Вы болете коронавирусом?",
reply_markup = IKM)
}
# метод для обработки нажатия кнопки
answer_cb <- function(bot, update) {
# полученные данные с кнопки
data <- update$callback_query$data
# получаем имя пользователя, нажавшего кнопку
uname <- update$effective_user()$first_name
# обработка результата
if ( data == 'no' ) {
msg <- paste0(uname, ", поздравляю, ваш тест на covid-19 отрицательный.")
} else {
msg <- paste0(uname, ", к сожалени ваш тест на covid-19 положительный.")
}
# Отправка сообщения
bot$sendMessage(chat_id = update$from_chat_id(),
text = msg)
# сообщаем боту, что запрос с кнопки принят
bot$answerCallbackQuery(callback_query_id = update$callback_query$id)
}
# создаём обработчики
inline_h <- CommandHandler('test', test)
query_handler <- CallbackQueryHandler(answer_cb)
# добавляем обработчики в диспетчер
updater <- updater + inline_h + query_handler
# запускаем бота
updater$start_polling()
Запустите приведённый выше пример кода, предварительно заменив ‘ТОКЕН ВАШЕГО БОТА’ на реальный токен, который вы получили при создании бота через BotFather.
Мы создали два метода:
- test - Для отправки в чат Inline клавиатуры
- answer_cb - Для обработки отправленных с клавиатуры данных.
Данные, которые будут отправлены с каждой кнопки задаются в аргументе callback_data
, при создании кнопки. Получить отправленные с кнопки данные можно с помощью конструкции update$callback_query$data
, внутри метода answer_cb.
Что бы бот реагировал на Inline клавиатуру, метод answer_cb обрабатывается специальным обработчиком: CallbackQueryHandler(answer_cb)
. Который запускает указанный метод по нажатию Inline кнопки. Обработчик CallbackQueryHandler принимает два аргумента:
-
callback
- Метод который необходимо запустить -
pattern
- Фильтр по данным, которые привязаны к кнопке с помощью аргументаcallback_data
.
Соответвенно с помощью аргумента pattern
мы можем под нажатие каждой кнопки написать отдельный метод:
library(telegram.bot)
# создаём экземпляр класса Updater
updater <- Updater('ТОКЕН ВАШЕГО БОТА')
# метод для отправки InLine клавиатуры
test <- function(bot, update) {
# создаём InLine клавиатуру
IKM <- InlineKeyboardMarkup(
inline_keyboard = list(
list(
InlineKeyboardButton("Да", callback_data = 'yes'),
InlineKeyboardButton("Нет", callback_data = 'no')
)
)
)
# Отправляем клавиатуру в чат
bot$sendMessage(update$from_chat_id(),
text = "Вы болете коронавирусом?",
reply_markup = IKM)
}
# метод для обработки нажатия кнопки Да
answer_cb_yes <- function(bot, update) {
# получаем имя пользователя, нажавшего кнопку
uname <- update$effective_user()$first_name
# обработка результата
msg <- paste0(uname, ", к сожалени ваш текст на covid-19 положительный.")
# Отправка сообщения
bot$sendMessage(chat_id = update$from_chat_id(),
text = msg)
# сообщаем боту, что запрос с кнопки принят
bot$answerCallbackQuery(callback_query_id = update$callback_query$id)
}
# метод для обработки нажатия кнопки Нет
answer_cb_no <- function(bot, update) {
# получаем имя пользователя, нажавшего кнопку
uname <- update$effective_user()$first_name
msg <- paste0(uname, ", поздравляю, ваш текст на covid-19 отрицательный.")
# Отправка сообщения
bot$sendMessage(chat_id = update$from_chat_id(),
text = msg)
# сообщаем боту, что запрос с кнопки принят
bot$answerCallbackQuery(callback_query_id = update$callback_query$id)
}
# создаём обработчики
inline_h <- CommandHandler('test', test)
query_handler_yes <- CallbackQueryHandler(answer_cb_yes, pattern = 'yes')
query_handler_no <- CallbackQueryHandler(answer_cb_no, pattern = 'no')
# добавляем обработчики в диспетчер
updater <- updater +
inline_h +
query_handler_yes +
query_handler_no
# запускаем бота
updater$start_polling()
Запустите приведённый выше пример кода, предварительно заменив ‘ТОКЕН ВАШЕГО БОТА’ на реальный токен, который вы получили при создании бота через BotFather.
Теперь мы написали 2 отдельных метода, т.е. по одному методу, под нажатие каждой кнопки, и использовали аргумент pattern
, при создании их обработчиков:
query_handler_yes <- CallbackQueryHandler(answer_cb_yes, pattern = 'yes')
query_handler_no <- CallbackQueryHandler(answer_cb_no, pattern = 'no')
Заканчивается код метода answer_cb командой bot$answerCallbackQuery(callback_query_id = update$callback_query$id)
, которая сообщает боту, что данные с inline клавиатуры получены.
3.4.2 Пример бота, который сообщает текущую погоду по выбранному городу
Давайте попробуем написать бота, который запрашивает данные о погоде.
Логика его работы будет следующая. Изначально командой /start
вы вызываете основную клавиатуру, в которой присутствует всего одна кнопка “Погода”. Нажав на эту кнопку вы получаете сообщение с Inline клавиатурой, для выбора города, по которому требуется узнать текущую погоду. Выбираете один из городов, и получаете текущую погоду.
В этом примере кода мы будем использовать несколько дополнительных пакетов:
-
httr
- пакет для работы с HTTP запросами, на основе которых построена работа с любым API. В нашем случае мы будем использовать бесплатный API openweathermap.org. -
stringr
- пакет для работы с текстом, в нашем случае мы будем его использовать для формирования сообщения о погоде в выбранном городе.
Код бота, который сообщает текущую погоду по выбранному городу
library(telegram.bot)
library(httr)
library(stringr)
# создаём экземпляр класса Updater
updater <- Updater('ТОКЕН ВАШЕГО БОТА')
# создаём методы
## метод для запуска основной клавиатуры
start <- function(bot, update) {
# создаём клавиатуру
RKM <- ReplyKeyboardMarkup(
keyboard = list(
list(
KeyboardButton("Погода")
)
),
resize_keyboard = TRUE,
one_time_keyboard = TRUE
)
# отправляем клавиатуру
bot$sendMessage(update$from_chat_id(),
text = 'Выберите команду',
reply_markup = RKM)
}
## Метод вызова Inine клавиатуры
weather <- function(bot, update) {
IKM <- InlineKeyboardMarkup(
inline_keyboard = list(
list(
InlineKeyboardButton(text = 'Москва', callback_data = 'New York,us'),
InlineKeyboardButton(text = 'Санкт-Петербург', callback_data = 'Saint Petersburg'),
InlineKeyboardButton(text = 'Нью-Йорк', callback_data = 'New York')
),
list(
InlineKeyboardButton(text = 'Екатеринбург', callback_data = 'Yekaterinburg,ru'),
InlineKeyboardButton(text = 'Берлин', callback_data = 'Berlin,de'),
InlineKeyboardButton(text = 'Париж', callback_data = 'Paris,fr')
),
list(
InlineKeyboardButton(text = 'Рим', callback_data = 'Rome,it'),
InlineKeyboardButton(text = 'Одесса', callback_data = 'Odessa,ua'),
InlineKeyboardButton(text = 'Киев', callback_data = 'Kyiv,ua')
),
list(
InlineKeyboardButton(text = 'Токио', callback_data = 'Tokyo'),
InlineKeyboardButton(text = 'Амстердам', callback_data = 'Amsterdam,nl'),
InlineKeyboardButton(text = 'Вашингтон', callback_data = 'Washington,us')
)
)
)
# Send Inline Keyboard
bot$sendMessage(chat_id = update$from_chat_id(),
text = "Выберите город",
reply_markup = IKM)
}
# метод для сообщения погоды
answer_cb <- function(bot, update) {
# получаем из сообщения город
city <- update$callback_query$data
# отправляем запрос
ans <- GET('https://api.openweathermap.org/data/2.5/weather',
query = list(q = city,
lang = 'ru',
units = 'metric',
appid = '4776568ccea136ffe4cda9f1969af340'))
# парсим ответ
result <- content(ans)
# формируем сообщение
msg <- str_glue("{result$name} погода:\n",
"Текущая температура: {result$main$temp}\n",
"Скорость ветра: {result$wind$speed}\n",
"Описание: {result$weather[[1]]$description}")
# отправляем информацию о погоде
bot$sendMessage(chat_id = update$from_chat_id(),
text = msg)
bot$answerCallbackQuery(callback_query_id = update$callback_query$id)
}
# создаём фильтры
## сообщения с текстом Погода
MessageFilters$weather <- BaseFilter(function(message) {
# проверяем текст сообщения
message$text == "Погода"
}
)
# создаём обработчики
h_start <- CommandHandler('start', start)
h_weather <- MessageHandler(weather, filters = MessageFilters$weather)
h_query_handler <- CallbackQueryHandler(answer_cb)
# добавляем обработчики в диспетчер
updater <- updater +
h_start +
h_weather +
h_query_handler
# запускаем бота
updater$start_polling()
Запустите приведённый выше пример кода, предварительно заменив ‘ТОКЕН ВАШЕГО БОТА’ на реальный токен, который вы получили при создании бота через BotFather.
Мы создали 3 метода, доступные внутри нашего погодного бота:
- start - Запуск основной клавиатуры бота
- weather - Запуск Inline клавиатуры для выбора города
- answer_cb - Основной метод, который по заданному городу запрашивает в API погоду, и отправляет её в чат.
Метод start у нас запускается командой /start
, что реализовано обработчиком CommandHandler('start', start)
.
Для запуска метода weather мы создали одноимённый фильтр:
# создаём фильтры
## сообщения с текстом Погода
MessageFilters$weather <- BaseFilter(function(message) {
# проверяем текст сообщения
message$text == "Погода"
}
)
И вызываем этот метод следующим обработчиком сообщений: MessageHandler(weather, filters = MessageFilters$weather)
.
И в конце концов, основной наш метод answer_cb реагирует на нажатие Inline кнопок, что реализовано специальным обработчиком: CallbackQueryHandler(answer_cb)
.
Внутри метода answer_cb, мы считываем отправленные с клавиатуры данные и записываем их в переменную city
: city <- update$callback_query$data
. После чего запрашиваем из API данные о погоде, формируем и отправляем сообщение, и в конце концов используем метод answerCallbackQuery
для того, что бы сообщить боту, о том, что мы обработали нажатие Inline кнопки.
3.4.3 Пример бота, который выводит список самых свежих статей со ссылками по-указанному Хабу из habr.com
Данного бота я привожу для того, что бы показать вам, как вывести Inline кнопки которые ведут на веб страницы.
Логика данного бота схожа с предыдущим, изначально мы запускаем основную клавиатуру командой /start
. Далее бот даёт нам на выбор список из 6 хабов, мы выбираем интересующий нас хаб, и получаем 5 самых свежих публикаций из выбранного Хаба.
Как вы понимаете, в данном случае нам необходимо получить список статей, и для этого мы будем использовать пакет tidyRSS
, который позволяет вам получать RSS feed (ленту) сайта в виде обычного дата фрейма в R, к счастью Хабр предоставляет RSS фид под каждый отдельный хаб.
RSS — это файл в формате .XML или .RSS, который используют сайты, чтобы передавать пользователю информацию об обновлениях.
Для начала установим пакет tidyRSS
.
install.packages('`tidyRSS`')
Получить RRS Feed по какому то хабу можно примерно так:
Теперь рассмотрим код построения описанного выше бота:
Код бот который выводит список наиболее свежих статей по выбранному Хабу
library(telegram.bot)
library(tidyRSS)
# создаём экземпляр класса Updater
updater <- Updater('ТОКЕН ВАШЕГО БОТА')
# создаём методы
## метод для запуска основной клавиатуры
start <- function(bot, update) {
# создаём клавиатуру
RKM <- ReplyKeyboardMarkup(
keyboard = list(
list(
KeyboardButton("Список статей")
)
),
resize_keyboard = TRUE,
one_time_keyboard = TRUE
)
# отправляем клавиатуру
bot$sendMessage(update$from_chat_id(),
text = 'Выберите команду',
reply_markup = RKM)
}
## Метод вызова Inine клавиатуры
habs <- function(bot, update) {
IKM <- InlineKeyboardMarkup(
inline_keyboard = list(
list(
InlineKeyboardButton(text = 'R', callback_data = 'r'),
InlineKeyboardButton(text = 'Data Mining', callback_data = 'data_mining'),
InlineKeyboardButton(text = 'Data Engineering', callback_data = 'data_engineering')
),
list(
InlineKeyboardButton(text = 'Big Data', callback_data = 'bigdata'),
InlineKeyboardButton(text = 'Python', callback_data = 'python'),
InlineKeyboardButton(text = 'Визуализация данных', callback_data = 'data_visualization')
)
)
)
# Send Inline Keyboard
bot$sendMessage(chat_id = update$from_chat_id(),
text = "Выберите Хаб",
reply_markup = IKM)
}
# метод для сообщения погоды
answer_cb <- function(bot, update) {
# получаем из сообщения название хаба
hub <- update$callback_query$data
# сообщение о том, что данные по кнопке получены
bot$answerCallbackQuery(callback_query_id = update$callback_query$id,
text = 'Подождите несколько минут, запрос обрабатывается')
# сообщение о том, что надо подождать пока бот получит данные
mid <- bot$sendMessage(chat_id = update$from_chat_id(),
text = "Подождите несколько минут пока, я соберу данные по выбранному Хабу")
# запрашиваем RSS Feed по Хабу
url <- paste0('https://habr.com/ru/rss/hub/', hub, '/')
posts <- head(tidyfeed(url), 5)
# удаляем сообщение о том, что надо подождать
bot$deleteMessage(update$from_chat_id(), mid$message_id)
# формируем список кнопок
keys <- lapply(1:5, function(x) list(InlineKeyboardButton(posts$item_title[x], url = posts$item_link[x])))
# формируем клавиатуру
IKM <- InlineKeyboardMarkup(
inline_keyboard = keys
)
# отправляем информацию о погоде
bot$sendMessage(chat_id = update$from_chat_id(),
text = paste0("5 наиболее свежих статей из Хаба ", hub),
reply_markup = IKM)
}
# создаём фильтры
## сообщения с текстом Погода
MessageFilters$hubs <- BaseFilter(function(message) {
# проверяем текст сообщения
message$text == "Список статей"
}
)
# создаём обработчики
h_start <- CommandHandler('start', start)
h_hubs <- MessageHandler(habs, filters = MessageFilters$hubs)
h_query_handler <- CallbackQueryHandler(answer_cb)
# добавляем обработчики в диспетчер
updater <- updater +
h_start +
h_hubs +
h_query_handler
# запускаем бота
updater$start_polling()
Запустите приведённый выше пример кода, предварительно заменив ‘ТОКЕН ВАШЕГО БОТА’ на реальный токен, который вы получили при создании бота через BotFather.
Список доступных для выбора Хабов мы вбили хардкодом, в методе habs
:
## Метод вызова Inine клавиатуры
habs <- function(bot, update) {
IKM <- InlineKeyboardMarkup(
inline_keyboard = list(
list(
InlineKeyboardButton(text = 'R', callback_data = 'r'),
InlineKeyboardButton(text = 'Data Mining', callback_data = 'data_mining'),
InlineKeyboardButton(text = 'Data Engineering', callback_data = 'data_engineering')
),
list(
InlineKeyboardButton(text = 'Big Data', callback_data = 'bigdata'),
InlineKeyboardButton(text = 'Python', callback_data = 'python'),
InlineKeyboardButton(text = 'Визуализация данных', callback_data = 'data_visualization')
)
)
)
# Send Inline Keyboard
bot$sendMessage(chat_id = update$message$chat_id,
text = "Выберите Хаб",
reply_markup = IKM)
}
Список статей из указанного Хаба мы получаем командой tidyfeed()
, из пакета tidyRSS
. Из полученной таблицы с помощью команды head()
оставляем только 5 самых верхних, которые и являются самыми свежими статьями.
# получаем из сообщения название хаба
hub <- update$callback_query$data
# запрашиваем RSS Feed по Хабу
url <- paste0('https://habr.com/ru/rss/hub/', hub, '/')
posts <- head(tidyfeed(url), 5)
Логика очень схожа с предыдущим ботом, но в данном случае Inline клавиатуру со списком статей мы генерируем динамически с помощью функции lapply()
.
# формируем список кнопок
keys <- lapply(1:5, function(x) list(InlineKeyboardButton(posts$item_title[x], url = posts$item_link[x])))
# формируем клавиатуру
IKM <- InlineKeyboardMarkup(
inline_keyboard = keys
)
В текст кнопки мы подставляем название статьи posts$item_title[x]
, а в аргумент url
ссылку на статью: url = posts$item_link[x]
.
Далее, создаём фильтр, обработчики и запускаем нашего бота.
3.5 Заключение
Вы сделали отличную работу, научившись настраивать клавиатуры для вашего бота. Теперь ваш бот может предлагать пользователям удобные варианты для взаимодействия. В следующей главе мы перейдем к построению диалогов, чтобы создать более сложные и естественные взаимодействия между пользователями и вашим ботом. Приготовьтесь углубиться в детали создания диалоговых последовательностей!
3.6 Тесты и задания
3.6.1 Тесты
Для закрепления материла рекомендую вам пройти тест доступный по ссылке.
3.6.2 Задания
- Создайте бота, который будет поддерживать Reply клавиатуру. На Reply клавиатуре будет всего одна кнопка “Время”. По нажатию на неё будет появляться Inline клавиатура с выбором из 6 часовых поясов.
- Africa/Cairo
- America/Chicago
- Europe/Moscow
- Asia/Bangkok
- Europe/Kiev
- Australia/Sydney
Кнопки Inline клавиатуры необходимо расположить по 2 в ряд, соответвенно в три ряда.
По нажатию на одну из кнопки Inline клавиатуры бот будет запрашивать информацию по текущему времени из API worldtimeapi.org.
Формат запроса к API: http://worldtimeapi.org/api/timezone/{area}/:{location}
.
Где area это континент, например Europe, а {location}
это город, например Kiev. Дату и время надо брать в ответе из компонента datetime
.
Если вы всё сделали правильно результат будет такой: