Писање телеграм бота у Р (део 4): Изградња доследног, логичног дијалога са ботом

Ако сте већ прочитали претходни три чланка из ове серије, онда већ знате како да напишете пуноправне телеграм ботове са тастатуром.

У овом чланку ћемо научити како да напишемо бота који ће одржавати конзистентан дијалог. Оне. Бот ће вам постављати питања и чекати да унесете неке информације. У зависности од података које унесете, бот ће извршити неке радње.

Такође у овом чланку ћемо научити како да користимо базу података испод хаубе бота, у нашем примеру ће то бити СКЛите, али можете користити било који други ДБМС. Писао сам детаљније о интеракцији са базама података на језику Р у Овај чланак.

Писање телеграм бота у Р (део 4): Изградња доследног, логичног дијалога са ботом

Сви чланци из серије „Писање телеграм бота у Р“

  1. Креирамо бота и користимо га за слање порука у телеграму
  2. Додајте подршку за команде и филтере за поруке боту
  3. Како додати подршку за тастатуру боту
  4. Изградња доследног, логичног дијалога са ботом

Садржина

Ако сте заинтересовани за анализу података, можда ће вас занимати моја телеграм и ИоуТубе канала. Већина садржаја је посвећена Р језику.

  1. Увод
  2. Процес изградње бота
  3. Структура бот пројекта
  4. Бот цонфиг
  5. Направите променљиву окружења
  6. Креирање базе података
  7. Писање функција за рад са базом података
  8. Бот методе
  9. Филтери за поруке
  10. Руковаоци
  11. Код за покретање бота
  12. Закључак

Увод

Да би бот од вас тражио податке и чекао да унесете било коју информацију, мораћете да снимите тренутно стање дијалога. Најбољи начин да то урадите је да користите неку врсту уграђене базе података, као што је СКЛите.

Оне. Логика ће бити следећа. Позивамо метод бот, а бот узастопно тражи неке информације од нас и на сваком кораку чека да се та информација унесе и може да их провери.

Написаћемо најједноставнији могући бот, прво ће тражити ваше име, затим ваше године, а примљене податке ће сачувати у бази података. Када пита за старост, провериће да ли су унети подаци број, а не текст.

Такав једноставан дијалог ће имати само три стања:

  1. старт је нормално стање бота, у којем не очекује никакве информације од вас
  2. ваит_наме - стање у којем бот чека да се унесе име
  3. ваит_аге је стање у којем бот чека да се унесе ваш узраст, број пуних година.

Процес изградње бота

Током чланка, градићемо бот корак по корак; цео процес се може шематски приказати на следећи начин:
Писање телеграм бота у Р (део 4): Изградња доследног, логичног дијалога са ботом

  1. Креирамо конфигурацију бота у којој ћемо сачувати нека подешавања. У нашем случају, токен бота и путања до датотеке базе података.
  2. Креирамо променљиву окружења у којој ће се чувати путања до пројекта са ботом.
  3. Ми креирамо саму базу података и бројне функције тако да бот може да комуницира са њом.
  4. Пишемо бот методе, тј. функције које ће обављати.
  5. Додавање филтера за поруке. Уз помоћ којих ће бот приступити потребним методама, у зависности од тренутног стања ћаскања.
  6. Додамо руковаоце који ће повезивати команде и поруке са потребним бот методама.
  7. Хајде да покренемо бота.

Структура бот пројекта

Ради практичности, поделићемо код нашег бота и друге повезане датотеке у следећу структуру.

  • бот.Р — главни код нашег бота
  • дб_бот_фунцтион.Р — блок кода са функцијама за рад са базом података
  • бот_метходс.Р — код бот метода
  • филтери_порука.Р — филтери порука
  • руковаоци.Р - руковаоци
  • цонфиг.цфг - конфигурација бота
  • цреате_дб_дата.скл — СКЛ скрипта за креирање табеле са подацима за ћаскање у бази података
  • цреате_дб_стате.скл — СКЛ скрипта за креирање табеле тренутног стања ћаскања у бази података
  • бот.дб - бот база података

Можете погледати цео пројекат бота, или преузети из мог спремиште на ГитХуб-у.

Бот цонфиг

Користићемо уобичајени као конфигурацију ини фајл, следећи образац:

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

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

У конфигурацији уписујемо бот токен и путању до базе података, тј. у датотеку бот.дб; креираћемо саму датотеку у следећем кораку.

За сложеније ботове можете креирати сложеније конфигурације, осим тога, није потребно писати ини конфигурацију, можете користити било који други формат укључујући ЈСОН.

Направите променљиву окружења

На сваком рачунару фасцикла са пројектом бота може да се налази у различитим директоријумима и на различитим дисковима, тако да ће у коду путања до фасцикле пројекта бити подешена преко променљиве окружења TG_BOT_PATH.

Постоји неколико начина за креирање променљиве окружења, најједноставнији је да је запишете у датотеку .Ренвирон.

Можете креирати или уређивати ову датотеку користећи команду file.edit(path.expand(file.path("~", ".Renviron"))). Извршите га и додајте једну линију у датотеку:

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

Затим сачувајте датотеку .Ренвирон и поново покрените РСтудио.

Креирање базе података

Следећи корак је креирање базе података. Требаће нам 2 табеле:

  • цхат_дата — подаци које је бот захтевао од корисника
  • цхат_стате — тренутно стање свих ћаскања

Можете креирати ове табеле користећи следећи СКЛ упит:

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

Ако сте преузели бот пројекат са ГитХуб, затим за креирање базе података можете користити следећи код у Р.

# Скрипт создания базы данных
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'))

Писање функција за рад са базом података

Већ имамо спреман конфигурациони фајл и креирану базу података. Сада морате написати функције за читање и писање података у ову базу података.

Ако сте пројекат преузели са ГитХуб, онда можете пронаћи функције у датотеци дб_бот_фунцтион.Р.

Код функције за рад са базом података

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

}

Направили смо 4 једноставне функције:

  • get_state() — добити тренутно стање ћаскања из базе података
  • set_state() — упишите тренутно стање ћаскања у базу података
  • get_chat_data() — примати податке које шаље корисник
  • set_chat_data() — евидентирати податке примљене од корисника

Све функције су прилично једноставне, или читају податке из базе података помоћу команде dbGetQuery(), или извршите UPSERT операција (промена постојећих података или уписивање нових података у базу), коришћењем функције dbExecute().

Синтакса за операцију УПСЕРТ је следећа:

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

Оне. у пољу наших табела цхат_ид има ограничење јединствености и примарни је кључ табела. У почетку покушавамо да додамо информације у табелу и добијамо грешку ако су подаци за тренутни ћаскање већ присутни, у ком случају једноставно ажурирамо информације за ово ћаскање.

Затим ћемо користити ове функције у методама и филтерима бота.

Бот методе

Следећи корак у изградњи нашег бота је креирање метода. Ако сте пројекат преузели са ГитХуб, онда су све методе у датотеци бот_метходс.Р.

Код бот методе

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

}

Направили смо 5 метода:

  • старт — Покрените дијалог
  • стање — Добијте тренутно стање ћаскања
  • ресет — Ресетујте тренутно стање ћаскања
  • ентер_наме — Бот тражи ваше име
  • ентер_аге — Бот пита за ваше године

метод start тражи ваше име и мења стање ћаскања у чекај_име, тј. у приправно стање за унос вашег имена.

Затим шаљете име и оно се обрађује методом enter_name, бот вас поздравља, уписује примљено име у базу података и пребацује ћаскање у стање ваит_аге.

У овој фази, бот очекује да унесете своје године. Ви пошаљете своје године, бот проверава поруку, ако сте послали неки текст уместо броја, он ће рећи: Ты ввёл некорректные данные, введи число, и сачекаће да поново унесете своје податке. Ако сте послали број, бот ће пријавити да је прихватио ваше године, уписати примљене податке у базу, пријавити све примљене податке од вас и вратити стање ћаскања на првобитну позицију, тј. В start.

Позивањем методе state можете затражити тренутни статус ћаскања у било ком тренутку, а помоћу reset вратите ћаскање у првобитно стање.

Филтери за поруке

У нашем случају, ово је један од најважнијих делова у изградњи бота. Уз помоћ филтера порука бот ће разумети које информације очекује од вас и како их треба обрадити.

У пројекту на ГитХуб филтери су регистровани у датотеци филтери_порука.Р.

Код филтера за поруке:

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

У филтерима користимо претходно написану функцију get_state(), да бисте затражили тренутно стање ћаскања. Ова функција захтева само 1 аргумент, ИД за ћаскање.

Следећи филтер чекај_име обрађује поруке када је ћаскање у стању wait_name, а сходно томе и филтер ваит_аге обрађује поруке када је ћаскање у стању wait_age.

Руковаоци

Позива се датотека са руковаоцима руковаоци.Р, и има следећи код:

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

Прво креирамо руковаоце командама који ће вам омогућити да покренете методе за покретање дијалога, ресетујете га и упитате тренутно стање.

Затим креирамо 2 обрађивача порука користећи филтере направљене у претходном кораку и додамо им филтер !MessageFilters$command, тако да можемо да користимо команде у било ком стању ћаскања.

Код за покретање бота

Сада имамо све спремно за покретање, главни код за покретање бота је у датотеци бот.Р.

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

Као резултат, добили смо овог бота:
Писање телеграм бота у Р (део 4): Изградња доследног, логичног дијалога са ботом

У било ком тренутку користећи команду /state можемо да упитамо тренутно стање ћаскања, и користећи команду /reset вратите ћаскање у првобитно стање и поново започните дијалог.

Закључак

У овом чланку смо схватили како да користимо базу података унутар бота и како да направимо секвенцијалне логичке дијалоге снимањем стања ћаскања.

У овом случају, погледали смо најпримитивнији пример, како би вам било лакше да разумете идеју изградње оваквих ботова; у пракси можете градити много сложеније дијалоге.

У следећем чланку из ове серије научићемо како да ограничимо права корисника ботова да користе различите његове методе.

Извор: ввв.хабр.цом

Додај коментар