Решение задач
В этом разделе книги приведены решения всех, представленных в учебнике задач.
Задача 1.1
- Создайте с помощью BotFather бота.
- Перейдите к диалогу с ботом, и узнайте идентификатор вашего с ботом чата.
- Отправьте с помощью созданного бота в 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
- Создайте бота, который будет поддерживать 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 попыток угадать это число.
Вы по очереди в каждой из попыток вводите числа, если введённое число меньше чем то, которое загадал бот то бот пишет “моё число больше”, иначе бот пишет “моё число меньше”. Если вы ввели правильное число то бот пишет что вы выйграли, и переводит диалог в исходное состояние.
Решение:
Создаём таблицу в базе данных для хранеия числа и текущей попытки.
Далее создаём функции для работы с бахой данных.
# 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()