ProHoster > Блог > администрация > Писане на бот за телеграма в R (част 4): Изграждане на последователен, логичен диалог с бота
Писане на бот за телеграма в R (част 4): Изграждане на последователен, логичен диалог с бота
Ако вече сте прочели предишния три статии от тази поредица, тогава вече знаете как да пишете пълноценни ботове за телеграми с клавиатура.
В тази статия ще научим как да напишем бот, който ще поддържа последователен диалог. Тези. Ботът ще ви задава въпроси и ще чака да въведете информация. В зависимост от данните, които въвеждате, ботът ще извърши някои действия.
Също така в тази статия ще научим как да използваме база данни под капака на бота, в нашия пример това ще бъде SQLite, но можете да използвате всяка друга СУБД. Писах по-подробно за взаимодействието с бази данни на езика R в тази статия.
Всички статии от поредицата “Писане на телеграм бот в R”
Ако се интересувате от анализ на данни, може да се интересувате от моя телеграма и YouTube канали. По-голямата част от съдържанието на който е посветено на езика R.
За да може ботът да поиска данни от вас и да изчака да въведете информация, ще трябва да запишете текущото състояние на диалога. Най-добрият начин да направите това е да използвате някакъв вид вградена база данни, като SQLite.
Тези. Логиката ще бъде следната. Извикваме метода на бота и ботът последователно изисква някаква информация от нас и на всяка стъпка чака тази информация да бъде въведена и може да я провери.
Ще напишем най-простия възможен бот, като първо ще поиска вашето име, след това вашата възраст и ще запази получените данни в базата данни. При запитване за възраст ще провери дали въведените данни са число, а не текст.
Такъв прост диалог ще има само три състояния:
start е нормалното състояние на бота, в което той не очаква никаква информация от вас
wait_name - състояние, в което ботът чака да бъде въведено име
wait_age е състоянието, в което ботът чака да бъде въведена вашата възраст, броят на пълните години.
Процес на изграждане на бот
По време на статията ще изградим бот стъпка по стъпка; целият процес може да бъде схематично изобразен, както следва:
Създаваме конфигурация на бот, в която ще съхраняваме някои настройки. В нашия случай токенът на бот и пътя до файла на базата данни.
Създаваме променлива на средата, в която ще се съхранява пътя до проекта с бота.
Ние създаваме самата база данни и редица функции, така че ботът да може да взаимодейства с нея.
Ние пишем бот методи, т.е. функциите, които ще изпълнява.
Добавяне на филтри за съобщения. С помощта на които ботът ще получи достъп до необходимите методи, в зависимост от текущото състояние на чата.
Добавяме манипулатори, които ще свързват команди и съобщения с необходимите бот методи.
Да стартираме бота.
Структура на бот проект
За удобство ще разделим кода на нашия бот и други свързани файлове в следната структура.
бот.Р — основният код на нашия бот
db_bot_function.R — блок от код с функции за работа с базата данни
bot_methods.R — код на бот методи
филтри_съобщения.R — филтри за съобщения
манипулатори.R - манипулатори
config.cfg - конфигурация на бот
create_db_data.sql — SQL скрипт за създаване на таблица с чат данни в базата данни
create_db_state.sql — SQL скрипт за създаване на таблица на текущото състояние на чат в базата данни
В конфигурацията записваме токена на бота и пътя до базата данни, т.е. към файла bot.db; ще създадем самия файл в следващата стъпка.
За по-сложни ботове можете да създадете по-сложни конфигурации, освен това не е необходимо да пишете ini конфигурация, можете да използвате всеки друг формат, включително JSON.
Създайте променлива на средата
На всеки компютър папката с проекта на бот може да се намира в различни директории и на различни дискове, така че в кода пътят до папката на проекта ще бъде зададен чрез променлива на средата TG_BOT_PATH.
Има няколко начина за създаване на променлива на средата, най-простият е да я запишете във файл .Renviron.
Можете да създадете или редактирате този файл с помощта на командата file.edit(path.expand(file.path("~", ".Renviron"))). Изпълнете го и добавете един ред към файла:
TG_BOT_PATH=C:/ПУТЬ/К/ВАШЕМУ/ПРОЕКТУ
След това запазете файла .Renviron и рестартирайте RStudio.
Създаване на база данни
Следващата стъпка е да създадете база данни. Ще ни трябват 2 маси:
chat_data — данни, които ботът е поискал от потребителя
chat_state — текущо състояние на всички чатове
Можете да създадете тези таблици, като използвате следната SQL заявка:
CREATE TABLE chat_data (
chat_id BIGINT PRIMARY KEY
UNIQUE,
name TEXT,
age INTEGER
);
CREATE TABLE chat_state (
chat_id BIGINT PRIMARY KEY
UNIQUE,
state TEXT
);
Ако сте изтеглили проекта за бот от GitHub, тогава за да създадете базата данни, можете да използвате следния код в R.
# Скрипт создания базы данных
library(DBI) # интерфейс для работы с СУБД
library(configr) # чтение конфига
library(readr) # чтение текстовых SQL файлов
library(RSQLite) # драйвер для подключения к SQLite
# директория проекта
setwd(Sys.getenv('TG_BOT_PATH'))
# чтение конфига
cfg <- read.config('config.cfg')
# подключение к SQLite
con <- dbConnect(SQLite(), cfg$db_settings$db_path)
# Создание таблиц в базе
dbExecute(con, statement = read_file('create_db_data.sql'))
dbExecute(con, statement = read_file('create_db_state.sql'))
Писане на функции за работа с база данни
Вече имаме готов конфигурационен файл и създадена база данни. Сега трябва да напишете функции за четене и запис на данни в тази база данни.
Ако сте изтеглили проекта от GitHub, тогава можете да намерите функциите във файла db_bot_function.R.
Функционален код за работа с базата данни
# ###########################################################
# Function for work bot with database
# получить текущее состояние чата
get_state <- function(chat_id) {
con <- dbConnect(SQLite(), cfg$db_settings$db_path)
chat_state <- dbGetQuery(con, str_interp("SELECT state FROM chat_state WHERE chat_id == ${chat_id}"))$state
return(unlist(chat_state))
dbDisconnect(con)
}
# установить текущее состояние чата
set_state <- function(chat_id, state) {
con <- dbConnect(SQLite(), cfg$db_settings$db_path)
# upsert состояние чата
dbExecute(con,
str_interp("
INSERT INTO chat_state (chat_id, state)
VALUES(${chat_id}, '${state}')
ON CONFLICT(chat_id)
DO UPDATE SET state='${state}';
")
)
dbDisconnect(con)
}
# запись полученных данных в базу
set_chat_data <- function(chat_id, field, value) {
con <- dbConnect(SQLite(), cfg$db_settings$db_path)
# 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(), cfg$db_settings$db_path)
# upsert состояние чата
data <- dbGetQuery(con,
str_interp("
SELECT ${field}
FROM chat_data
WHERE chat_id = ${chat_id};
")
)
dbDisconnect(con)
return(data[[field]])
}
Създадохме 4 прости функции:
get_state() — вземете текущото състояние на чата от базата данни
set_state() — запишете текущото състояние на чата в базата данни
get_chat_data() — получаване на данни, изпратени от потребителя
set_chat_data() — запис на данни, получени от потребителя
Всички функции са доста прости, те или четат данни от базата данни с помощта на командата dbGetQuery(), или се ангажирайте UPSERT операция (промяна на съществуващи данни или запис на нови данни в базата данни), използвайки функцията dbExecute().
Синтаксисът за операцията UPSERT е както следва:
INSERT INTO chat_data (chat_id, ${field})
VALUES(${chat_id}, '${value}')
ON CONFLICT(chat_id)
DO UPDATE SET ${field}='${value}';
Тези. в полето на нашите таблици chat_id има ограничение за уникалност и е първичният ключ на таблиците. Първоначално се опитваме да добавим информация към таблицата и получаваме грешка, ако вече има данни за текущия чат, в който случай просто актуализираме информацията за този чат.
След това ще използваме тези функции в методите и филтрите на бота.
Бот методи
Следващата стъпка в изграждането на нашия бот е създаването на методи. Ако сте изтеглили проекта от GitHub, тогава всички методи са във файла bot_methods.R.
Код на метода на бот
# ###########################################################
# bot methods
# start dialog
start <- function(bot, update) {
#
# Send query
bot$sendMessage(update$message$chat_id,
text = "Введи своё имя")
# переключаем состояние диалога в режим ожидания ввода имени
set_state(chat_id = update$message$chat_id, state = 'wait_name')
}
# get current chat state
state <- function(bot, update) {
chat_state <- get_state(update$message$chat_id)
# Send state
bot$sendMessage(update$message$chat_id,
text = unlist(chat_state))
}
# reset dialog state
reset <- function(bot, update) {
set_state(chat_id = update$message$chat_id, state = 'start')
}
# enter username
enter_name <- function(bot, update) {
uname <- update$message$text
# Send message with name
bot$sendMessage(update$message$chat_id,
text = paste0(uname, ", приятно познакомится, я бот!"))
# Записываем имя в глобальную переменную
#username <<- uname
set_chat_data(update$message$chat_id, 'name', uname)
# Справшиваем возраст
bot$sendMessage(update$message$chat_id,
text = "Сколько тебе лет?")
# Меняем состояние на ожидание ввода имени
set_state(chat_id = update$message$chat_id, state = 'wait_age')
}
# enter user age
enter_age <- function(bot, update) {
uage <- as.numeric(update$message$text)
# проверяем было введено число или нет
if ( is.na(uage) ) {
# если введено не число то переспрашиваем возраст
bot$sendMessage(update$message$chat_id,
text = "Ты ввёл некорректные данные, введи число")
} else {
# если введено число сообщаем что возраст принят
bot$sendMessage(update$message$chat_id,
text = "ОК, возраст принят")
# записываем глобальную переменную с возрастом
#userage <<- uage
set_chat_data(update$message$chat_id, 'age', uage)
# сообщаем какие данные были собраны
username <- get_chat_data(update$message$chat_id, 'name')
userage <- get_chat_data(update$message$chat_id, 'age')
bot$sendMessage(update$message$chat_id,
text = paste0("Тебя зовут ", username, " и тебе ", userage, " лет. Будем знакомы"))
# возвращаем диалог в исходное состояние
set_state(chat_id = update$message$chat_id, state = 'start')
}
}
Създадохме 5 метода:
начало — Стартиране на диалогов прозорец
състояние — Вземете текущото състояние на чата
нулиране — Нулиране на текущото състояние на чат
enter_name — Ботът пита за вашето име
enter_age — Ботът пита за вашата възраст
метод start пита за вашето име и променя състоянието на чат на чакащо_име, т.е. в режим на готовност за въвеждане на вашето име.
След това изпращате името и то се обработва от метода enter_name, ботът ви поздравява, записва полученото име в базата данни и превключва чата към състоянието възраст_на изчакване.
На този етап ботът очаква да въведете възрастта си. Изпращате възрастта си, ботът проверява съобщението, ако сте изпратили някакъв текст вместо номер, ще каже: Ты ввёл некорректные данные, введи число, и ще изчака да въведете отново данните си. Ако сте изпратили номер, ботът ще съобщи, че е приел вашата възраст, ще запише получените данни в базата данни, ще отчете всички данни, получени от вас и ще върне състоянието на чата в първоначалната му позиция, т.е. V start.
Чрез извикване на метода state можете да поискате текущото състояние на чата по всяко време и като използвате reset върнете чата в първоначалното му състояние.
Филтри за съобщения
В нашия случай това е една от най-важните части в изграждането на бот. Именно с помощта на филтри за съобщения ботът ще разбере каква информация очаква от вас и как трябва да бъде обработена.
В проекта на GitHub филтрите са регистрирани във файла филтри_съобщения.R.
Код на филтъра за съобщения:
# ###########################################################
# message state filters
# фильтр сообщений в состоянии ожидания имени
MessageFilters$wait_name <- BaseFilter(function(message) {
get_state( message$chat_id ) == "wait_name"
}
)
# фильтр сообщений в состоянии ожидания возраста
MessageFilters$wait_age <- BaseFilter(function(message) {
get_state( message$chat_id ) == "wait_age"
}
)
Във филтрите използваме предварително написаната функция get_state(), за да поискате текущото състояние на чата. Тази функция изисква само 1 аргумент, ID на чата.
Следващ филтър чакащо_име обработва съобщения, когато чатът е в състояние wait_name, и съответно филтъра възраст_на изчакване обработва съобщения, когато чатът е в състояние wait_age.
Манипулатори
Файлът с манипулатори се извиква манипулатори.Rи има следния код:
# ###########################################################
# handlers
# command handlers
start_h <- CommandHandler('start', start)
state_h <- CommandHandler('state', state)
reset_h <- CommandHandler('reset', reset)
# message handlers
## !MessageFilters$command - означает что команды данные обработчики не обрабатывают,
## только текстовые сообщения
wait_age_h <- MessageHandler(enter_age, MessageFilters$wait_age & !MessageFilters$command)
wait_name_h <- MessageHandler(enter_name, MessageFilters$wait_name & !MessageFilters$command)
Първо създаваме манипулатори на команди, които ще ви позволят да стартирате методи за стартиране на диалог, да го нулирате и да направите заявка за текущото състояние.
След това създаваме 2 манипулатора на съобщения, използвайки филтрите, създадени в предишната стъпка, и добавяме филтър към тях !MessageFilters$command, така че да можем да използваме команди във всяко състояние на чат.
Код за стартиране на бот
Сега имаме всичко готово за стартиране, основният код за стартиране на бота е във файла бот.Р.
library(telegram.bot)
library(tidyverse)
library(RSQLite)
library(DBI)
library(configr)
# переходим в папку проекта
setwd(Sys.getenv('TG_BOT_PATH'))
# читаем конфиг
cfg <- read.config('config.cfg')
# создаём экземпляр бота
updater <- Updater(cfg$bot_settings$bot_token)
# Загрузка компонентов бота
source('db_bot_function.R') # функции для работы с БД
source('bot_methods.R') # методы бота
source('message_filters.R') # фильтры сообщений
source('handlers.R') # обработчики сообщений
# Добавляем обработчики в диспетчер
updater <- updater +
start_h +
wait_age_h +
wait_name_h +
state_h +
reset_h
# Запускаем бота
updater$start_polling()
В резултат на това получихме този бот:
По всяко време с помощта на командата /state можем да направим запитване за текущото състояние на чата и да използваме командата /reset върнете чата в първоначалното му състояние и започнете диалога отново.
Заключение
В тази статия разбрахме как да използваме база данни в бот и как да изграждаме последователни логически диалози, като записваме състоянието на чата.
В този случай разгледахме най-примитивния пример, за да ви бъде по-лесно да разберете идеята за изграждане на такива ботове; на практика можете да изградите много по-сложни диалози.
В следващата статия от тази поредица ще научим как да ограничим правата на потребителите на ботове да използват различни от неговите методи.