Глава 3 Как добавить боту поддержку клавиатуры

3.1 Видео урок по добавлению боту клавиатуры

3.2 Введение

В этой главе мы повысим юзабилити нашего бота за счёт добавления клавиатуры, которая сделает интерфейс бота интуитивно понятным, и простым в использовании.

3.3 Какие типы клавиатур поддерживает телеграм бот

На момент написания книги telegram.bot позволяет вам создать клавиатуры двух типов:

  • Reply - Основная, обычная клавиатура, которая находится под панелью ввода текста сообщения. Такая клавиатура просто отправляет боту текстовое сообщение, и в качестве текста отправит тот текст, который написан на самой кнопке.
  • Inline - Клавиатура привязанная к конкретному сообщению бота. Данная клавиатура отправляет боту данные, привязанные к нажатой кнопке, эти данные могут отличаться от текста, написанного на самой кнопке. И обрабатываются такие кнопки через CallbackQueryHandler.

Для того, что бы бот открыл клавиатуру необходимо при отправке сообщения через метод sendMessage(), передать созданную ранее клавиатуру в аргумент reply_markup.

Ниже мы разберём несколько примеров.

3.4 Reply клавиатура

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

bot <- Bot(token = "TOKEN")
chat_id <- "CHAT_ID"

# Create Custom Keyboard
text <- "Aren't those custom keyboards cool?"
RKM <- ReplyKeyboardMarkup(
  keyboard = list(
    list(KeyboardButton("Yes, they certainly are!")),
    list(KeyboardButton("I'm not quite sure")),
    list(KeyboardButton("No..."))
  ),
  resize_keyboard = FALSE,
  one_time_keyboard = TRUE
)

# Send Custom Keyboard
bot$sendMessage(chat_id, text, reply_markup = RKM)

Выше приведён пример из официальной справки пакета telegram.bot. Для создания клавиатуры используется функция ReplyKeyboardMarkup(), которая в свою очередь принимает список списков кнопок, которые создаются функцией KeyboardButton().

Почему в ReplyKeyboardMarkup() необходимо передавать не просто список, а список списков? Дело в том, что вы передаёте основной список, и в нём отдельными списками вы задаёте каждый ряд кнопок, т.к. в один ряд можно расположить несколько кнопок.

Аргумент resize_keyboard позволяет автоматически подбирать оптимальный размер кнопок клавиатуры, а аргумент one_time_keyboard позволяет скрывать клавиатуру после каждого нажатия на кнопку.

Давайте напишем простейшего бота, у которого будет 3 кнопки: * Чат ID - Запросить чат ID диалога с ботом * Моё имя - Запросить своё имя * Мой логин - Запросить своё имя пользователя в телеграм

library(telegram.bot)

# создаём экземпляр класса Updater
updater <- Updater('ТОКЕН ВАШЕГО БОТА')

# создаём методы
## метод для запуска клавиатуры
start <- function(bot, update) {

  # создаём клавиатуру
  RKM <- ReplyKeyboardMarkup(
    keyboard = list(
      list(KeyboardButton("Чат ID")),
      list(KeyboardButton("Моё имя")),
      list(KeyboardButton("Мой логин"))
    ),
    resize_keyboard = FALSE,
    one_time_keyboard = TRUE
  )

  # отправляем клавиатуру
  bot$sendMessage(update$from_chat_id(),
                  text = 'Выберите команду',
                  reply_markup = RKM)

}

## метод возвразающий id чата
chat_id <- function(bot, update) {

  bot$sendMessage(update$from_chat_id(),
                  text = paste0("Чат id этого диалога: ", update$from_chat_id()),
                  parse_mode = "Markdown")

}

## метод возвращающий имя
my_name <- function(bot, update) {

  bot$sendMessage(update$from_chat_id(),
                  text = paste0("Вас зовут ", update$effective_user()$first_name),
                  parse_mode = "Markdown")

}

## метод возвращающий логин
my_username <- function(bot, update) {

  bot$sendMessage(update$from_chat_id(),
                  text = paste0("Ваш логин ", update$effective_user()$username),
                  parse_mode = "Markdown")

}

# создаём фильтры
## сообщения с текстом Чат ID
MessageFilters$chat_id <- BaseFilter(function(message) {

  # проверяем текст сообщения
  message$text == "Чат ID"

}
)

## сообщения с текстом Моё имя
MessageFilters$name <- BaseFilter(function(message) {

  # проверяем текст сообщения
  message$text == "Моё имя"

}
)

## сообщения с текстом Мой логин
MessageFilters$username <- BaseFilter(function(message) {

  # проверяем текст сообщения
  message$text == "Мой логин"
)

# создаём обработчики
h_start    <- CommandHandler('start', start)
h_chat_id  <- MessageHandler(chat_id, filters = MessageFilters$chat_id)
h_name     <- MessageHandler(my_name, filters = MessageFilters$name)
h_username <- MessageHandler(my_username, filters = MessageFilters$username)

# добавляем обработчики в диспетчер
updater <- updater +
            h_start +
            h_chat_id +
            h_name +
            h_username

# запускаем бота
updater$start_polling()

Запустите приведённый выше пример кода, предварительно заменив ‘ТОКЕН ВАШЕГО БОТА’ на реальный токен, который вы получили при создании бота через BotFather.

После запуска задайте боту команду /start, т.к. именно её мы определили для запуска клавиатуры.

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

Мы создали 4 метода:

  • start - Запуск клавиатуры
  • chat_id - Запрос идентификатора чата
  • my_name - Запрос своего имени
  • my_username - Запрос своего логина

В объект MessageFilters добавили 3 фильтра сообщений, по их тексту:

  • chat_id - Сообщения с текстом "Чат ID"
  • name - Сообщения с текстом "Моё имя"
  • username - Сообщения с текстом "Мой логин"

И создали 4 обработчика, которые по заданным командам и фильтрам будут выполнять указанные методы.

# создаём обработчики
h_start    <- CommandHandler('start', start)
h_chat_id  <- MessageHandler(chat_id, filters = MessageFilters$chat_id)
h_name     <- MessageHandler(my_name, filters = MessageFilters$name)
h_username <- MessageHandler(my_username, filters = MessageFilters$username)

Сама клавиатура создаётся внутри метода start() командой ReplyKeyboardMarkup().

RKM <- ReplyKeyboardMarkup(
    keyboard = list(
      list(KeyboardButton("Чат ID")),
      list(KeyboardButton("Моё имя")),
      list(KeyboardButton("Мой логин"))
    ),
    resize_keyboard = FALSE,
    one_time_keyboard = TRUE
)

В нашем случае все кнопки мы расположили друг под другом, но мы можем расположить их в один ряд, внеся изменения в список списков кнопок. Т.к. один ряд внутри клавиатуры создаётся через вложенный список кнопок, то для того, что бы вывести наши кнопки в один ряд надо переписать часть кода по построению клавиатуры вот так:

RKM <- ReplyKeyboardMarkup(
    keyboard = list(
      list(
          KeyboardButton("Чат ID"),
          KeyboardButton("Моё имя"),
          KeyboardButton("Мой логин")
     )
    ),
    resize_keyboard = FALSE,
    one_time_keyboard = TRUE
)

Отправляется клавиатура в чат методом sendMessage(), в аргументе reply_markup.

  bot$sendMessage(update$from_chat_id(),
                  text = 'Выберите команду',
                  reply_markup = RKM)

3.4.1 Как запросить у пользователя номер телефона и локацию

При создании кнопок Reply клавиатуры мы используем функцию KeyboardButton(), у которой доступны 2 дополнительные аргумента:

  • request_contact - Запросить номер телефона пользователя
  • request_location - Запросить локацию пользователя
bot <- Bot(token = "ТОКЕН ВАШЕГО БОТА")

chat_id <- "ID ВАШЕГО ЧАТА"

# Create Custom Keyboard
text <- "Поделитесь своими данными"

RKM <- ReplyKeyboardMarkup(
  keyboard = list(
    list(KeyboardButton("Поделиться номером телефона", request_contact = TRUE)),
    list(KeyboardButton("Поделиться местонахождением", request_location = TRUE))
  ),
  resize_keyboard = FALSE,
  one_time_keyboard = TRUE
)

# Send Custom Keyboard
bot$sendMessage(chat_id, text, reply_markup = RKM)

Когда пользователь поделиться с ботом контактами, то в принятом от бота обновлении, в сообщении будет раздел contact со следующими данными:

  • phone_number - номер телефона в международном формате
  • first_name - Имя
  • last_name - Фамилия
  • user_id - Внутренний идентификатор пользователя в telegram

Когда пользователь делиться местонахождением то в сообщении полученного ответа будет раздел location с информацией о координатах местонахождения пользователя, т.е. широта и долгота.

3.5 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.5.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.5.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.5.3 Пример бота, который выводит список самых свежих статей со ссылками по-указанному Хабу из habr.com

Данного бота я привожу для того, что бы показать вам, как вывести Inline кнопки которые ведут на веб страницы.

Логика данного бота схожа с предыдущим, изначально мы запускаем основную клавиатуру командой /start. Далее бот даёт нам на выбор список из 6 хабов, мы выбираем интересующий нас хаб, и получаем 5 самых свежих публикаций из выбранного Хаба.

Как вы понимаете, в данном случае нам необходимо получить список статей, и для этого мы будем использовать пакет tidyRSS, который позволяет вам получать RSS feed (ленту) сайта в виде обычного дата фрейма в R, к счастью Хабр предоставляет RSS фид под каждый отдельный хаб.

RSS — это файл в формате .XML или .RSS, который используют сайты, чтобы передавать пользователю информацию об обновлениях.

Для начала установим пакет tidyRSS.

install.packages('`tidyRSS`')

Получить RRS Feed по какому то хабу можно примерно так:

library(tidyRSS)
url <- 'https://habr.com/ru/rss/hub/r/'
rss_fead <- tidyfeed(url)

Теперь рассмотрим код построения описанного выше бота:

Код бот который выводит список наиболее свежих статей по выбранному Хабу

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.6 Заключение

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

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

3.7 Тесты и задания

3.7.1 Тесты

Для закрепления материла рекомендую вам пройти тест доступный по ссылке.

3.7.2 Задания

  1. Создайте бота, который будет поддерживать 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.

Если вы всё сделали правильно результат будет такой: