Решение задач

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

Задача 1.1

  1. Создайте с помощью BotFather бота.
  2. Перейдите к диалогу с ботом, и узнайте идентификатор вашего с ботом чата.
  3. Отправьте с помощью созданного бота в telegram первые 20 строк из встроенного в R набора данных ToothGrowth.

Решение:

library(purrr)
library(tidyr)
library(stringr)
library(telegram.bot)

# функция для перевода data.frame в telegram таблицу 
to_tg_table <- function( table, align = NULL, indents = 3, parse_mode = 'Markdown' ) {
  
  # если выравнивание не задано то выравниваем по левому краю
  if ( is.null(align) ) {
    
    col_num <- length(table)
    align   <- str_c( rep('l', col_num), collapse = '' )
    
  }
  
  # проверяем правильно ли заданно выравнивание
  if ( length(table) != nchar(align) ) {
    
    align <- NULL
    
  }
  
  # новое выравнивание столбцов 
  side <- sapply(1:nchar(align), 
                 function(x) { 
                   letter <- substr(align, x, x)
                   switch (letter,
                           'l' = 'right',
                           'r' = 'left',
                           'c' = 'both',
                           'left'
                   )
                 })
  
  # сохраняем имена
  t_names      <- names(table)
  
  # вычисляем ширину столбцов
  names_length <- sapply(t_names, nchar) 
  value_length <- sapply(table, function(x) max(nchar(as.character(x))))
  max_length   <- ifelse(value_length > names_length, value_length, names_length)
  
  # подгоняем размер имён столбцов под их ширину + указанное в indents к-во пробелов 
  t_names <- mapply(str_pad, 
                    string = t_names, 
                    width  = max_length + indents, 
                    side   = side)
  
  # объединяем названия столбцов
  str_names <- str_c(t_names, collapse = '')
  
  # аргументы для фукнции str_pad
  rules <- list(string = table, width = max_length + indents, side = side)
  
  # поочереди переводим каждый столбец к нужному виду
  t_str <-   pmap_df( rules, str_pad )%>%
    unite("data", everything(), remove = TRUE, sep = '') %>%
    unlist(data) %>%
    str_c(collapse = '\n') 
  
  # если таблица занимает более 4096 символов обрезаем её
  if ( nchar(t_str) >= 4021 ) {
    
    warning('Таблица составляет более 4096 символов!')
    t_str <- substr(t_str, 1, 4021)
    
  }
  
  # символы выделения блока кода согласно выбранной разметке
  code_block <- switch(parse_mode, 
                       'Markdown' = c('```', '```'),
                       'HTML' = c('<code>', '</code>'))
  
  # переводим в code
  res <- str_c(code_block[1], str_names, t_str, code_block[2], sep = '\n')
  
  return(res)
}

# создаём экземпляр бота
bot <- Bot('1165649194:AAFkDqIzQ6Wq5GV0YU7PmEZcv1gmWIFIB_8')

# получаем ID чата 
# (предварительно отправьте боту любое сообщение)
chat_id <- bot$getUpdates()[[1]]$from_chat_id()

# преоразуем таблицу ToothGrowth
TG <- to_tg_table( head(ToothGrowth, 20) )

# отправляем таблицу в Telegram
bot$sendMessage(chat_id, 
                TG,
                'Markdown')

Задача 2.1

Создайте бота, который будет по команде /sum и переданное в качестве дополнительных параметров произвольное количество перечисленных через пробел чисел, возвращать их сумму.

Решение:

library(telegram.bot)

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

# Создаём функцию, которая будет суммировать переданные числа
summing <- function(bot, update, args) {

  # Переводим полученный вектор параметров в числа и суммируем
  x <- sum(as.integer(args))

  # создаём сообщение
  msg <- paste0('Сумма переданных чисел: ', x)

  # отправляем результат
  bot$sendMessage(update$message$chat_id, msg, 'Markdown')

}

# создаём обработчик
h_sum <- CommandHandler('sum', summing, pass_args = TRUE)

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

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

Задача 3.1

  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.

Решение:

library(telegram.bot)
library(httr)
library(stringr)

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

# Запуск клавиатуры
start <- function(bot, update) {
  
  # строим Reply клавиатуру
  RKM <- ReplyKeyboardMarkup(
    keyboard = list(
      list(
        KeyboardButton('Время')
      )
    ))
  
  # отпралвяем Reply клавиатуру
  bot$sendMessage(update$message$chat_id, 
                  'Выберите команду', 
                  'Markdown',
                  reply_markup = RKM)
  
}

# Отправляем inline клавиатуру
inline <- function(bot, update) {
  
  IKM <- InlineKeyboardMarkup(
              inline_keyboard = 
                list(
                  list(
                    InlineKeyboardButton(text = 'Africa/Cairo', callback_data = 'Africa/Cairo'),
                    InlineKeyboardButton(text = 'America/Chicago', callback_data = 'America/Chicago')
                  ),
                  list(
                    InlineKeyboardButton(text = 'Europe/Moscow', callback_data = 'Europe/Moscow'),
                    InlineKeyboardButton(text = 'Asia/Bangkok', callback_data = 'Asia/Bangkok')
                  ),
                  list(
                    InlineKeyboardButton(text = 'Europe/Kiev', callback_data = 'Europe/Kiev'),
                    InlineKeyboardButton(text = 'Australia/Sydney', callback_data = 'Australia/Sydney')
                  )
                ))
  
  # отпралвяем Reply клавиатуру
  bot$sendMessage(update$message$chat_id, 
                  'Выберите регион', 
                  'Markdown',
                  reply_markup = IKM)
  
}

# обрабатываем нажатие на кнопку
curtime <- function(bot, update) {
  
  # сообщаем боту, что запрос с кнопки принят
  bot$answerCallbackQuery(callback_query_id = update$callback_query$id) 
  
  # данные с кнопки
  data <- update$callback_query$data
  
  # разбиваем на регион и город
  geo <- unlist(strsplit(data, split = '/'))
  
  # компонуем URL
  url <- str_glue('http://worldtimeapi.org/api/timezone/{geo[1]}/{geo[2]}')
  
  # запрос к API
  answer <- GET(url)
  
  # парсим ответ
  res <- content(answer)
  
  # создаём сообщение
  msg <- str_glue('Текущее время в {data}: {res$datetime}')
  
  # отправляем сообщение
  bot$sendMessage(update$from_chat_id(), 
                  msg, 
                  'Markdown')
}

# Фильтр для Reply клавиатуры
MessageFilters$start <- 
  BaseFilter(
    function(message) {
      message$text == 'Время'
    }
  )

# Обработчики
h_start <- CommandHandler('start', start)
h_time  <- MessageHandler(inline, MessageFilters$start)
h_cb    <- CallbackQueryHandler(curtime)

# Добавляем обработчики в диспетчер
updater <- updater + h_start + h_time + h_cb

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

Задача 4.1

Постройте бота который будет поддерживать игру угадай число. Т.е. по команде /start бот будет загадывать число от 1 до 50. Далее у вас будет 5 попыток угадать это число.

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

Решение:

Создаём таблицу в базе данных для хранеия числа и текущей попытки.

CREATE TABLE chat_data (
    chat_id BIGINT  PRIMARY KEY
                    UNIQUE,
    attempt    INTEGER,
    number     INTEGER
);

Далее создаём функции для работы с бахой данных.

# write chat data
# write chat data
set_chat_data <- function(chat_id, field, value) {
  
  
  con <- dbConnect(SQLite(), db)
  
  # upsert состояние чата
  dbExecute(con, 
            str_interp("
            INSERT INTO chat_data (chat_id, ${field})
                VALUES(${chat_id}, '${value}') 
                ON CONFLICT(chat_id) 
                DO UPDATE SET ${field}='${value}';
            ")
  )
  
  dbDisconnect(con)
  
}

# read chat data
get_chat_data <- function(chat_id, field) {
  
  
  con <- dbConnect(SQLite(), db)
  
  # upsert состояние чата
  data <- dbGetQuery(con, 
                     str_interp("
            SELECT ${field}
            FROM chat_data
            WHERE chat_id = ${chat_id};
            ")
  )
  
  dbDisconnect(con)
  
  return(data[[field]])
  
}

Основной код бота выглядит так:

library(RSQLite)
library(DBI)
library(telegram.bot)
library(stringr)

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

# путь к базе
db <- "ПУСТЬ К БАЗЕ ДАННЫХ/bot.db"

start <- function(bot, update) {
  
  # бот загадывает число
  num <- round(runif(1, 1, 50), 0)
  
  # записываем данные в базу о начале игры
  set_chat_data( update$message$chat_id, 'number', num)
  set_chat_data( update$message$chat_id, 'attempt', 1)
  
  # отпралвяем Reply клавиатуру
  bot$sendMessage(update$message$chat_id, 
                  'Число загаданно, начинаем игру, ваша первая попытка.', 
                  'Markdown')
  
}

attempt <- function(bot, update) {
  
  num <- get_chat_data(update$message$chat_id, 'number')
  att <- get_chat_data(update$message$chat_id, 'attempt')
  
  user_num <- update$message$text
  
  if ( user_num < num ) {
    
    bot$sendMessage(update$message$chat_id, 
                    paste0('Номер попытки: ', att, ". Моё число больше"),
                    'Markdown')
    
  } else if ( user_num > num ) {
    
    bot$sendMessage(update$message$chat_id, 
                    paste0('Номер попытки: ', att, ". Моё число меньше"),
                    'Markdown')
    
  } else {
    
    bot$sendMessage(update$message$chat_id, 
                    paste0('Номер попытки: ', att, ". Поздравляю, вы угадали число!"),
                    'Markdown')
    
    set_chat_data( update$message$chat_id, 'attempt', 0)
    
  }
  
  if ( att == 5 &  user_num != num )  {
    
    bot$sendMessage(update$message$chat_id, 
                    paste0("Вы проиграли, я загадал число ", num),
                    'Markdown')
    set_chat_data( update$message$chat_id, 'attempt', 0)
    
  }
  
  set_chat_data( update$message$chat_id, 'attempt', att + 1)
  
}


# фильтр сообщение в состоянии ожидания имени
MessageFilters$attempt <- BaseFilter(function(message) {

  att <- get_chat_data(message$chat_id, 'attempt') 
  0 < att & att < 6
}
)

# обработчики
h_start   <- CommandHandler('start', start)
h_attempt <- MessageHandler(attempt, MessageFilters$attempt & !MessageFilters$command)

# диспетчер
updater <- updater + h_start + h_attempt

# запуск
updater$start_polling()

Задача 5.1

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

Решение:

library(telegram.bot)

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

# Создаём функцию, которая будет суммировать переданные числа
summing <- function(bot, update, args) {
  
  if ( update$message$from$username == 'YourUsername' ) {

    # Переводим полученный вектор параметров в числа и суммируем
    x <- sum(as.integer(args))
  
    # создаём сообщение
    msg <- paste0('Сумма переданных чисел: ', x)
  
    # отправляем результат
    bot$sendMessage(update$message$chat_id, msg, 'Markdown')
    
  } else {
    
    # отправляем результат
    bot$sendMessage(update$message$chat_id, 
                    'У вас не достаточно прав на использование этой функции бота!', 
                    'Markdown')
    
  }

}

# создаём обработчик
h_sum <- CommandHandler('sum', summing, pass_args = TRUE)

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

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