ProHoster > Блог > Administracija > Pisanje telegram bota u R (4. dio): Izgradnja dosljednog, logičnog dijaloga s botom
Pisanje telegram bota u R (4. dio): Izgradnja dosljednog, logičnog dijaloga s botom
Ako ste već pročitali prethodni tri članka iz ove serije, onda već znate kako pisati punopravne telegram botove pomoću tastature.
U ovom članku ćemo naučiti kako napisati bota koji će održavati dosljedan dijalog. One. Bot će vam postavljati pitanja i čekati da unesete neke podatke. Ovisno o podacima koje unesete, bot će izvršiti neke radnje.
Također u ovom članku ćemo naučiti kako koristiti bazu podataka ispod haube bota, u našem primjeru to će biti SQLite, ali možete koristiti bilo koji drugi DBMS. Pisao sam detaljnije o interakciji sa bazama podataka u jeziku R ovaj članak.
Da bi bot od vas tražio podatke i čekao da unesete bilo koju informaciju, morat ćete snimiti trenutno stanje dijaloga. Najbolji način da to učinite je korištenje neke vrste ugrađene baze podataka, kao što je SQLite.
One. Logika će biti sljedeća. Pozivamo bot metodu, a bot uzastopno traži neke informacije od nas i na svakom koraku čeka da se ta informacija unese i može ih provjeriti.
Napisaćemo najjednostavniji mogući bot, prvo će tražiti vaše ime, zatim vaše godine, a primljene podatke će sačuvati u bazi podataka. Prilikom upita za starost, provjerit će da li su uneseni podaci broj, a ne tekst.
Takav jednostavan dijalog će imati samo tri stanja:
start je normalno stanje bota, u kojem ne očekuje nikakve informacije od vas
wait_name - stanje u kojem bot čeka da se unese ime
wait_age je stanje u kojem bot čeka da se unese vaša starost, broj punih godina.
Proces izgradnje bota
Tokom članka ćemo napraviti bot korak po korak; cijeli proces se može shematski prikazati na sljedeći način:
Kreiramo konfiguraciju bota u koju ćemo pohraniti neke postavke. U našem slučaju, token bota i putanja do datoteke baze podataka.
Kreiramo varijablu okruženja u kojoj će biti pohranjena putanja do projekta sa botom.
Kreiramo samu bazu podataka i brojne funkcije kako bi bot mogao komunicirati s njom.
Pišemo bot metode, tj. funkcije koje će obavljati.
Dodavanje filtera poruka. Uz pomoć kojih će bot pristupiti potrebnim metodama, ovisno o trenutnom stanju razgovora.
Dodamo rukovaoce koji će povezati komande i poruke sa potrebnim bot metodama.
Pokrenimo bota.
Struktura bot projekta
Radi praktičnosti, podijelit ćemo kod našeg bota i druge povezane datoteke u sljedeću strukturu.
bot.R — glavni kod našeg bota
db_bot_function.R — blok koda sa funkcijama za rad sa bazom podataka
bot_methods.R — kod bot metoda
message_filters.R — filteri poruka
rukovaoci.R - rukovaoce
config.cfg - konfiguracija bota
create_db_data.sql — SQL skripta za kreiranje tabele sa podacima za ćaskanje u bazi podataka
create_db_state.sql — SQL skripta za kreiranje tabele trenutnog stanja razgovora u bazi podataka
U konfiguraciji upisujemo bot token i putanju do baze podataka, tj. u datoteku bot.db; samu datoteku ćemo kreirati u sljedećem koraku.
Za složenije botove možete kreirati složenije konfiguracije, osim toga nije potrebno pisati ini konfiguraciju, možete koristiti bilo koji drugi format uključujući JSON.
Kreirajte varijablu okruženja
Na svakom računaru fascikla sa projektom bot može se nalaziti u različitim direktorijumima i na različitim diskovima, tako da će u kodu put do fascikle projekta biti postavljen preko varijable okruženja TG_BOT_PATH.
Postoji nekoliko načina za kreiranje varijable okruženja, najjednostavniji je da je zapišete u datoteku .Renviron.
Možete kreirati ili uređivati ovu datoteku pomoću naredbe file.edit(path.expand(file.path("~", ".Renviron"))). Izvršite ga i dodajte jedan red u datoteku:
TG_BOT_PATH=C:/ПУТЬ/К/ВАШЕМУ/ПРОЕКТУ
Zatim sačuvajte datoteku .Renviron i ponovo pokrenite RStudio.
Kreiranje baze podataka
Sljedeći korak je kreiranje baze podataka. Trebat će nam 2 stola:
chat_data — podaci koje je bot zatražio od korisnika
chat_state — trenutno stanje svih razgovora
Ove tabele možete kreirati koristeći sledeći SQL upit:
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
);
Ako ste preuzeli bot projekat sa GitHub, zatim za kreiranje baze podataka možete koristiti sljedeći kod u 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'))
Pisanje funkcija za rad sa bazom podataka
Već imamo spreman konfiguracijski fajl i kreiranu bazu podataka. Sada morate napisati funkcije za čitanje i pisanje podataka u ovu bazu podataka.
Ako ste projekat preuzeli sa GitHub, tada možete pronaći funkcije u datoteci db_bot_function.R.
Funkcijski kod za rad sa bazom podataka
# ###########################################################
# 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]])
}
Napravili smo 4 jednostavne funkcije:
get_state() — dobiti trenutno stanje razgovora iz baze podataka
set_state() — upišite trenutno stanje ćaskanja u bazu podataka
get_chat_data() — primati podatke koje šalje korisnik
set_chat_data() — zapis podataka primljenih od korisnika
Sve funkcije su prilično jednostavne, ili čitaju podatke iz baze podataka pomoću naredbe dbGetQuery(), ili urezivanje UPSERT operacija (promjena postojećih podataka ili upisivanje novih podataka u bazu podataka), korištenje funkcije dbExecute().
Sintaksa za operaciju UPSERT je sljedeća:
INSERT INTO chat_data (chat_id, ${field})
VALUES(${chat_id}, '${value}')
ON CONFLICT(chat_id)
DO UPDATE SET ${field}='${value}';
One. u polju naših tabela chat_id ima ograničenje jedinstvenosti i primarni je ključ tabela. U početku pokušavamo da dodamo informacije u tabelu, a dobijamo grešku ako su podaci za trenutni chat već prisutni, u kom slučaju jednostavno ažuriramo informacije za ovaj chat.
Zatim ćemo koristiti ove funkcije u botovim metodama i filterima.
Bot metode
Sljedeći korak u izgradnji našeg bota je kreiranje metoda. Ako ste projekat preuzeli sa GitHub, tada su sve metode u datoteci bot_methods.R.
Šifra bot metode
# ###########################################################
# 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')
}
}
Kreirali smo 5 metoda:
start — Pokrenite dijalog
stanje — Dobijte trenutno stanje ćaskanja
reset — Resetujte trenutno stanje ćaskanja
enter_name — bot traži vaše ime
enter_age — bot pita za vaše godine
Metoda start traži vaše ime i mijenja stanje razgovora u čekaj_ime, tj. u stanje pripravnosti za unos vašeg imena.
Zatim šaljete ime i ono se obrađuje metodom enter_name, bot vas pozdravlja, upisuje primljeno ime u bazu podataka i prebacuje chat u stanje wait_age.
U ovoj fazi, bot očekuje da unesete svoje godine. Vi pošaljete svoje godine, bot provjerava poruku, ako ste poslali neki tekst umjesto broja, reći će: Ты ввёл некорректные данные, введи число, i čekat će da ponovo unesete svoje podatke. Ako ste poslali broj, bot će prijaviti da je prihvatio vaše godine, upisati primljene podatke u bazu podataka, prijaviti sve primljene podatke od vas i vratiti stanje chata na prvobitnu poziciju, tj. V start.
Pozivanjem metode state možete zatražiti trenutni status ćaskanja u bilo kojem trenutku, a koristeći reset vratite chat u prvobitno stanje.
Filteri za poruke
U našem slučaju, ovo je jedan od najvažnijih dijelova u izgradnji bota. Uz pomoć filtera poruka bot će shvatiti koje informacije očekuje od vas i kako ih treba obraditi.
U projektu na GitHub filteri su registrovani u datoteci message_filters.R.
Kôd filtera poruka:
# ###########################################################
# 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"
}
)
U filterima koristimo prethodno napisanu funkciju get_state(), kako biste zatražili trenutno stanje razgovora. Ova funkcija zahtijeva samo 1 argument, chat id.
Sljedeći filter čekaj_ime obrađuje poruke kada je chat u stanju wait_name, a shodno tome i filter wait_age obrađuje poruke kada je chat u stanju wait_age.
Rukovaoci
Poziva se fajl sa rukovaocima rukovaoci.R, i ima sljedeći kod:
# ###########################################################
# 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)
Prvo kreiramo rukovaoce komandama koji će vam omogućiti da pokrenete metode za pokretanje dijaloga, resetujete ga i upitate trenutno stanje.
Zatim kreiramo 2 rukovaoca porukama koristeći filtere kreirane u prethodnom koraku i dodajemo im filter !MessageFilters$command, tako da možemo koristiti komande u bilo kojem stanju razgovora.
Kod za pokretanje bota
Sada imamo sve spremno za pokretanje, glavni kod za pokretanje bota je u datoteci bot.R.
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()
Kao rezultat, dobili smo ovog bota:
U bilo kom trenutku koristeći naredbu /state možemo upitati trenutno stanje ćaskanja i pomoću naredbe /reset vratite razgovor u prvobitno stanje i ponovo započnite dijalog.
zaključak
U ovom članku smo shvatili kako koristiti bazu podataka unutar bota i kako napraviti sekvencijalne logičke dijaloge snimanjem stanja razgovora.
U ovom slučaju, pogledali smo najprimitivniji primjer, kako bi vam bilo lakše razumjeti ideju izgradnje ovakvih botova; u praksi možete graditi mnogo složenije dijaloge.
U sljedećem članku iz ove serije naučit ćemo kako ograničiti prava korisnika botova da koriste različite njegove metode.