ProHoster > Blog > Uprava > Pisanje bota za telegram v R (4. del): gradnja doslednega, logičnega dialoga z botom
Pisanje bota za telegram v R (4. del): gradnja doslednega, logičnega dialoga z botom
Če ste že prebrali prejšnjo trije članki iz te serije, potem že veste, kako napisati polnopravne telegramske bote s tipkovnico.
V tem članku se bomo naučili, kako napisati bota, ki bo vzdrževal dosleden dialog. Tisti. Bot vam bo zastavil vprašanja in čakal, da vnesete nekaj informacij. Glede na podatke, ki jih vnesete, bo bot izvedel nekaj dejanj.
Tudi v tem članku se bomo naučili uporabljati bazo podatkov pod pokrovom bota, v našem primeru bo to SQLite, vendar lahko uporabite katero koli drugo DBMS. O interakciji z bazami podatkov v jeziku R sem podrobneje pisal v ta članek.
Da bo bot od vas zahteval podatke in čakal, da vnesete kakršne koli informacije, boste morali zabeležiti trenutno stanje dialoga. Najboljši način za to je uporaba neke vrste vdelane baze podatkov, kot je SQLite.
Tisti. Logika bo naslednja. Pokličemo metodo bot, bot pa od nas zaporedno zahteva nekaj podatkov in na vsakem koraku počaka na vnos teh podatkov in jih lahko preveri.
Napisali bomo najpreprostejšega možnega bota, najprej vas bo vprašal za ime, nato starost in prejete podatke shranil v bazo. Pri vprašanju starosti bo preveril, ali so vneseni podatki številka in ne besedilo.
Tako preprost dialog bo imel samo tri stanja:
start je običajno stanje bota, v katerem od vas ne pričakuje nobenih informacij
wait_name - stanje, v katerem bot čaka na vnos imena
wait_age je stanje, v katerem bot čaka na vnos vaše starosti, število polnih let.
Postopek gradnje botov
V članku bomo korak za korakom zgradili bota; celoten postopek lahko shematično prikažemo na naslednji način:
Ustvarimo konfiguracijo bota, v kateri bomo shranili nekaj nastavitev. V našem primeru žeton bota in pot do datoteke baze podatkov.
Ustvarimo spremenljivko okolja, v kateri bo shranjena pot do projekta z botom.
Ustvarimo samo bazo podatkov in številne funkcije, da lahko bot komunicira z njo.
Pišemo bot metode, tj. funkcije, ki jih bo opravljal.
Dodajanje filtrov za sporočila. S pomočjo katerega bo bot dostopal do potrebnih metod, odvisno od trenutnega stanja klepeta.
Dodamo upravljalnike, ki bodo povezali ukaze in sporočila s potrebnimi metodami botov.
Zaženimo bota.
Struktura projekta Bot
Zaradi udobja bomo kodo našega bota in druge povezane datoteke razdelili v naslednjo strukturo.
bot.R — glavna koda našega bota
db_bot_function.R — blok kode s funkcijami za delo z bazo podatkov
bot_methods.R — koda botovskih metod
filtri_sporočil.R — filtri sporočil
vodniki.R - upravljavci
config.cfg - konfiguracija bota
create_db_data.sql — SQL skript za ustvarjanje tabele s podatki klepeta v bazi podatkov
create_db_state.sql — SQL skript za ustvarjanje tabele trenutnega stanja klepeta v bazi podatkov
V config zapišemo žeton bota in pot do baze, tj. v datoteko bot.db; samo datoteko bomo ustvarili v naslednjem koraku.
Za bolj zapletene robote lahko ustvarite bolj zapletene konfiguracije, poleg tega ni potrebno napisati ini konfiguracije, lahko uporabite katero koli drugo obliko, vključno z JSON.
Ustvarite spremenljivko okolja
Na vsakem računalniku se lahko mapa s projektom bota nahaja v različnih imenikih in na različnih pogonih, zato bo v kodi pot do mape projekta nastavljena prek spremenljivke okolja TG_BOT_PATH.
Spremenljivko okolja lahko ustvarite na več načinov, najenostavnejši je, da jo zapišete v datoteko .Renviron.
To datoteko lahko ustvarite ali uredite z ukazom file.edit(path.expand(file.path("~", ".Renviron"))). Izvedite ga in dodajte eno vrstico v datoteko:
TG_BOT_PATH=C:/ПУТЬ/К/ВАШЕМУ/ПРОЕКТУ
Nato shranite datoteko .Renviron in znova zaženite RStudio.
Ustvarjanje baze podatkov
Naslednji korak je izdelava baze podatkov. Potrebovali bomo 2 mizi:
chat_data — podatki, ki jih je bot zahteval od uporabnika
chat_state — trenutno stanje vseh klepetov
Te tabele lahko ustvarite z naslednjo poizvedbo 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
);
Če ste projekt bota prenesli iz GitHub, potem lahko za ustvarjanje baze podatkov uporabite naslednjo kodo v 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 funkcij za delo z bazo podatkov
Imamo že pripravljeno konfiguracijsko datoteko in izdelano bazo podatkov. Zdaj morate napisati funkcije za branje in pisanje podatkov v to bazo podatkov.
Če ste projekt prenesli iz GitHub, potem lahko najdete funkcije v datoteki db_bot_function.R.
Funkcijska koda za delo z bazo podatkov
# ###########################################################
# 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]])
}
Ustvarili smo 4 preproste funkcije:
get_state() — pridobite trenutno stanje klepeta iz baze podatkov
set_state() — zapišite trenutno stanje klepeta v bazo podatkov
get_chat_data() — prejemanje podatkov, ki jih pošlje uporabnik
set_chat_data() — beleženje podatkov, prejetih od uporabnika
Vse funkcije so precej preproste, bodisi preberejo podatke iz baze podatkov z ukazom dbGetQuery(), ali zavezati UPSERT delovanje (spreminjanje obstoječih podatkov ali pisanje novih podatkov v bazo), uporaba funkcije dbExecute().
Sintaksa za operacijo UPSERT je naslednja:
INSERT INTO chat_data (chat_id, ${field})
VALUES(${chat_id}, '${value}')
ON CONFLICT(chat_id)
DO UPDATE SET ${field}='${value}';
Tisti. v našem polju tabel chat_id ima omejitev edinstvenosti in je primarni ključ tabel. Na začetku poskušamo v tabelo dodati informacije in prejmemo napako, če so podatki za trenutni klepet že prisotni, v tem primeru preprosto posodobimo informacije za ta klepet.
Nato bomo te funkcije uporabili v metodah in filtrih bota.
Botovske metode
Naslednji korak pri gradnji našega bota je ustvarjanje metod. Če ste projekt prenesli iz GitHub, potem so vse metode v datoteki bot_methods.R.
Koda metode bota
# ###########################################################
# 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')
}
}
Ustvarili smo 5 metod:
start — Začnite pogovorno okno
stanje — Pridobite trenutno stanje klepeta
ponastavi — Ponastavite trenutno stanje klepeta
enter_name — Bot zahteva vaše ime
enter_age — Bot vas vpraša za vašo starost
Metoda start vpraša za vaše ime in spremeni stanje klepeta v čakalno ime, tj. v pripravljenosti za vnos vašega imena.
Nato pošljete ime in obdela ga metoda enter_name, vas bot pozdravi, prejeto ime zapiše v bazo podatkov in klepet preklopi v stanje čakalna_doba.
Na tej stopnji bot pričakuje, da vnesete svojo starost. Pošljete svojo starost, bot preveri sporočilo, če ste namesto številke poslali besedilo, bo pisalo: Ты ввёл некорректные данные, введи число, in bo počakal, da ponovno vnesete svoje podatke. Če ste poslali številko, bo bot sporočil, da je sprejel vašo starost, zapisal prejete podatke v bazo podatkov, sporočil vse podatke, ki jih je prejel od vas, in vrnil stanje klepeta v prvotni položaj, tj. V start.
S klicem metode state kadar koli lahko zahtevate trenutno stanje klepeta in uporabite reset vrnite klepet v prvotno stanje.
Filtri sporočil
V našem primeru je to eden najpomembnejših delov pri izdelavi bota. S pomočjo filtrov za sporočila bo bot razumel, katere informacije od vas pričakuje in kako jih je treba obdelati.
V projektu na GitHub filtri so registrirani v datoteki filtri_sporočil.R.
Koda filtra sporočil:
# ###########################################################
# 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"
}
)
V filtrih uporabljamo prej napisano funkcijo get_state(), da zahtevate trenutno stanje klepeta. Ta funkcija zahteva samo 1 argument, ID klepeta.
Naslednji filter čakalno ime obdeluje sporočila, ko je klepet v stanju wait_name, in temu primerno filter čakalna_doba obdeluje sporočila, ko je klepet v stanju wait_age.
Vodniki
Pokliče se datoteka z obdelovalci vodniki.Rin ima naslednjo kodo:
# ###########################################################
# 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)
Najprej izdelamo upravljalnike ukazov, ki vam bodo omogočili zagon metod za zagon pogovornega okna, njegovo ponastavitev in poizvedbo o trenutnem stanju.
Nato ustvarimo 2 upravljalnika sporočil z uporabo filtrov, ustvarjenih v prejšnjem koraku, in jima dodamo filter !MessageFilters$command, tako da lahko uporabljamo ukaze v katerem koli stanju klepeta.
Koda za zagon bota
Zdaj imamo vse pripravljeno za zagon, glavna koda za zagon bota je v datoteki 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()
Kot rezultat smo dobili tega bota:
Kadar koli z uporabo ukaza /state lahko poizvedujemo o trenutnem stanju klepeta in uporabimo ukaz /reset vrnite klepet v prvotno stanje in znova začnite dialog.
Zaključek
V tem članku smo ugotovili, kako uporabiti bazo podatkov znotraj bota in kako zgraditi zaporedna logična dialoga s snemanjem stanja klepeta.
V tem primeru smo pogledali najbolj primitiven primer, da bi vam bilo lažje razumeti idejo o izdelavi takšnih botov; v praksi lahko zgradite veliko bolj zapletene dialoge.
V naslednjem članku v tej seriji se bomo naučili, kako uporabnikom botov omejiti pravice do uporabe različnih njegovih metod.