ProHoster > Blog > Bestjoer > In telegrambot skriuwe yn R (diel 4): Bouwe in konsekwint, logyske dialooch mei de bot
In telegrambot skriuwe yn R (diel 4): Bouwe in konsekwint, logyske dialooch mei de bot
As jo hawwe al lêzen de foarige trije artikels út dizze searje, dan witte jo al hoe't jo folweardige telegrambots skriuwe mei in toetseboerd.
Yn dit artikel sille wy leare hoe't jo in bot skriuwe dy't in konsekwint dialooch sil behâlde. Dy. De bot sil jo fragen stelle en wachtsje oant jo wat ynformaasje ynfiere. Ofhinklik fan 'e gegevens dy't jo ynfiere, sil de bot guon aksjes útfiere.
Ek yn dit artikel sille wy leare hoe't jo in databank brûke ûnder de kap fan 'e bot, yn ús foarbyld sil it SQLite wêze, mar jo kinne elke oare DBMS brûke. Ik skreau yn mear detail oer ynteraksje mei databases yn 'e R-taal yn dit artikel.
Alle artikels út 'e searje "In telegrambot skriuwe yn R"
As jo ynteressearre binne yn gegevensanalyse, kinne jo miskien wêze ynteressearre yn myn telegram и youtube kanalen. It grutste part fan de ynhâld is wijd oan de R-taal.
Om de bot gegevens fan jo te freegjen en te wachtsjen oant jo alle ynformaasje ynfiere, moatte jo de aktuele steat fan it dialooch opnimme. De bêste manier om dit te dwaan is in soarte fan ynbêde databank te brûken, lykas SQLite.
Dy. De logika sil as folgjend wêze. Wy neame de bot-metoade, en de bot freget opfolgjend wat ynformaasje fan ús, en by elke stap wachtet it op dizze ynformaasje om yn te fieren en kin it kontrolearje.
Wy sille de ienfâldichste mooglike bot skriuwe, earst sil it om jo namme freegje, dan jo leeftyd, en sil de ûntfongen gegevens opslaan yn 'e databank. As jo nei leeftyd freegje, sil it kontrolearje dat de ynfierde gegevens in nûmer binne en gjin tekst.
Sa'n ienfâldige dialooch sil mar trije steaten hawwe:
start is de normale tastân fan 'e bot, wêryn't it gjin ynformaasje fan jo ferwachtet
wait_name - steat wêryn de bot wachtet op in namme om te wurde ynfierd
wait_age is de steat wêryn de bot wachtet op jo leeftyd om yn te fieren, it oantal folsleine jierren.
Bot gebou proses
Tidens it artikel sille wy stap foar stap in bot bouwe; it heule proses kin skematysk as folgjend wurde ôfbylde:
Wy meitsje in bot-konfiguraasje wêryn wy guon ynstellings sille opslaan. Yn ús gefal, it bot token, en it paad nei it databankbestân.
Wy meitsje in omjouwingsfariabele wêryn it paad nei it projekt mei de bot wurdt opslein.
Wy meitsje de databank sels, en in oantal funksjes sadat de bot kin ynteraksje mei it.
Wy skriuwe bot metoaden, d.w.s. de funksjes it sil útfiere.
Berjochtfilters tafoegje. Mei de help wêrfan de bot tagong sil ta de nedige metoaden, ôfhinklik fan de aktuele steat fan it petear.
Wy foegje handlers ta dy't kommando's en berjochten ferbine mei de nedige botmetoaden.
Litte wy de bot starte.
Bot projekt struktuer
Foar gemak sille wy de koade fan ús bot, en oare relatearre bestannen, ferdiele yn 'e folgjende struktuer.
bot.R - de haadkoade fan ús bot
db_bot_function.R - in blok koade mei funksjes foar wurkjen mei in databank
bot_methods.R - koade fan botmetoaden
message_filters.R - berjochtfilters
handlers.R - hannelers
config.cfg - bot konfiguraasje
create_db_data.sql - SQL-skript foar it meitsjen fan in tabel mei peteargegevens yn 'e databank
create_db_state.sql - SQL-skript foar it meitsjen fan in tabel fan 'e hjoeddeistige petearstatus yn' e databank
Yn 'e konfiguraasje skriuwe wy it bot token en it paad nei de databank, d.w.s. nei it bot.db-bestân; wy sille it bestân sels yn 'e folgjende stap oanmeitsje.
Foar mear komplekse bots kinne jo mear komplekse konfiguraasjes oanmeitsje, boppedat is it net nedich om in ini-konfiguraasje te skriuwen, jo kinne elke oare opmaak brûke, ynklusyf JSON.
Meitsje in omjouwingsfariabele
Op elke PC kin de map mei it botprojekt yn ferskate mappen en op ferskate skiven lizze, dus yn 'e koade sil it paad nei de projektmap ynsteld wurde fia in omjouwingsfariabele TG_BOT_PATH.
D'r binne ferskate manieren om in omjouwingsfariabele te meitsjen, de ienfâldichste is it te skriuwen yn in bestân .Renviron.
Jo kinne dit bestân oanmeitsje of bewurkje mei it kommando file.edit(path.expand(file.path("~", ".Renviron"))). Fier it út en foegje ien rigel ta oan it bestân:
TG_BOT_PATH=C:/ПУТЬ/К/ВАШЕМУ/ПРОЕКТУ
Folgjende bewarje de triem .Renviron en opnij starte RStudio.
It meitsjen fan in databank
De folgjende stap is om in database te meitsjen. Wy sille 2 tabellen nedich wêze:
chat_data - gegevens dy't de bot frege fan 'e brûker
chat_state - aktuele steat fan alle petearen
Jo kinne dizze tabellen oanmeitsje mei de folgjende SQL-query:
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
);
As jo ynladen it bot projekt út GitHub, dan kinne jo de folgjende koade brûke yn 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'))
Skriuwfunksjes om te wurkjen mei de databank
Wy hawwe al in konfiguraasjetriem klear en in database makke. No moatte jo funksjes skriuwe om gegevens te lêzen en te skriuwen nei dizze databank.
As jo ynladen it projekt út GitHub, dan kinne jo de funksjes fine yn it bestân db_bot_function.R.
Funksje koade foar wurkjen mei de databank
# ###########################################################
# 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]])
}
Wy hawwe 4 ienfâldige funksjes makke:
get_state() - krije de hjoeddeistige petearstatus fan 'e database
set_state() - skriuw de hjoeddeistige petearstatus nei de databank
get_chat_data() - ûntfange gegevens ferstjoerd troch de brûker
set_chat_data() - record gegevens ûntfongen fan 'e brûker
Alle funksjes binne frij ienfâldich, se lêze gegevens út 'e databank mei it kommando dbGetQuery(), of ynsette UPSERT operaasje (feroarje besteande gegevens of it skriuwen fan nije gegevens nei de databank), mei help fan de funksje dbExecute().
De syntaksis foar de UPSERT-operaasje is as folget:
INSERT INTO chat_data (chat_id, ${field})
VALUES(${chat_id}, '${value}')
ON CONFLICT(chat_id)
DO UPDATE SET ${field}='${value}';
Dy. yn ús tabellen fjild chat_id hat in unike beheining en is de primêre kaai fan tabellen. Yn earste ynstânsje besykje wy ynformaasje oan 'e tabel ta te foegjen, en wy krije in flater as gegevens foar it aktuele petear al oanwêzich binne, yn dat gefal aktualisearje wy gewoan de ynformaasje foar dit petear.
Folgjende sille wy dizze funksjes brûke yn 'e metoaden en filters fan' e bot.
Bot metoaden
De folgjende stap yn it bouwen fan ús bot is om metoaden te meitsjen. As jo ynladen it projekt út GitHub, dan binne alle metoaden yn it bestân bot_methods.R.
Bot metoade koade
# ###########################################################
# 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')
}
}
Wy hawwe 5 metoaden makke:
start - Begjin in dialooch
steat - Krij de hjoeddeistige petearstatus
weromsette - Reset de hjoeddeiske petear tastân
enter_name - De bot freget om jo namme
enter_age - De bot freget om jo leeftyd
Metoade start freget om jo namme, en feroaret de petearstatus nei wait_name, d.w.s. standby foar it ynfieren fan jo namme.
Dêrnei stjoere jo de namme en it wurdt ferwurke troch de metoade enter_name, de bot begroet jo, skriuwt de ûntfongen namme yn 'e databank, en skeakelet it petear nei de steat wachtsje_leeftyd.
Op dit stadium ferwachtet de bot dat jo jo leeftyd ynfiere. Jo stjoere jo leeftyd, de bot kontroleart it berjocht, as jo wat tekst stjoere ynstee fan in nûmer, sil it sizze: Ты ввёл некорректные данные, введи число, en sil wachtsje oant jo jo gegevens opnij ynfiere. As jo in nûmer stjoerd hawwe, sil de bot melde dat it jo leeftyd hat akseptearre, skriuw de ûntfongen gegevens yn 'e databank, rapportearje alle gegevens ûntfongen fan jo en bringt de petearstatus werom nei syn oarspronklike posysje, d.w.s. V start.
Troch de metoade te roppen state kinne jo oanfreegje de hjoeddeiske petear status op elts momint, en mei help fan de reset bring it petear werom nei syn oarspronklike steat.
Berjochtfilters
Yn ús gefal is dit ien fan 'e wichtichste dielen by it bouwen fan in bot. It is mei help fan berjochtfilters dat de bot sil begripe hokker ynformaasje hy fan jo ferwachtet en hoe't it moat wurde ferwurke.
Yn it projekt op GitHub filters wurde registrearre yn de triem message_filters.R.
Berjochtfilterkoade:
# ###########################################################
# 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"
}
)
Yn filters brûke wy de earder skreaune funksje get_state(), om de aktuele steat fan it petear op te freegjen. Dizze funksje fereasket mar 1 argumint, petear id.
Folgjende filter wait_name ferwurket berjochten as it petear yn in steat is wait_name, en dêrmei it filter wachtsje_leeftyd ferwurket berjochten as it petear yn in steat is wait_age.
Handlers
De triem mei handlers wurdt neamd handlers.R, en hat de folgjende koade:
# ###########################################################
# 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)
Earst meitsje wy kommando-handlers wêrmei jo metoaden kinne útfiere om in dialooch te begjinnen, it werom te setten en de aktuele steat te freegjen.
Folgjende, wy meitsje 2 berjocht handlers mei help fan de filters makke yn de foarige stap, en heakje in filter oan harren !MessageFilters$command, sadat wy kommando's kinne brûke yn elke petearstatus.
Bot lansearring koade
No hawwe wy alles klear om te starten, de haadkoade foar it starten fan de bot is yn it bestân 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()
As resultaat krigen wy dizze bot:
Op elk momint mei help fan it kommando /state wy kinne query de hjoeddeiske petear steat, en mei help fan it kommando /reset bring it petear werom nei syn oarspronklike steat en begjin de dialooch opnij.
konklúzje
Yn dit artikel hawwe wy útfûn hoe't jo in databank yn in bot kinne brûke, en hoe't jo opfolgjende logyske dialogen kinne bouwe troch de petearstatus op te nimmen.
Yn dit gefal seagen wy nei it meast primitive foarbyld, sadat it makliker wêze soe foar jo om it idee fan it bouwen fan sokke bots te begripen; yn 'e praktyk kinne jo folle kompleksere dialogen bouwe.
Yn it folgjende artikel yn dizze searje sille wy leare hoe't jo de rjochten fan bot-brûkers kinne beheine om ferskate fan har metoaden te brûken.