Scriem un bot Telegram în limbajul R (partea 4): Construirea unui dialog secvențial și logic cu botul

Dacă ați fost deja familiarizați cu articolele anterioare trei articole din această serie, atunci deja știți să scrieți boti Telegram compleți cu tastatură.

În acest articol, vom învăța să scriem un bot care va susține un dialog secvențial. Adică, botul va pune întrebări și va aștepta de la dumneavoastră introducerea unei informații. În funcție de datele pe care le introduceți, botul va efectua anumite acțiuni.

De asemenea, în acest articol vom învăța să folosim o bază de date în spatele botului, în exemplul nostru aceasta va fi SQLite, dar puteți utiliza orice alt sistem de gestionare a bazelor de date. Mai multe detalii despre interacțiunea cu bazele de date în limbajul R am scris în această articole.

Scriem un bot Telegram în limbajul R (partea 4): Construirea unui dialog secvențial și logic cu botul

Toate articolele din seria "Scriem un bot Telegram în limbajul R"

  1. Creăm un bot și trimitem mesaje prin el în Telegram
  2. Adăugăm suport pentru comenzi și filtre de mesaje botului
  3. Cum să adăugăm suport pentru tastatură botului
  4. Construirea unui dialog secvențial și logic cu botul

Cuprins

Dacă sunteți interesat de analiza datelor, s-ar putea să vă intereseze canalele mele. telegram și youtube Marea parte a conținutului lor este dedicată limbajului R.

  1. Introducere
  2. Procesul de construire a botului
  3. Structura proiectului botului
  4. Configurația botului
  5. Creăm o variabilă de mediu
  6. Creăm o bază de date
  7. Scriem funcții pentru a lucra cu baza de date
  8. Metodele botului
  9. Filtre de mesaje
  10. Manipulatoare
  11. Codul de pornire al botului
  12. Concluzie

Introducere

Pentru ca botul să poată solicita date de la dumneavoastră și să aștepte introducerea unor informații, va trebui să mențineți starea curentă a dialogului. Cea mai bună metodă este să folosiți o bază de date încorporată, de exemplu SQLite.

Adică, logica va fi următoarea. Apelăm metoda botului, iar botul cere secvențial informațiile, așteptând la fiecare pas introducerea acestei informații, și poate efectua verificări asupra acesteia.

Vom scrie un bot cât mai simplu, mai întâi va întreba numele dumneavoastră, apoi vârsta, datele obținute fiind salvate în baza de date. La solicitarea vârstei, va verifica ca datele introduse să fie un număr, nu text.

Un astfel de dialog simplu va avea doar trei stări:

  1. start — starea normală a botului, în care nu așteaptă informații de la dumneavoastră
  2. wait_name — starea în care botul așteaptă introducerea numelui
  3. wait_age — starea în care botul așteaptă să introduci vârsta ta, numărul de ani compleți.

Procesul de construire a botului

În această articol, vom construi un bot pas cu pas, întreg procesul poate fi ilustrat astfel:
Scriem un bot Telegram în limbajul R (partea 4): Construirea unui dialog secvențial și logic cu botul

  1. Creăm configurația botului, în care vom stoca anumite setări. În cazul nostru, tokenul botului și calea către fișierul bazei de date.
  2. Creăm o variabilă de mediu în care va fi stocată calea către proiectul cu botul.
  3. Creăm baza de date în sine și o serie de funcții pentru a permite botului să interacționeze cu aceasta.
  4. Scriem metodele botului, adică funcțiile pe care acesta le va executa.
  5. Adăugăm filtre de mesaje, prin care botul va apela metodele corespunzătoare, în funcție de starea curentă a chat-ului.
  6. Adăugăm gestori, care vor lega comenzile și mesajele cu metodele corespunzătoare ale botului.
  7. Pornim botul.

Structura proiectului botului

Pentru comoditate, vom împărți codul botului nostru și celelalte fișiere asociate în următoarea structură.

  • bot.R — codul principal al botului nostru
  • db_bot_function.R — blocul de cod cu funcțiile pentru lucrul cu baza de date
  • bot_methods.R — codul metodelor botului
  • message_filters.R — filtrele de mesaje
  • handlers.R — gestori
  • config.cfg — configurația botului
  • create_db_data.sql — script SQL pentru crearea tabelului cu datele chat-ului în baza de date
  • create_db_state.sql — script SQL pentru crearea tabelului stării curente a chat-ului în baza de date
  • bot.db — baza de date a botului

Întregul proiect al botului poate fi vizualizat, sau descărcați din mine repositoriul de pe GitHub.

Configurația botului

Ca și configurație, vom folosi un simplu fișier ini, de următorul tip:

[bot_settings]
bot_token=TOKENUL_TĂU_DE_BOT

[db_settings]
db_path=C:/Calea/Către/Folderul/Proiectului/bot.db

În configurație, scriem tokenul botului și calea către baza de date, adică către fișierul bot.db, iar fișierul în sine îl vom crea în următorul pas.

Pentru boți mai complexi, se pot crea configurații și mai complexe, de asemenea, nu este necesar să scrii exact un fișier de configurare ini, poți utiliza orice alt format, inclusiv JSON.

Creăm o variabilă de mediu

Pe fiecare PC, folderul cu proiectul botului poate fi situat în directoare diferite, pe discuri diferite, așa că în cod calea către folderul proiectului va fi stabilită printr-o variabilă de mediu TG_BOT_PATH.

Crearea unei variabile de mediu se poate face în mai multe moduri, cel mai simplu — îl scrii în fișierul .Renviron.

Crearea sau editarea acestui fișier se poate face cu comanda file.edit(path.expand(file.path("~", ".Renviron"))). Execută-l și adaugă în fișier o linie:

TG_BOT_PATH=C:/Calea/Către/Proiectul/Tău

Apoi salvează fișierul .Renviron și reporniți RStudio.

Creăm o bază de date

Următorul pas este crearea bazei de date. Vom avea nevoie de 2 tabele:

  • chat_data — datele pe care botul le-a cerut utilizatorului
  • chat_state — starea curentă a tuturor chat-urilor

Aceste tabele pot fi create prin următoarea interogare SQL:

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
);

Dacă ați descărcat proiectul botului de la GitHub, atunci pentru a crea baza de date puteți folosi următorul cod în limbajul 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'))

Scriem funcții pentru a lucra cu baza de date

Avem deja fișierul de configurare și baza de date creată. Acum trebuie să scriem funcții pentru citirea și scrierea datelor în această bază.

Dacă ați descărcat proiectul de la GitHub, atunci funcțiile le puteți găsi în fișierul db_bot_function.R.

Codul funcțiilor pentru lucrul cu baza de date

# ###########################################################
# 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]])

}

Am creat 4 funcții simple:

  • get_state() — obțineți starea curentă a chat-ului din Baza de Date
  • set_state() — salvați starea curentă a chat-ului în Baza de Date
  • get_chat_data() — obțineți datele trimise de utilizator
  • set_chat_data() — salvați datele primite de la utilizator

Toate funcțiile sunt destul de simple, ele fie citesc date din bază folosind comanda dbGetQuery(), fie efectuează UPSERT operațiunea (modificarea datelor existente sau salvarea de date noi în Baza de Date) folosind funcția dbExecute().

Sintaxa operației UPSERT arată astfel:

INSERT INTO chat_data (chat_id, ${field})
VALUES(${chat_id}, '${value}') 
ON CONFLICT(chat_id) 
DO UPDATE SET ${field}='${value}';

Adică, în tabelele noastre câmpul chat_id are o restricție de unicitate și este cheia primară a tabelelor. Inițial, încercăm să adăugăm informații în tabel, și obținem o eroare dacă datele pentru chat-ul curent sunt deja prezente, în acest caz pur și simplu actualizăm informațiile pentru acest chat.

Aceste funcții le vom folosi ulterior în metodele și filtrele botului.

Metodele botului

Următorul pas în construirea botului nostru va fi crearea metodelor. Dacă ați descărcat proiectul de la GitHub, toate metodele se găsesc în fișierul bot_methods.R.

Codul metodelor botului

# ###########################################################
# 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')
  }

}

Am creat 5 metode:

  • start — Inițierea dialogului
  • state — Obțineți starea curentă a chat-ului
  • reset — Resetați starea curentă a chat-ului
  • enter_name — Botul vă întreabă numele
  • enter_age — Botul vă întreabă vârsta

Metoda start întreabă numele dumneavoastră și schimbă starea chat-ului în wait_name, adică în modul de așteptare a introducerii numelui dumneavoastră.

După aceea, trimiteți numele și acesta este procesat de metoda enter_name, botul vă salută, salvează numele primit în bază și schimbă chat-ul în starea wait_age.

În această etapă, botul așteaptă să introduceți vârsta dumneavoastră. Dacă trimiteți un text în loc de un număr, botul va spune: Ați introdus date incorecte, vă rugăm să introduceți un număr, și va aștepta din nou introducerea datelor. În cazul în care ați trimis un număr, botul va confirma acceptarea vârstei dumneavoastră, va înregistra datele primite în bază de date, vă va informa despre toate informațiile primite și va readuce starea chat-ului la poziția inițială, adică la start.

Apelând metoda state puteți solicita oricând starea curentă a chat-ului, iar metoda reset va restabili chat-ul la starea inițială.

Filtre de mesaje

În cazul nostru, aceasta este una dintre cele mai importante părți în construirea botului. Datorită filtrilor de mesaje, botul va înțelege ce informație așteaptă de la dumneavoastră și cum trebuie să o proceseze.

În proiectul de pe GitHub filtrele sunt definite în fișierul message_filters.R.

Codul filtrilor de mesaje:

# ###########################################################
# 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"
}
)

În filtre folosim funcția scrisă anterior get_state(), pentru a solicita starea curentă a chat-ului. Această funcție necesită doar 1 argument, id-ul chat-ului.

Mai departe, filtrul wait_name procesează mesajele când chat-ul este în starea wait_name, iar, în consecință, filtrul wait_age procesează mesajele când chat-ul este în starea wait_age.

Manipulatoare

Fișierul cu handlerii se numește handlers.R, și are următorul cod:

# ###########################################################
# 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)

Mai întâi, creăm handleri pentru comenzi, care vă vor permite să lansați metode pentru începutul dialogului, resetarea acestuia și solicitarea stării curente.

Apoi, creăm 2 handleri pentru mesaje folosind filtrele create în pasul anterior și adăugăm filtrul !MessageFilters$command, pentru a putea folosi comenzi în orice stare a chat-ului.

Codul de pornire al botului

Acum avem totul pregătit pentru lansare, codul principal de pornire a botului se află în fișierul bot.R.

library(telegram.bot)
library(tidyverse)
library(RSQLite)
library(DBI)
library(configr)

# ne mutăm în folderul proiectului
setwd(Sys.getenv('TG_BOT_PATH'))

# citim configurația
cfg <- read.config('config.cfg')

# creăm o instanță a botului
updater <- Updater(cfg$bot_settings$bot_token)

# Încărcarea componentelor botului
source('db_bot_function.R') # funcții pentru lucrul cu BD
source('bot_methods.R')     # metode ale botului
source('message_filters.R') # filtre de mesaje
source('handlers.R') # handleri pentru mesaje

# Adăugăm handlerii în dispatcher
updater <- updater +
  start_h +
  wait_age_h +
  wait_name_h +
  state_h +
  reset_h

# Pornim botul
updater$start_polling()

Ca rezultat, am obținut un bot de acest tip:
Scriem un bot Telegram în limbajul R (partea 4): Construirea unui dialog secvențial și logic cu botul

În orice moment, cu ajutorul comenzii /state putem solicita starea curentă a chat-ului, iar cu ajutorul comenzii /reset resetarea chatului la starea inițială și începerea dialogului din nou.

Concluzie

În acest articol, am discutat despre cum să folosim baze de date în interiorul botului și cum să construim dialoguri logice secvențiale prin fixarea stării chatului.

În acest caz, am analizat cel mai simplu exemplu, pentru a vă ajuta să înțelegeți mai bine ideea construirii unor astfel de boți; în practică, puteți construi dialoguri mult mai complexe.

În următorul articol din această serie, vom învăța cum să restricționăm utilizatorilor botului drepturile de a folosi diferitele sale metode.

Sursa: habr.com

Cumpără un hosting fiabil pentru site-uri cu protecție DDoS, servere VPS VDS 🔥 Cumpără un hosting fiabil pentru site-uri cu protecție DDoS, servere VPS VDS | ProHoster