Skribante telegram-bot en R (parto 4): Konstruante konsekvencan, logikan dialogon kun la bot

Se vi jam legis la antaŭan tri artikoloj de ĉi tiu serio, tiam vi jam scias kiel skribi plenrajtajn telegram-botojn per klavaro.

En ĉi tiu artikolo, ni lernos kiel skribi bot, kiu konservos konsekvencan dialogon. Tiuj. La roboto demandos al vi kaj atendos, ke vi enigu iujn informojn. Depende de la datumoj, kiujn vi enigas, la roboto faros kelkajn agojn.

Ankaŭ en ĉi tiu artikolo ni lernos kiel uzi datumbazon sub la kapuĉo de la bot, en nia ekzemplo ĝi estos SQLite, sed vi povas uzi ajnan alian DBMS. Mi skribis pli detale pri interagado kun datumbazoj en la R-lingvo en ĉi tiu artikolo.

Skribante telegram-bot en R (parto 4): Konstruante konsekvencan, logikan dialogon kun la bot

Ĉiuj artikoloj de la serio "Skribante telegram-bot en R"

  1. Ni kreas bot kaj uzas ĝin por sendi mesaĝojn en telegramo
  2. Aldonu komandan subtenon kaj mesaĝajn filtrilojn al la bot
  3. Kiel aldoni klavaran subtenon al bot
  4. Konstruante konsekvencan, logikan dialogon kun la bot

Enhavo

Se vi interesiĝas pri datuma analizo, vi eble interesiĝos pri mia telegramo и youtube kanaloj. La plej granda parto de kies enhavo estas dediĉita al la R-lingvo.

  1. Enkonduko
  2. Bot-konstruprocezo
  3. Bot-projekta strukturo
  4. Bot-agordo
  5. Kreu mediovariablon
  6. Kreante datumbazon
  7. Skribantaj funkcioj por labori kun la datumbazo
  8. Bot-metodoj
  9. Mesaĝaj filtriloj
  10. Pritraktantoj
  11. Bot-lanĉokodo
  12. konkludo

Enkonduko

Por ke la roboto petu datumojn de vi kaj atendu, ke vi enigu ajnan informon, vi devos registri la nunan staton de la dialogo. La plej bona maniero fari tion estas uzi ian enigitan datumbazon, kiel SQLite.

Tiuj. La logiko estos kiel sekvas. Ni nomas la bot-metodon, kaj la bot sinsekve petas iujn informojn de ni, kaj ĉe ĉiu paŝo ĝi atendas ke ĉi tiu informo estu enigita kaj povas kontroli ĝin.

Ni skribos la plej simplan ebla bot, unue ĝi petos vian nomon, poste vian aĝon, kaj konservos la ricevitajn datumojn al la datumbazo. Demandante aĝon, ĝi kontrolos, ke la enmetitaj datumoj estas nombro kaj ne teksto.

Tia simpla dialogo havos nur tri statojn:

  1. komenco estas la normala stato de la bot, en kiu ĝi ne atendas ajnan informon de vi
  2. wait_name - stato en kiu la bot atendas ke nomo estu enigita
  3. wait_age estas la stato en kiu la roboto atendas ke via aĝo estu enigita, la nombro da plenaj jaroj.

Bot-konstruprocezo

Dum la artikolo, ni konstruos roboton paŝon post paŝo; la tuta procezo povas esti skeme prezentita jene:
Skribante telegram-bot en R (parto 4): Konstruante konsekvencan, logikan dialogon kun la bot

  1. Ni kreas bot-agordon en kiu ni stokos iujn agordojn. En nia kazo, la bot-ĵetono, kaj la vojo al la datumbaza dosiero.
  2. Ni kreas mediovariablon en kiu la vojo al la projekto kun la bot estos stokita.
  3. Ni kreas la datumbazon mem, kaj kelkajn funkciojn por ke la bot povas interagi kun ĝi.
  4. Ni skribas bot-metodojn, t.e. la funkcioj kiujn ĝi plenumos.
  5. Aldonante mesaĝajn filtrilojn. Kun la helpo de kiu la bot aliros la necesajn metodojn, depende de la nuna stato de la babilejo.
  6. Ni aldonas traktilojn, kiuj konektos komandojn kaj mesaĝojn kun la necesaj bot-metodoj.
  7. Ni lanĉu la bot.

Bot-projekta strukturo

Por komforto, ni dividos la kodon de nia bot, kaj aliajn rilatajn dosierojn, en la sekvan strukturon.

  • bot.R — la ĉefa kodo de nia bot
  • db_bot_function.R — bloko de kodo kun funkcioj por labori kun la datumbazo
  • bot_metodoj.R - kodo de bot-metodoj
  • mesaĝo_filtriloj.R - mesaĝaj filtriloj
  • prizorgantoj.R - prizorgantoj
  • agordo.cfg - bot-agordo
  • create_db_data.sql — SQL-skripto por krei tabelon kun babildatumoj en la datumbazo
  • create_db_state.sql — SQL-skripto por krei tabelon de la nuna babileja stato en la datumbazo
  • bot.db - bot-datumbazo

Vi povas vidi la tutan robotprojekton, aŭ скачать de mia deponejo sur GitHub.

Bot-agordo

Ni uzos la kutiman kiel agordon ini dosiero, la sekva formo:

[bot_settings]
bot_token=ТОКЕН_ВАШЕГО_БОТА

[db_settings]
db_path=C:/ПУТЬ/К/ПАПКЕ/ПРОЕКТА/bot.db

En la agordo ni skribas la bot-ĵetonon kaj la vojon al la datumbazo, t.e. al la bot.db dosiero; ni kreos la dosieron mem en la sekva paŝo.

Por pli kompleksaj robotoj, vi povas krei pli kompleksajn agordojn, krome, ne necesas skribi ini-agordon, vi povas uzi ajnan alian formaton inkluzive de JSON.

Kreu mediovariablon

En ĉiu komputilo, la dosierujo kun la bot-projekto povas troviĝi en malsamaj dosierujoj kaj sur malsamaj diskoj, do en la kodo la vojo al la projekta dosierujo estos fiksita per mediovariablo. TG_BOT_PATH.

Estas pluraj manieroj krei mediovariablon, la plej simpla estas skribi ĝin en dosiero .Renviron.

Vi povas krei aŭ redakti ĉi tiun dosieron per la komando file.edit(path.expand(file.path("~", ".Renviron"))). Efektivigu ĝin kaj aldonu unu linion al la dosiero:

TG_BOT_PATH=C:/ПУТЬ/К/ВАШЕМУ/ПРОЕКТУ

Poste konservu la dosieron .Renviron kaj rekomencu RStudio.

Kreante datumbazon

La sekva paŝo estas krei datumbazon. Ni bezonos 2 tablojn:

  • chat_data - datumoj kiujn la bot petis de la uzanto
  • chat_state — aktuala stato de ĉiuj babilejoj

Vi povas krei ĉi tiujn tabelojn uzante la sekvan SQL-demandon:

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

Se vi elŝutis la bot-projekton de GitHub, tiam por krei la datumbazon vi povas uzi la sekvan kodon en 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'))

Skribantaj funkcioj por labori kun la datumbazo

Ni jam havas agordan dosieron preta kaj datumbazon kreita. Nun vi devas skribi funkciojn por legi kaj skribi datumojn al ĉi tiu datumbazo.

Se vi elŝutis la projekton de GitHub, tiam vi povas trovi la funkciojn en la dosiero db_bot_function.R.

Funkcia kodo por labori kun la datumbazo

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

}

Ni kreis 4 simplajn funkciojn:

  • get_state() — akiri la nunan babilejon de la datumbazo
  • set_state() — skribu la nunan babilejon al la datumbazo
  • get_chat_data() — ricevi datumojn senditajn de la uzanto
  • set_chat_data() — registri datumojn ricevitajn de la uzanto

Ĉiuj funkcioj estas sufiĉe simplaj, ili aŭ legas datumojn de la datumbazo per la komando dbGetQuery(), aŭ kompromiti UPSERT operacio (ŝanĝi ekzistantajn datumojn aŭ skribi novajn datumojn al la datumbazo), uzante la funkcion dbExecute().

La sintakso por la operacio UPSERT estas jena:

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

Tiuj. en nia tabelkampo babilejo_id havas unikeclimon kaj estas la ĉefa ŝlosilo de tabeloj. Komence, ni provas aldoni informojn al la tabelo, kaj ni ricevas eraron se datumoj por la nuna babilejo jam ĉeestas, en kiu kazo ni simple ĝisdatigas la informojn por ĉi tiu babilejo.

Poste, ni uzos ĉi tiujn funkciojn en la metodoj kaj filtriloj de la bot.

Bot-metodoj

La sekva paŝo en konstruado de nia bot estas krei metodojn. Se vi elŝutis la projekton de GitHub, tiam ĉiuj metodoj estas en la dosiero bot_metodoj.R.

Bot-metoda kodo

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

}

Ni kreis 5 metodojn:

  • start — Komencu dialogon
  • stato — Akiru la nunan babilejon
  • restarigi — Restarigi la nunan babilejon
  • enter_name — La roboto petas vian nomon
  • enter_age — La bot demandas vian aĝon

Metodo start petas vian nomon, kaj ŝanĝas la babilejon al atendu_nomo, t.e. al atendo por enigi vian nomon.

Poste, vi sendas la nomon kaj ĝi estas procesita per la metodo enter_name, la bot salutas vin, skribas la ricevitan nomon en la datumbazon, kaj ŝanĝas la babilejon al la stato atendi_aĝon.

En ĉi tiu etapo, la bot atendas, ke vi eniru vian aĝon. Vi sendas vian aĝon, la bot kontrolas la mesaĝon, se vi sendis iun tekston anstataŭ nombron, ĝi diros: Ты ввёл некорректные данные, введи число, kaj atendos ke vi reenigu viajn datumojn. Se vi sendis numeron, la roboto raportos, ke ĝi akceptis vian aĝon, skribos la ricevitajn datumojn al la datumbazo, raportos ĉiujn datumojn ricevitajn de vi kaj resendos la babilejon al sia originala pozicio, t.e. V start.

Nomante la metodon state vi povas peti la nunan babilejon iam ajn, kaj uzante la reset redonu la babilejon al ĝia originala stato.

Mesaĝaj filtriloj

En nia kazo, ĉi tio estas unu el la plej gravaj partoj en konstruado de bot. Estas helpe de mesaĝaj filtriloj, ke la bot komprenos, kiajn informojn ĝi atendas de vi kaj kiel ĝi devas esti prilaborita.

En la projekto sur GitHub filtriloj estas registritaj en la dosiero mesaĝo_filtriloj.R.

Mesaĝa filtrila kodo:

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

En filtriloj ni uzas la antaŭe skribitan funkcion get_state(), por peti la nunan staton de la babilejo. Ĉi tiu funkcio postulas nur 1 argumenton, babilejon.

Sekva filtrilo atendu_nomo prilaboras mesaĝojn kiam la babilejo estas en stato wait_name, kaj sekve la filtrilo atendi_aĝon prilaboras mesaĝojn kiam la babilejo estas en stato wait_age.

Pritraktantoj

La dosiero kun pritraktiloj estas nomita prizorgantoj.R, kaj havas la sekvan kodon:

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

Unue ni kreas komand-traktilojn, kiuj permesos al vi ruli metodojn por komenci dialogon, restarigi ĝin kaj pridemandi la nunan staton.

Poste, ni kreas 2 mesaĝajn prizorgantojn uzante la filtrilojn kreitajn en la antaŭa paŝo, kaj aldonas filtrilon al ili !MessageFilters$command, por ke ni povu uzi komandojn en iu ajn babilejo.

Bot-lanĉokodo

Nun ni havas ĉion preta por lanĉi, la ĉefa kodo por lanĉi la bot estas en la dosiero bot.R.

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

# переходим в папку проекта
setwd(Sys.getenv('TG_BOT_PATH'))

# читаем конфиг
cfg <- read.config('config.cfg')

# создаём экземпляр бота
updater <- Updater(cfg$bot_settings$bot_token)

# Загрузка компонентов бота
source('db_bot_function.R') # функции для работы с БД
source('bot_methods.R')     # методы бота
source('message_filters.R') # фильтры сообщений
source('handlers.R') # обработчики сообщений

# Добавляем обработчики в диспетчер
updater <- updater +
  start_h +
  wait_age_h +
  wait_name_h +
  state_h +
  reset_h

# Запускаем бота
updater$start_polling()

Kiel rezulto, ni ricevis ĉi tiun bot:
Skribante telegram-bot en R (parto 4): Konstruante konsekvencan, logikan dialogon kun la bot

En ajna momento uzante la komandon /state ni povas pridemandi la nunan babilejon, kaj uzante la komandon /reset redonu la babilejon al ĝia originala stato kaj rekomencu la dialogon.

konkludo

En ĉi tiu artikolo, ni eksciis kiel uzi datumbazon ene de bot, kaj kiel konstrui sinsekvajn logikajn dialogojn registrante la babilejon.

En ĉi tiu kazo, ni rigardis la plej primitivan ekzemplon, por ke estus pli facile por vi kompreni la ideon konstrui tiajn robotojn; praktike, vi povas konstrui multe pli kompleksajn dialogojn.

En la sekva artikolo en ĉi tiu serio, ni lernos kiel limigi la rajtojn de bot-uzantoj uzi diversajn ĝiajn metodojn.

fonto: www.habr.com

Aldoni komenton