Ak ste sa už oboznámili s predchádzajúcimi z tejto série, už viete, ako písať plnohodnotných telegramových botov s klávesnicou.
V tomto článku sa naučíme písať bota, ktorý bude podporovať sekvenčný dialóg. T.j. bot vám bude klásť otázky a čakať od vás na vstup akýchkoľvek informácií. V závislosti od zadaných údajov bot vykoná určité akcie.
Taktiež sa v tomto článku naučíme používať pod kapotou bota databázy, v našom príklade to bude SQLite, ale môžete využiť akúkoľvek inú DBMS. Podrobnejšie o interakcii s databázami v jazyku R som písal v .

Všetky články zo série "Píšeme telegramového bota v jazyku R"
Obsah
Ak vás zaujíma analýza dát, mohli by vás zaujímať moje a kanály. Väčšina obsahu je venovaná jazyku R.
Úvod
Aby bot mohol od vás požadovať údaje a čakať na vstup akýchkoľvek informácií, budete musieť zaznamenať aktuálny stav dialógu. Najlepší spôsob, ako to robiť, je použiť nejakú zabudovanú databázu, napríklad SQLite.
To znamená, že logika bude nasledovná. Vyvoláme metódu bota, a bot postupne od nás žiada nejaké informácie, pričom v každom kroku čaká na vstup týchto informácií a môže ich overiť.
Napísal by sme maximálne jednoduchého bota, ktorý najprv bude žiadať o vaše meno, potom vek, získané údaje uloží do databázy. Pri žiadosti o vek bude overovať, že zadané údaje sú číslom, a nie textom.
Takýto jednoduchý dialóg bude mať celkovo tri stavy:
- start — bežný stav bota, v ktorom nečaká od vás žiadne informácie
- wait_name — stav, v ktorom bot čaká na zadané meno
- wait_age — stav, pri ktorom bot čaká na zadanie vášho veku, počtu plných rokov.
Proces vytvárania bota
V priebehu článku krok za krokom vytvoríme bota, celý proces možno schematicky znázorniť nasledovne:

- Vytvoríme konfiguráciu bota, v ktorej budeme uchovávať niektoré nastavenia. V našom prípade token bota a cestu k súboru databázy.
- Vytvoríme premennú prostredia, v ktorej bude uložená cesta k projektu s botom.
- Vytvoríme samotnú databázu a rad funkcií, aby bot mohol s ňou interagovať.
- Napíšeme metódy bota, teda funkcie, ktoré bude vykonávať.
- Pridáme filtre správ. S ich pomocou sa bot bude obracať na potrebné metódy v závislosti od aktuálneho stavu chatu.
- Pridáme spracovateľov, ktorí prepoja príkazy a správy s potrebnými metódami bota.
- Spustíme bota.
Štruktúra projektu bota
Pre pohodlie rozdelíme kód nášho bota a ďalšie s ním súvisiace súbory na nasledujúcu štruktúru.
- bot.R — hlavný kód nášho bota
- db_bot_function.R — blok kódu s funkciami na prácu s databázou
- bot_methods.R — kód metód bota
- message_filters.R — filtre správ
- handlers.R — spracovatelia
- config.cfg — konfig bota
- create_db_data.sql — SQL skript na vytvorenie tabuľky s dátami chatu v databáze
- create_db_state.sql — SQL skript na vytvorenie tabuľky aktuálneho stavu chatu v databáze
- bot.db — databáza bota
Celý projekt bota si môžete pozrieť, alebo z môjho .
Konfigurácia bota
Ako konfiguráciu budeme používať obyčajný , nasledujúceho typu:
[bot_settings]
bot_token=TOKEN_VAŠEHO_BOTA
[db_settings]
db_path=C:/CESTA/K/PRIEČINKU/PROJEKTU/bot.dbDo konfigurácie zapíšeme token bota a cestu k databáze, t.j. k súboru bot.db, ktorý vytvoríme v ďalšom kroku.
Pre zložitejšie boty je možné vytvárať aj zložitejšie konfigurácie, navyše nie je potrebné písať iba ini konfiguráciu, môžete použiť aj akýkoľvek iný formát vrátane JSON.
Vytvárame premennú prostredia
Na každom PC môže byť priečinok s projektom bota umiestnený v rôznych adresároch a na rôznych diskoch, preto bude cesta k priečinku projektu v kóde určená prostredníctvom premennej prostredia TG_BOT_PATH.
Vytvoriť premennú prostredia možno viacerými spôsobmi, najjednoduchšie je zadať ju do súboru .Renviron.
Tento súbor môžete vytvoriť alebo upraviť pomocou príkazu file.edit(path.expand(file.path("~", ".Renviron"))). Vykonajte ho a pridajte do súboru jeden riadok:
TG_BOT_PATH=C:/CESTA/K/VÁŠMU/PROJEKTUĎalej uložte súbor .Renviron a reštartujte RStudio.
Vytvárame databázu
Ďalším krokom je vytvorenie databázy. Budeme potrebovať 2 tabuľky:
- chat_data — dáta, ktoré bot požiadal od používateľa
- chat_state — aktuálny stav všetkých chatov
Tieto tabuľky môžeme vytvoriť pomocou nasledujúceho SQL dopytu:
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
);
Ak ste stiahli projekt bota z , môžete na vytvorenie databázy využiť nasledujúci kód v jazyku 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'))
Píšeme funkcie na prácu s databázou
Už máme pripravený konfiguračný súbor a vytvorenú databázu. Teraz je potrebné napísať funkcie na čítanie a zápis dát do tejto databázy.
Ak ste stiahli projekt z , funkcie nájdete v súbore db_bot_function.R.
Kód funkcií na prácu s databázou
# ###########################################################
# 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]])
}Vytvorili sme 4 jednoduché funkcie:
get_state()— získať aktuálny stav chatu z DBset_state()— zapísať aktuálny stav chatu do DBget_chat_data()— získať dáta zaslané používateľomset_chat_data()— zapísať dáta prijaté od používateľa
Všetky funkcie sú pomerne jednoduché, buď čítajú dáta z databázy pomocou príkazu dbGetQuery(), alebo vykonávajú UPSERT operáciu (zmena existujúcich dát alebo zápis nových dát do DB), pomocou funkcie dbExecute().
Syntax UPSERT operácie vyzerá nasledovne:
INSERT INTO chat_data (chat_id, ${field})
VALUES(${chat_id}, '${value}')
ON CONFLICT(chat_id)
DO UPDATE SET ${field}='${value}';T.j. v našich tabuľkách pole chat_id má obmedzenie uniqueness a je primárnym kľúčom tabuliek. Najprv sa pokúsime pridať informácie do tabuľky a dostaneme chybu, ak dáta pre aktuálny chat už existujú, v takom prípade len aktualizujeme informácie pre daný chat.
Tieto funkcie budeme nabudúce používať v metódach a filtroch bota.
Metódy bota
Ďalším krokom pri budovaní nášho bota bude vytvorenie metód. Ak ste stiahli projekt z , všetky metódy sa nachádzajú v súbore bot_methods.R.
Kód metód 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')
}
}Vytvorili sme 5 metód:
- start — Spustenie dialógu
- state — Získať aktuálny stav chatu
- reset — Obnoviť aktuálny stav chatu
- enter_name — Bot sa pýta na vaše meno
- enter_age — Bot sa pýta na váš vek
Metóda start sa pýta na vaše meno a prevádza stav chatu do wait_name, t.j. do režimu čakania na zadanie vášho mena.
Následne odošlete meno a to sa spracuje metódou enter_name, bot sa s vami víta, zapisuje prijaté meno do databázy a prevádza chat do stavu wait_age.
V tomto kroku bot čaká na zadanie vášho veku. Pošlete svoj vek, bot skontroluje správnosť správy; ak namiesto čísla pošlete nejaký text, povie: Zadal si nesprávne údaje, zadaj číslo, a bude opäť čakať na vaše opätovné zadanie údajov. Ak ste poslali číslo, bot potvrdí prijatie vášho veku, zapíše prijaté údaje do databázy, oznámi všetky získané údaje a vráti chat do pôvodného stavu, t.j. do start.
Vyvolaním metódy state môžete kedykoľvek požiadať o aktuálny stav chatu, a pomocou metódy reset vrátiť chat do pôvodného stavu.
Filtre správ
V našom prípade je to jedna z najdôležitejších častí pri tvorbe bota. Práve pomocou filtrov správ bude bot rozumieť, aké informácie od vás očakáva, a ako ich spracovávať.
V projekte na sú filtre definované v súbore message_filters.R.
Kód filtrov správ:
# ###########################################################
# 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"
}
)Vo filtroch používame predtým napísanú funkciu get_state(), aby sme mohli získať aktuálny stav chatu. Táto funkcia vyžaduje iba 1 argument, id chatu.
Následne filter wait_name spracováva správy, keď je chat v stave wait_name, a zodpovedajúco filter wait_age spracováva správy, keď je chat v stave wait_age.
Spracovatelia
Súbor s obsluhovačmi sa volá handlers.R, a obsahuje nasledujúci kód:
# ###########################################################
# 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)Najskôr vytvárame obsluhovače príkazov, ktoré vám umožnia spustiť metódy na začatie dialógu, resetovanie a požiadanie o aktuálny stav.
Potom vytvárame 2 obsluhovače správ s použitím filtrov vytvorených v predchádzajúcom kroku a pridávame k nim filter !MessageFilters$command, aby sme mohli používať príkazy v akomkoľvek stave chatu.
Kód na spustenie bota
Teraz máme všetko pripravené na spustenie, hlavný kód na spustenie bota sa nachádza v súbore bot.R.
library(telegram.bot)
library(tidyverse)
library(RSQLite)
library(DBI)
library(configr)
# prejdeme do zložky projektu
setwd(Sys.getenv('TG_BOT_PATH'))
# čítame konfig
cfg <- read.config('config.cfg')
# vytvoríme instanciu bota
updater <- Updater(cfg$bot_settings$bot_token)
# Načítanie komponentov bota
source('db_bot_function.R') # funkcie na prácu s DB
source('bot_methods.R') # metódy bota
source('message_filters.R') # filtre správ
source('handlers.R') # obsluhovače správ
# Pridávame obsluhovače do správcov
updater <- updater +
start_h +
wait_age_h +
wait_name_h +
state_h +
reset_h
# Spúšťame bota
updater$start_polling()Výsledkom je takýto bot:

Kedykoľvek pomocou príkazu /state môžeme získať aktuálny stav chatu a pomocou príkazu /reset previesť chat do pôvodného stavu a začať dialóg znova.
Záver
V tomto článku sme sa zaoberali tým, ako používať databázy v rámci bota a ako budovať sekvenčné logické dialógy vďaka uchovaniu stavu chatu.
V tomto prípade sme zvážili najprimitívnejší príklad, aby ste lepšie pochopili myšlienku budovania takýchto botov; v praxi môžete vytvárať oveľa zložitejšie dialógy.
V nasledujúcom článku z tejto série sa naučíme obmedziť používateľské práva bota na používanie rôznych jeho metód.
Zdroj: habr.com
