Píšeme telegramového bota v jazyku R (časť 4): Vytváranie sekvenčného, logického dialógu s botom

Ak ste sa už oboznámili s predchádzajúcimi tromi článkami 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 tomto článku.

Píšeme telegramového bota v jazyku R (časť 4): Vytváranie sekvenčného, logického dialógu s botom

Všetky články zo série "Píšeme telegramového bota v jazyku R"

  1. Vytvárame bota a posielame s ním správy do telegramu
  2. Pridávame botovi podporu príkazov a filtre správ
  3. Ako pridať botovi podporu klávesnice
  4. Vytváranie sekvenčného, logického dialógu s botom

Obsah

Ak vás zaujíma analýza dát, mohli by vás zaujímať moje telegram a youtube kanály. Väčšina obsahu je venovaná jazyku R.

  1. Úvod
  2. Proces vytvárania bota
  3. Štruktúra projektu bota
  4. Konfigurácia bota
  5. Vytvárame premennú prostredia
  6. Vytvárame databázu
  7. Píšeme funkcie na prácu s databázou
  8. Metódy bota
  9. Filtre správ
  10. Spracovatelia
  11. Kód na spustenie bota
  12. Záver

Ú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:

  1. start — bežný stav bota, v ktorom nečaká od vás žiadne informácie
  2. wait_name — stav, v ktorom bot čaká na zadané meno
  3. 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:
Píšeme telegramového bota v jazyku R (časť 4): Vytváranie sekvenčného, logického dialógu s botom

  1. 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.
  2. Vytvoríme premennú prostredia, v ktorej bude uložená cesta k projektu s botom.
  3. Vytvoríme samotnú databázu a rad funkcií, aby bot mohol s ňou interagovať.
  4. Napíšeme metódy bota, teda funkcie, ktoré bude vykonávať.
  5. Pridáme filtre správ. S ich pomocou sa bot bude obracať na potrebné metódy v závislosti od aktuálneho stavu chatu.
  6. Pridáme spracovateľov, ktorí prepoja príkazy a správy s potrebnými metódami bota.
  7. 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 stiahnuť z môjho repozitára na GitHub.

Konfigurácia bota

Ako konfiguráciu budeme používať obyčajný ini súbor, nasledujúceho typu:

[bot_settings]
bot_token=TOKEN_VAŠEHO_BOTA

[db_settings]
db_path=C:/CESTA/K/PRIEČINKU/PROJEKTU/bot.db

Do 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 GitHub, 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 GitHub, 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 DB
  • set_state() — zapísať aktuálny stav chatu do DB
  • get_chat_data() — získať dáta zaslané používateľom
  • set_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 GitHub, 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 GitHub 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:
Píšeme telegramového bota v jazyku R (časť 4): Vytváranie sekvenčného, logického dialógu s botom

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

Kúpte si spoľahlivý hosting pre webové stránky s ochranou proti DDoS, VPS VDS servery 🔥 Kúpte si spoľahlivý hosting pre webové stránky s ochranou proti DDoS, VPS VDS servery | ProHoster