Escrevendo um bot de telegrama em R (parte 4): Construindo um diálogo lógico e consistente com o bot

Se você já leu o anterior três artigos desta série, você já sabe como escrever bots de telegrama completos com um teclado.

Neste artigo, aprenderemos como escrever um bot que mantenha um diálogo consistente. Aqueles. O bot fará perguntas e esperará que você insira algumas informações. Dependendo dos dados inseridos, o bot realizará algumas ações.

Também neste artigo aprenderemos como usar um banco de dados sob o capô do bot, em nosso exemplo será SQLite, mas você pode usar qualquer outro SGBD. Escrevi com mais detalhes sobre a interação com bancos de dados na linguagem R em Este artigo.

Escrevendo um bot de telegrama em R (parte 4): Construindo um diálogo lógico e consistente com o bot

Todos os artigos da série “Escrevendo um bot de telegrama em R”

  1. Criamos um bot e o utilizamos para enviar mensagens no telegrama
  2. Adicione suporte a comandos e filtros de mensagens ao bot
  3. Como adicionar suporte de teclado a um bot
  4. Construindo um diálogo consistente e lógico com o bot

Conteúdo

Se você estiver interessado em análise de dados, talvez esteja interessado em meu telegrama и Youtube canais. A maior parte do conteúdo é dedicada à linguagem R.

  1. Introdução
  2. Processo de construção de bot
  3. Estrutura do projeto de bot
  4. Configuração do bot
  5. Crie uma variável de ambiente
  6. Criando um banco de dados
  7. Escrevendo funções para trabalhar com o banco de dados
  8. Métodos de bot
  9. Filtros de mensagens
  10. Manipuladores
  11. Código de inicialização do bot
  12. Conclusão

Introdução

Para que o bot solicite seus dados e espere que você insira qualquer informação, você precisará registrar o estado atual do diálogo. A melhor maneira de fazer isso é usar algum tipo de banco de dados incorporado, como o SQLite.

Aqueles. A lógica será a seguinte. Chamamos o método bot, e o bot nos solicita sequencialmente algumas informações, e a cada passo ele espera que essas informações sejam inseridas e possa verificá-las.

Escreveremos o bot mais simples possível, primeiro ele pedirá seu nome, depois sua idade, e salvará os dados recebidos no banco de dados. Ao perguntar a idade, verificará se os dados inseridos são um número e não um texto.

Um diálogo tão simples terá apenas três estados:

  1. start é o estado normal do bot, no qual ele não espera nenhuma informação sua
  2. wait_name - estado em que o bot espera que um nome seja inserido
  3. wait_age é o estado em que o bot espera que sua idade seja inserida, o número de anos completos.

Processo de construção de bot

Durante o artigo, construiremos um bot passo a passo; todo o processo pode ser esquematicamente representado da seguinte forma:
Escrevendo um bot de telegrama em R (parte 4): Construindo um diálogo lógico e consistente com o bot

  1. Criamos uma configuração de bot na qual armazenaremos algumas configurações. No nosso caso, o token do bot e o caminho para o arquivo do banco de dados.
  2. Criamos uma variável de ambiente na qual será armazenado o caminho para o projeto com o bot.
  3. Criamos o próprio banco de dados e uma série de funções para que o bot possa interagir com ele.
  4. Escrevemos métodos de bot, ou seja, as funções que irá desempenhar.
  5. Adicionando filtros de mensagens. Com a ajuda do qual o bot acessará os métodos necessários, dependendo do estado atual do chat.
  6. Adicionamos manipuladores que conectarão comandos e mensagens aos métodos de bot necessários.
  7. Vamos lançar o bot.

Estrutura do projeto de bot

Por conveniência, dividiremos o código do nosso bot e outros arquivos relacionados na seguinte estrutura.

  • bot.R — o código principal do nosso bot
  • db_bot_function.R — um bloco de código com funções para trabalhar com o banco de dados
  • bot_methods.R — código dos métodos do bot
  • filtros_de_mensagem.R — filtros de mensagens
  • manipuladores.R - manipuladores
  • configuração.cfg - configuração do bot
  • create_db_data.sql — Script SQL para criação de tabela com dados de chat no banco de dados
  • create_db_state.sql — Script SQL para criar uma tabela do estado atual do chat no banco de dados
  • bot.db - banco de dados de bots

Você pode visualizar todo o projeto do bot ou baixar do meu repositório no GitHub.

Configuração do bot

Usaremos o usual como configuração arquivo ini, o seguinte formato:

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

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

Na configuração escrevemos o token do bot e o caminho para o banco de dados, ou seja, ao arquivo bot.db; criaremos o próprio arquivo na próxima etapa.

Para bots mais complexos, você pode criar configurações mais complexas, além disso, não é necessário escrever uma configuração ini, você pode usar qualquer outro formato incluindo JSON.

Crie uma variável de ambiente

Em cada PC, a pasta com o projeto do bot pode estar localizada em diretórios e unidades diferentes, portanto, no código, o caminho para a pasta do projeto será definido por meio de uma variável de ambiente TG_BOT_PATH.

Existem diversas maneiras de criar uma variável de ambiente, a mais simples é escrevê-la em um arquivo .Renviron.

Você pode criar ou editar este arquivo usando o comando file.edit(path.expand(file.path("~", ".Renviron"))). Execute-o e adicione uma linha ao arquivo:

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

Em seguida salve o arquivo .Renviron e reinicie o RStudio.

Criando um banco de dados

A próxima etapa é criar um banco de dados. Precisaremos de 2 tabelas:

  • chat_data — dados que o bot solicitou do usuário
  • chat_state — estado atual de todos os chats

Você pode criar essas tabelas usando a seguinte consulta 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
);

Se você baixou o projeto do bot em GitHub, então para criar o banco de dados você pode usar o seguinte código em 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'))

Escrevendo funções para trabalhar com o banco de dados

Já temos um arquivo de configuração pronto e um banco de dados criado. Agora você precisa escrever funções para ler e gravar dados neste banco de dados.

Se você baixou o projeto de GitHub, então você pode encontrar as funções no arquivo db_bot_function.R.

Código de função para trabalhar com o banco de dados

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

}

Criamos 4 funções simples:

  • get_state() — obtém o estado atual do chat do banco de dados
  • set_state() — escreve o estado atual do chat no banco de dados
  • get_chat_data() — receber dados enviados pelo usuário
  • set_chat_data() — registrar dados recebidos do usuário

Todas as funções são bastante simples, elas leem dados do banco de dados usando o comando dbGetQuery(), ou cometer UPSERT operação (alterar dados existentes ou gravar novos dados no banco de dados), usando a função dbExecute().

A sintaxe para a operação UPSERT é a seguinte:

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

Aqueles. em nosso campo de tabelas id_chat tem uma restrição de exclusividade e é a chave primária das tabelas. Inicialmente, tentamos adicionar informações à tabela, e obtemos um erro se os dados do chat atual já estiverem presentes, neste caso simplesmente atualizamos as informações deste chat.

A seguir, usaremos essas funções nos métodos e filtros do bot.

Métodos de bot

A próxima etapa na construção de nosso bot é criar métodos. Se você baixou o projeto de GitHub, então todos os métodos estão no arquivo bot_methods.R.

Código do método bot

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

}

Criamos 5 métodos:

  • start — Inicia uma caixa de diálogo
  • state — Obtenha o estado atual do chat
  • reset — Redefine o estado atual do chat
  • enter_name — O bot pede seu nome
  • enter_age — O bot pergunta sua idade

método start pede seu nome e muda o estado do bate-papo para nome_espera, ou seja em espera para inserir seu nome.

A seguir, você envia o nome e ele é processado pelo método enter_name, o bot cumprimenta você, escreve o nome recebido no banco de dados e muda o chat para o estado espera_idade.

Nesta fase, o bot espera que você insira sua idade. Você envia sua idade, o bot verifica a mensagem, se você enviou algum texto em vez de um número, ele dirá: Ты ввёл некорректные данные, введи числоe esperará que você insira seus dados novamente. Se você enviou um número, o bot informará que aceitou sua idade, gravará os dados recebidos no banco de dados, reportará todos os dados recebidos de você e retornará o estado do chat à sua posição original, ou seja, V start.

Chamando o método state você pode solicitar o status atual do bate-papo a qualquer momento e usando o reset retornar o chat ao seu estado original.

Filtros de mensagens

No nosso caso, esta é uma das partes mais importantes na construção de um bot. É com a ajuda dos filtros de mensagens que o bot entenderá quais informações espera de você e como devem ser processadas.

No projeto em GitHub filtros são registrados no arquivo filtros_de_mensagem.R.

Código do filtro de mensagens:

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

Nos filtros usamos a função escrita anteriormente get_state(), para solicitar o estado atual do chat. Esta função requer apenas 1 argumento, ID do chat.

Próximo filtro nome_espera processa mensagens quando o chat está em estado wait_namee, consequentemente, o filtro espera_idade processa mensagens quando o chat está em estado wait_age.

Manipuladores

O arquivo com manipuladores é chamado manipuladores.R, e tem o seguinte código:

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

Primeiro, criamos manipuladores de comandos que permitirão executar métodos para iniciar um diálogo, redefini-lo e consultar o estado atual.

A seguir, criamos 2 manipuladores de mensagens usando os filtros criados na etapa anterior e adicionamos um filtro a eles !MessageFilters$command, para que possamos usar comandos em qualquer estado de chat.

Código de inicialização do bot

Agora que temos tudo pronto para lançar, o código principal para lançar o bot está no arquivo 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()

Como resultado, obtivemos este bot:
Escrevendo um bot de telegrama em R (parte 4): Construindo um diálogo lógico e consistente com o bot

A qualquer momento usando o comando /state podemos consultar o estado atual do chat e usando o comando /reset retorne o chat ao seu estado original e reinicie o diálogo.

Conclusão

Neste artigo, descobrimos como usar um banco de dados dentro de um bot e como construir diálogos lógicos sequenciais registrando o estado do chat.

Neste caso, olhamos para o exemplo mais primitivo, para que fosse mais fácil para você entender a ideia de construir tais bots; na prática, você pode construir diálogos muito mais complexos.

No próximo artigo desta série, aprenderemos como restringir os direitos dos usuários de bot de usar vários de seus métodos.

Fonte: habr.com

Adicionar um comentário