Hvis du allerede har læst de tidligere i denne serie, så kan du allerede skrive fuldgyldige Telegram-bots med tastatur.
I denne artikel vil vi lære at skrive en bot, der understøtter en sekventiel samtale. Dvs. botten vil stille dig spørgsmål og vente på, at du indtaster en eller anden information. Afhængig af de oplysninger, du indtaster, vil botten udføre visse handlinger.
Vi vil også lære at bruge databaser under motorhjelmen til botten; i vores eksempel vil det være SQLite, men du kan bruge enhver anden DBMS. Mere om interaktion med databaser i R har jeg skrevet i .

Alle artikler i serien "Vi skriver en telegram-bot i R"
Indhold
Hvis du er interesseret i dataanalyse, kan mine og kanaler være interessante. Størstedelen af indholdet er dedikeret til R-sprog.
Introduktion
For at botten kan anmode om data fra dig og vente på, at du indtaster information, skal du registrere den aktuelle tilstand af dialogen. Den bedste måde at gøre dette på er at bruge en indlejret database, f.eks. SQLite.
Dvs. logikken vil være som følger. Vi kalder botens metode, og botten anmoder sekventielt om nogle oplysninger, og på hvert trin venter den på, at disse oplysninger indtastes og kan verificere dem.
Vi vil skrive en så simpel bot som muligt; først vil den spørge om dit navn, derefter din alder, og de indtastede oplysninger vil blive gemt i databasen. Ved anmodning om alder vil den kontrollere, at de indtastede data er tal og ikke tekst.
En sådan simpel dialog vil have kun tre tilstande:
- start — normalt bot-tilstand, hvor den ikke venter på oplysninger fra dig
- wait_name — tilstand, hvor botten venter på, at du indtaster dit navn
- wait_age — tilstand, hvor botten venter på, at du indtaster din alder, antallet af hele år.
Processen til at bygge botten
I løbet af artiklen vil vi trin for trin bygge botten; hele processen kan skematisk vises som følger:

- Vi opretter bot-konfigurationen, hvor vi vil gemme nogle indstillinger. I vores tilfælde token for botten og stien til databasen.
- Vi opretter en miljøvariabel, der vil gemme stien til projektet med botten.
- Vi opretter selve databasen og en række funktioner, så botten kan interagere med den.
- Vi skriver metoder til botten, dvs. funktioner, som den vil udføre.
- Vi tilføjer beskedfiltre, som botten vil bruge til at kalde de nødvendige metoder afhængigt af den aktuelle tilstand i chatten.
- Vi tilføjer controllere, der forbinder kommandoer og beskeder med de relevante metoder i botten.
- Vi starter botten.
Bot-projektets struktur
For nemheds skyld opdeler vi koden til vores bot og andre relaterede filer i følgende struktur.
- bot.R — hovedkoden til vores bot
- db_bot_function.R — kodeblok med funktioner til at arbejde med databasen
- bot_methods.R — koden til botmetoderne
- message_filters.R — beskedfiltre
- handlers.R — controllere
- config.cfg — botens konfiguration
- create_db_data.sql — SQL-script til oprettelse af tabellen med chatdata i databasen
- create_db_state.sql — SQL-script til oprettelse af tabellen med den aktuelle tilstand af chatten i databasen
- bot.db — botens database
Hele botprojektet kan ses eller fra min .
Bot-konfiguration
Som konfiguration vil vi bruge en almindelig , af følgende slags:
[bot_settings]
bot_token=DIN_BOTS_TOKEN
[db_settings]
db_path=C:/STI/TIL/FOLDER/PROJEKT/bot.dbI konfigurationen skriver vi botens token og stien til databasen, dvs. til filen bot.db, som vi vil oprette i næste trin.
For mere komplekse bots kan man også oprette mere komplekse konfigurationer; det er desuden ikke nødvendigt at skrive en ini-konfiguration, du kan bruge ethvert andet format inklusive JSON.
Opretter en miljøvariabel
På hver PC kan mappen med botprojektet være placeret i forskellige direktører og på forskellige drev, derfor vil stien til projektmappen i koden blive angivet via en miljøvariabel TG_BOT_PATH.
Man kan oprette en miljøvariabel på flere måder, den nemmeste er at skrive den i filen .Renviron.
Man kan oprette eller redigere denne fil ved hjælp af kommandoen file.edit(path.expand(file.path("~", ".Renviron"))). Udfør den og tilføj én linje til filen:
TG_BOT_PATH=C:/STI/TIL/DIN/PROJEKTGem filen .Renviron og genstart RStudio.
Opretter en database
Næste trin er oprettelsen af databasen. Vi har brug for 2 tabeller:
- chat_data — data som botten har forespurgt fra brugeren
- chat_state — den aktuelle tilstand af alle chats
Disse tabeller kan oprettes ved hjælp af følgende SQL-forespørgsel:
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
);
Hvis du har downloadet botprojektet fra , så du kan bruge følgende kode på R-sprog til at oprette databasen.
# Скрипт создания базы данных
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'))
Skriver funktioner til at arbejde med databasen
Vi har allerede en konfigurationsfil klar, og databasen er oprettet. Nu skal vi skrive funktioner til at læse fra og skrive til denne database.
Hvis du har downloadet projektet fra , så kan du finde funktionerne i filen db_bot_function.R.
Kode til funktioner til at arbejde med databasen
# ###########################################################
# 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]])
}Vi har oprettet 4 simple funktioner:
get_state()— få den nuværende chattilstand fra databasenset_state()— gem den nuværende chattilstand i databasenget_chat_data()— få data sendt af brugerenset_chat_data()— gem data modtaget fra brugeren
Alle funktioner er ret enkle, de læser enten data fra databasen ved hjælp af kommandoen dbGetQuery(), eller udfører UPSERT operationen (ændring af eksisterende data eller indsættelse af nye data i databasen) ved hjælp af funktionen dbExecute().
Syntaksen for UPSERT-operationen ser således ud:
INSERT INTO chat_data (chat_id, ${field})
VALUES(${chat_id}, '${value}')
ON CONFLICT(chat_id)
DO UPDATE SET ${field}='${value}';Det vil sige, at feltet chat_id i vores tabeller har et unikt begrænsning og er den primære nøgle i tabellerne. Først forsøger vi at tilføje information til tabellen, og vi får en fejl, hvis dataene for den aktuelle chat allerede findes, i så fald opdaterer vi blot informationen for denne chat.
Disse funktioner vil vi derefter bruge i metoderne og filtrene i botten.
Botmetoder
Næste skridt i opbygningen af vores bot vil være at oprette metoder. Hvis du har downloadet projektet fra , så findes alle metoder i filen bot_methods.R.
Kode til botmetoder
# ###########################################################
# 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')
}
}Vi har oprettet 5 metoder:
- start — Start dialogen
- state — Få den nuværende chattilstand
- reset — Nulstil den nuværende chattilstand
- enter_name — Botten beder om dit navn
- enter_age — Botten beder om din alder
Metoden start beder om dit navn, og ændrer chattilstanden til wait_name, det vil sige, i en ventetilstand for at indtaste dit navn.
Derefter sender du navnet, og det behandles med metoden enter_name, botten hilser på dig, gemmer det modtagne navn i databasen og ændrer chatten til tilstanden wait_age.
På dette tidspunkt venter botten på, at du indtaster din alder. Du sender din alder, botten tjekker beskeden, hvis du har sendt tekst i stedet for et tal, siger den: Du har indtastet ugyldige data, indtast et tal, og vil vente på, at du indtaster data igen. Hvis du sender et tal, vil botten meddele, at den har modtaget din alder, registrere de modtagne data i databasen, informere om alle de opnåede oplysninger fra dig og bringe chattilstanden tilbage til udgangspunktet, dvs. til start.
Ved at kalde metoden state kan du til enhver tid anmode om den nuværende chattilstand, og metoden reset kan bringe chatten tilbage til dens oprindelige tilstand.
Beskedfiltre
I vores tilfælde er dette en af de vigtigste dele i opbygningen af botten. Netop ved hjælp af beskedfiltre vil botten forstå, hvilken information den venter fra dig, og hvordan den skal behandles.
I projektet på er filtrene skrevet i filen message_filters.R.
Kode for beskedfiltre:
# ###########################################################
# 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"
}
)I filtrene bruger vi den tidligere skrevne funktion get_state(), for at anmode om den nuværende chattilstand. Denne funktion kræver kun 1 argument, chat-id.
Derefter behandler filteret wait_name beskeder, når chatten er i tilstand wait_name, og tilsvarende filteret wait_age beskeder, når chatten er i tilstand wait_age.
Håndterere
Filen med håndtererne hedder handlers.R, og har følgende kode:
# ###########################################################
# 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)Først opretter vi håndtere for kommandoer, som vil give dig mulighed for at starte metoder til at påbegynde dialogen, nulstille den og anmode om den nuværende tilstand.
Derefter opretter vi 2 håndtere af beskeder ved hjælp af de tidligere oprettede filtre, og tilføjer filteret !MessageFilters$command, så vi i enhver chattilstand kan bruge kommandoer.
Bot-opsætningskode
Nu er vi klar til at starte, hovedkoden til at starte botten findes i filen bot.R.
library(telegram.bot)
library(tidyverse)
library(RSQLite)
library(DBI)
library(configr)
# går ind i projektmappen
setwd(Sys.getenv('TG_BOT_PATH'))
# læser konfigurationen
cfg <- read.config('config.cfg')
# opretter en instans af botten
updater <- Updater(cfg$bot_settings$bot_token)
# Indlæs komponenter af botten
source('db_bot_function.R') # funktioner til at arbejde med DB
source('bot_methods.R') # metoder til botten
source('message_filters.R') # beskedfiltre
source('handlers.R') # besked-håndterere
# Tilføjer håndtere til dispatcher
updater <- updater +
start_h +
wait_age_h +
wait_name_h +
state_h +
reset_h
# Starter botten
updater$start_polling()Som et resultat har vi fået en sådan bot:

Når som helst kan vi ved hjælp af kommandoen /state anmode om den nuværende chattilstand, og ved hjælp af kommandoen /reset kan bringe chatten tilbage til sin oprindelige tilstand og starte dialogen forfra.
Konklusion
I denne artikel har vi gennemgået, hvordan man bruger databaser inden i botten, og hvordan man opbygger sekventielle logiske dialoger ved at fastholde chattilstanden.
I dette tilfælde har vi set på det mest primitive eksempel for at gøre det lettere for dig at forstå ideen om at opbygge sådanne bots. I praksis kan du bygge langt mere komplekse dialoger.
I den næste artikel i denne serie vil vi lære at begrænse bot-brugernes rettigheder til at anvende forskellige metoder.
Kilde: habr.com
