Píšeme telegram bota v jazyce R (část 4): Vytváření sekvenčního, logického dialogu s botem

Pokud jste se již seznámili s předchozími třemi články 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 tomto článku.

Píšeme telegram bota v jazyce R (část 4): Vytváření sekvenčního, logického dialogu s botem

Všechny články ze série "Píšeme telegramového bota v jazyce R"

  1. Vytváříme bota a pomocí něj posíláme zprávy na telegram
  2. Přidáváme botovi podporu příkazů a filtry zpráv
  3. Jak přidat botovi podporu klávesnice
  4. Vytváření sekvenčního, logického dialogu s botem

Obsah

Pokud se zajímáte o analýzu dat, možná vás zaujmou mé telegram a youtube kanály. Většina obsahu je věnována jazyku R.

  1. Úvod
  2. Proces vytváření bota
  3. Struktura projektu bota
  4. Konfigurace bota
  5. Vytváříme proměnnou prostředí
  6. Vytváříme databázi
  7. Píšeme funkce pro práci s databází
  8. Metody bota
  9. Filtry zpráv
  10. Hlavní zpracovatelé
  11. Kód pro spuštění bota
  12. Závě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:

  1. start — běžný stav bota, ve kterém nečeká na žádné informace od vás
  2. wait_name — stav, kdy bot čeká na zadání jména
  3. 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ě:
Píšeme telegram bota v jazyce R (část 4): Vytváření sekvenčního, logického dialogu s botem

  1. 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.
  2. Vytváříme proměnnou prostředí, ve které bude uložena cesta k projektu s botem.
  3. Vytváříme samotnou databázi a řadu funkcí, aby mohl bot s ní interagovat.
  4. Píšeme metody bota, tj. funkce, které bude vykonávat.
  5. Přidáváme filtry zpráv. Pomocí nich bude bot volat potřebné metody v závislosti na aktuálním stavu chatu.
  6. Přidáváme zpracovatele, kteří propojí příkazy a zprávy s potřebnými metodami bota.
  7. 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 stáhnout z mého repozitáře na GitHubu.

Konfigurace bota

Jako konfiguraci budeme používat běžný ini soubor, následujícího typu:

[bot_settings]
bot_token=VAŠE_BOT_TOKEN

[db_settings]
db_path=C:/CESTA/K/SLOŽCE/PROJEKTU/bot.db

Do 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/PROJEKTU

Poté 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 GitHub, 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 GitHub, 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 DB
  • set_state() — zapsat aktuální stav chatu do DB
  • get_chat_data() — získat data odeslaná uživatelem
  • set_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 GitHub, 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 GitHub 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:
Píšeme telegram bota v jazyce R (část 4): Vytváření sekvenčního, logického dialogu s botem

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

Koupit spolehlivý hosting pro webové stránky s ochranou proti DDoS, VPS VDS servery 🔥 Koupit spolehlivý hosting pro webové stránky s ochranou proti DDoS, VPS VDS servery | ProHoster