Pokud jste se již seznámili s předchozími z této série, už víte, jak psát plnohodnotné telegramové boty s klávesnicí.
V tomto článku se naučíme vytvářet bota, který bude podporovat sekvenční dialog. Tedy bot bude klást otázky a čekat na váš vstup jakékoliv informace. Na základě vámi zadaných údajů pak bot provede určité akce.
Také se v tomto článku naučíme, jak pod kapotou bota používat databáze, v našem příkladu to bude SQLite, ale můžete použít jakoukoli jinou DB. Podrobněji o spolupráci s databázemi v jazyce R jsem psal v .

Všechny články ze série "Píšeme telegramového bota v jazyce R"
Obsah
Pokud se zajímáte o analýzu dat, možná vás zaujmou mé a kanály. Většina obsahu je věnována jazyku R.
Úvod
Aby bot mohl požadovat od vás data a čekat na vstup jakéhokoli informace, budete potřebovat uchovávat aktuální stav dialogu. Nejlepší způsob, jak to udělat, je použít nějakou zabudovanou databázi, například SQLite.
Tedy logika bude následující. Voláme metodu bota a bot postupně žádá o nějaké informace, přičemž v každém kroku čeká na tento vstup a může provádět jeho kontrolu.
Napíšeme maximálně jednoduchého bota, který nejprve požádá o vaše jméno, poté o věk, a získaná data uloží do databáze. Při dotazu na věk bude kontrolovat, aby zadané údaje byly číslem, a ne textem.
Takový jednoduchý dialog bude mít pouze tři stavy:
- start — běžný stav bota, ve kterém nečeká na žádné informace od vás
- wait_name — stav, kdy bot čeká na zadání jména
- wait_age — stav, kdy bot očekává zadání vašeho věku, počet plných let.
Proces vytváření bota
V průběhu článku společně krok za krokem vytvoříme bota, celý proces lze schematicky znázornit následovně:

- Vytváříme konfiguraci bota, ve které budeme uchovávat některé nastavení. V našem případě token bota a cestu k souboru databáze.
- Vytváříme proměnnou prostředí, ve které bude uložena cesta k projektu s botem.
- Vytváříme samotnou databázi a řadu funkcí, aby mohl bot s ní interagovat.
- Píšeme metody bota, tj. funkce, které bude vykonávat.
- Přidáváme filtry zpráv. Pomocí nich bude bot volat potřebné metody v závislosti na aktuálním stavu chatu.
- Přidáváme zpracovatele, kteří propojí příkazy a zprávy s potřebnými metodami bota.
- Spouštíme bota.
Struktura projektu bota
Pro přehlednost rozdělíme kód našeho bota a ostatní související soubory do následující struktury.
- bot.R — hlavní kód našeho bota
- db_bot_function.R — blok kódu s funkcemi pro práci s databází
- bot_methods.R — kód metod bota
- message_filters.R — filtry zpráv
- handlers.R — zpracovatelé
- config.cfg — konfigurace bota
- create_db_data.sql — SQL skript pro vytvoření tabulky s daty chatu v databázi
- create_db_state.sql — SQL skript pro vytvoření tabulky aktuálního stavu chatu v databázi
- bot.db — databáze bota
Celý projekt bota si můžete prohlédnout, nebo z mého .
Konfigurace bota
Jako konfiguraci budeme používat běžný , následujícího typu:
[bot_settings]
bot_token=VAŠE_BOT_TOKEN
[db_settings]
db_path=C:/CESTA/K/SLOŽCE/PROJEKTU/bot.dbDo konfigurace zapisujeme token bota a cestu k databázi, tj. k souboru bot.db, samotný soubor vytvoříme v dalším kroku.
Pro složitější boty lze vytvářet i složitější konfigurace, navíc není nutné psát zrovna ini konfiguraci, můžete použít jakýkoli jiný formát včetně JSON.
Vytváříme proměnnou prostředí
Na každém PC může být složka s projektem bota umístěna v různých adresářích a na různých discích, proto bude cesta k složce projektu v kódu nastavena prostřednictvím proměnné prostředí TG_BOT_PATH.
Proměnnou prostředí lze vytvořit několika způsoby, nejjednodušší je ji zapsat do souboru .Renviron.
Tento soubor můžete vytvořit nebo upravit pomocí příkazu file.edit(path.expand(file.path("~", ".Renviron"))). Proveďte ho a přidejte do souboru jeden řádek:
TG_BOT_PATH=C:/CESTA/K/VÁŠEMU/PROJEKTUPoté uložte soubor .Renviron a restartujte RStudio.
Vytváříme databázi
Další krok — vytvoření databáze. Budeme potřebovat 2 tabulky:
- chat_data — data, která bot požádal uživatele
- chat_state — aktuální stav všech chatů
Tyto tabulky lze vytvořit pomocí následujícího SQL dotazu:
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
);
Pokud jste stáhli projekt bota z , můžete použít následující kód v jazyce R k vytvoření databáze.
# Скрипт создания базы данных
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 funkce pro práci s databází
Máme již připravený konfigurační soubor a databázi. Nyní je třeba napsat funkce pro čtení a zápis dat do této databáze.
Pokud jste stáhli projekt z , funkce najdete v souboru db_bot_function.R.
Kód funkcí pro práci s databází
# ###########################################################
# 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]])
}Vytvořili jsme 4 jednoduché funkce:
get_state()— získat aktuální stav chatu z DBset_state()— zapsat aktuální stav chatu do DBget_chat_data()— získat data odeslaná uživatelemset_chat_data()— zapsat data přijatá od uživatele
Všechny funkce jsou dost jednoduché, buď čtou data z databáze pomocí příkazu dbGetQuery(), nebo provádějí UPSERT operaci (změna existujících dat nebo zápis nových dat do DB) pomocí funkce dbExecute().
Syntaxe operace UPSERT vypadá takto:
INSERT INTO chat_data (chat_id, ${field})
VALUES(${chat_id}, '${value}')
ON CONFLICT(chat_id)
DO UPDATE SET ${field}='${value}';Tj. v našich tabulkách má pole chat_id omezení unikátnosti a je primárním klíčem tabulek. Nejprve se pokusíme přidat informace do tabulky a obdržíme chybu, pokud data pro aktuální chat již existují; v tomto případě jednoduše aktualizujeme informace o daném chatu.
Tyto funkce budeme dále používat v metodách a filtrech bota.
Metody bota
Dalším krokem při budování našeho bota bude vytvoření metod. Pokud jste stáhli projekt z , všechny metody se nacházejí v souboru bot_methods.R.
Kód metod 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')
}
}Vytvořili jsme 5 metod:
- start — Spuštění dialogu
- state — Získat aktuální stav chatu
- reset — Obnovit aktuální stav chatu
- enter_name — Bot žádá o vaše jméno
- enter_age — Bot se ptá na váš věk
Metoda start se ptá na vaše jméno a převádí stav chatu na wait_name, tj. do režimu čekání na zadání vašeho jména.
Poté odešlete jméno, které je zpracováno metodou enter_name, bot vás pozdraví, zapíše získané jméno do databáze a převádí chat do stavu wait_age.
V této fázi bot čeká na vložení vašeho věku. Odešlete svůj věk, bot zkontroluje zprávu; pokud místo čísla odešlete nějaký text, řekne: Zadal jsi nesprávná data, zadej číslo, a bude čekat na tvoje opětovné zadání dat. Pokud jsi odeslal číslo, bot ti oznámí, že přijal tvůj věk, zapíše získaná data do databáze, oznámí všechny přijaté údaje a nastaví stav chatu do výchozího stavu, tj. do start.
Vyvoláním metody stav můžeš v každém okamžiku požádat o aktuální stav chatu, a metodou reset přenést chat do výchozího stavu.
Filtry zpráv
V našem případě je to jedna z nejdůležitějších částí při tvorbě bota. Díky filtrům zpráv bude bot rozumět, jaké informace od tebe očekává a jak s nimi má zacházet.
V projektu na filtry jsou napsány v souboru message_filters.R.
Kód filtrů zprá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"
}
)Ve filtrech používáme dříve napsanou funkci get_state(), abychom získali aktuální stav chatu. Tato funkce vyžaduje pouze 1 argument, id chatu.
Dále filtr wait_name zpracovává zprávy, když je chat v stavu wait_name, a odpovídajícím způsobem filtr wait_age zpracovává zprávy, když je chat v stavu wait_age.
Hlavní zpracovatelé
Soubory s zpracovateli se nazývá handlers.R, a má následující 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)Nejprve vytváříme zpracovatele příkazů, kteří ti umožní spouštět metody pro zahájení dialogu, jeho reset a dotaz na aktuální stav.
Dále vytváříme 2 zpracovatele zpráv za použití filtrů vytvořených v předchozím kroku a přidáváme k nim filtr !MessageFilters$command, abychom mohli v jakémkoli stavu chatu používat příkazy.
Kód pro spuštění bota
Nyní máme vše připraveno k spuštění, hlavní kód pro spuštění bota se nachází v souboru bot.R.
library(telegram.bot)
library(tidyverse)
library(RSQLite)
library(DBI)
library(configr)
# přecházíme do složky projektu
setwd(Sys.getenv('TG_BOT_PATH'))
# čteme konfiguraci
cfg <- read.config('config.cfg')
# vytváříme instanci bota
updater <- Updater(cfg$bot_settings$bot_token)
# Načítání komponent bota
source('db_bot_function.R') # funkce pro práci s databází
source('bot_methods.R') # metody bota
source('message_filters.R') # filtry zpráv
source('handlers.R') # zpracovatele zpráv
# Přidáváme zpracovatele do správce
updater <- updater +
start_h +
wait_age_h +
wait_name_h +
state_h +
reset_h
# Spouštíme bota
updater$start_polling()Výsledkem je, že máme takového bota:

V každém okamžiku pomocí příkazu /state můžeme vyžadovat aktuální stav chatu, a pomocí příkazu /reset přenést chat do výchozího stavu a začít dialog znovu.
Závěr
V tomto článku jsme se podívali na to, jak používat databáze uvnitř bota a jak vytvářet sekvenční logické dialogy díky ukládání stavu chatu.
V tomto případě jsme se zaměřili na nejprimitivnější příklad, abyste mohli snáze pochopit myšlenku budování takových botů; v praxi můžete vytvářet mnohem složitější dialogy.
V příštím článku této série se naučíme omezovat uživatelům bota práva na používání různých jeho metod.
Zdroj: habr.com
