Escrevendo um bot no Telegram em R (parte 4): Construindo um diálogo sequencial e lógico com o bot

Se você já se familiarizou com os três artigos anteriores desta série, você já sabe como escrever bots do telegram completos com teclado. Neste artigo, vamos aprender a escrever um bot que suportará um diálogo sequencial. Ou seja, o bot fará perguntas e aguardará que você insira alguma informação. Dependendo dos dados que você inserir, o bot executará algumas ações.

Neste artigo, também aprenderemos a usar um banco de dados por trás do bot, no nosso exemplo será o SQLite, mas você pode usar qualquer outro SGBD. Escrevi mais detalhadamente sobre a interação com bancos de dados na linguagem R em

Todos os artigos da série "Escrevendo um bot do telegram na linguagem R" este artigo.

Escrevendo um bot no Telegram em R (parte 4): Construindo um diálogo sequencial e lógico com o bot

Criando um bot e enviando mensagens através dele no telegram

  1. Adicionando suporte a comandos e filtros de mensagens ao bot
  2. Como adicionar suporte a teclado ao bot
  3. Construindo um diálogo sequencial e lógico com o bot
  4. Se você se interessa por análise de dados, talvez você se interesse pelos meus

Conteúdo

youtube telegram e canais. A maior parte do conteúdo é dedicada à linguagem R. O processo de construção do bot

  1. Introdução
  2. Estrutura do projeto do bot
  3. Configuração do bot
  4. Criando variável de ambiente
  5. Criando banco de dados
  6. Escrevendo funções para trabalhar com o banco de dados
  7. Métodos do bot
  8. Filtros de mensagens
  9. Manipuladores
  10. Código para iniciar o bot
  11. Para que o bot possa solicitar dados de você e aguardar a inserção de alguma informação, será necessário registrar o estado atual do diálogo. A melhor maneira de fazer isso é usar um banco de dados embutido, como o SQLite.
  12. Conclusão

Introdução

Ou seja, a lógica será a seguinte. Chamamos o método do bot, e o bot solicita sequencialmente alguma informação, aguardando a entrada dessa informação a cada passo, e pode verificar sua validade.

Vamos escrever um bot o mais simples possível, que primeiro perguntará seu nome e depois sua idade, salvando esses dados no banco de dados. Ao solicitar a idade, ele verificará se os dados inseridos são um número e não texto.

Esse diálogo simples terá apenas três estados:

start — estado normal do bot, no qual ele não espera nenhuma informação sua

  1. wait_name — estado em que o bot aguarda a inserção do nome
  2. wait_age — estado em que o bot aguarda a inserção da sua idade, em anos completos.
  3. wait_age — o estado em que o bot aguarda a entrada da sua idade, o número de anos completos.

Estrutura do projeto do bot

Neste artigo, iremos passo a passo construir um bot, todo o processo pode ser representado esquematicamente da seguinte forma:
Escrevendo um bot no Telegram em R (parte 4): Construindo um diálogo sequencial e lógico com o bot

  1. Criamos a configuração do bot, onde 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, onde será armazenado o caminho do projeto do bot.
  3. Criamos o banco de dados em si e uma série de funções para que o bot possa interagir com ele.
  4. Escrevemos os métodos do bot, ou seja, as funções que ele irá executar.
  5. Adicionamos filtros de mensagens, através dos quais o bot chamará os métodos necessários, dependendo do estado atual do chat.
  6. Adicionamos manipuladores que conectarão os comandos e mensagens aos métodos apropriados do bot.
  7. Iniciamos o bot.

Configuração do bot

Para facilitar, dividiremos o código do nosso bot e outros arquivos relacionados na seguinte estrutura.

  • bot.R — código principal do nosso bot
  • db_bot_function.R — bloco de código com funções para trabalhar com o banco de dados
  • bot_methods.R — código dos métodos do bot
  • message_filters.R — filtros de mensagens
  • handlers.R — manipuladores
  • config.cfg — configuração do bot
  • create_db_data.sql — script SQL para criar a tabela com os dados do chat no banco de dados
  • create_db_state.sql — script SQL para criar a tabela do estado atual do chat no banco de dados
  • bot.db — banco de dados do bot

Todo o projeto do bot pode ser visualizado, ou baixar do meu repositório no GitHub.

Criando variável de ambiente

Como configuração, vamos usar um simples arquivo ini, do seguinte formato:

[bot_settings]
bot_token=SEU_TOKEN_AQUI

[db_settings]
db_path=C:/CAMINHO/PARA/PASTA/DO/PROJETO/bot.db

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

Para bots mais complexos, é possível criar configurações mais elaboradas; além disso, não é obrigatório usar apenas configurações ini, você pode utilizar qualquer outro formato, incluindo JSON.

Criando banco de dados

Em cada PC, a pasta do projeto do bot pode estar localizada em diferentes diretórios e em diferentes discos, por isso, no código, o caminho para a pasta do projeto será definido através de uma variável de ambiente TG_BOT_PATH.

Criar uma variável de ambiente pode ser feito de várias maneiras, a mais simples é descrevê-la no arquivo .Renviron.

Criar ou editar esse arquivo pode ser feito com o comando file.edit(path.expand(file.path("~", ".Renviron"))). Execute-o e adicione ao arquivo uma linha:

TG_BOT_PATH=C:/CAMINHO/PARA/SEU/PROJETO

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

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

O próximo passo é criar o banco de dados. Precisaremos de 2 tabelas:

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

Essas tabelas podem ser criadas com 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, você pode usar o seguinte código em R para criar o banco.

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

Métodos do bot

Já temos um arquivo de configuração e o banco de dados criado. Agora precisamos escrever funções para ler e gravar dados nesse banco.

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

Código das funções para operação 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() — obter o estado atual do chat do BD
  • set_state() — gravar o estado atual do chat no BD
  • get_chat_data() — obter dados enviados pelo usuário
  • set_chat_data() — gravar dados recebidos do usuário

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

A sintaxe da 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}';

Ou seja, em nossas tabelas, o campo chat_id tem uma restrição de exclusividade e é a chave primária das tabelas. Inicialmente, tentamos adicionar informações à tabela e recebemos um erro se os dados do chat atual já estiverem presentes, nesse caso, apenas atualizamos as informações desse chat.

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

Filtros de mensagens

O próximo passo na construção do nosso bot será a criação de métodos. Se você baixou o projeto de GitHub, todos os métodos estão no arquivo bot_methods.R.

Código dos métodos do 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 — Iniciar o diálogo
  • state — Obter o estado atual do chat
  • reset — Redefinir o estado atual do chat
  • enter_name — O bot solicita seu nome
  • enter_age — O bot solicita sua idade

Método start solicita seu nome e muda o estado do chat para wait_name, ou seja, em modo de espera para a entrada do seu nome.

Em seguida, você envia o nome e ele é processado pelo método enter_name, o bot cumprimenta você, grava o nome recebido no banco e muda o chat para o estado wait_age.

Neste estágio, o bot aguarda que você insira a sua idade. Você envia sua idade, o bot verifica a mensagem; se em vez de um número você enviar algum texto, ele dirá: Você inseriu dados incorretos, insira um número, e aguardará que você insira os dados novamente. Caso você tenha enviado um número, o bot informará que aceitou sua idade, registrará os dados recebidos e colocará o estado do chat de volta à posição inicial, ou seja, em start.

Chamando o método state você pode solicitar a qualquer momento o estado atual do chat, e com o método resetar reiniciar o chat para o estado inicial.

Manipuladores

No nosso caso, esta é uma das partes mais importantes na construção do bot. É exatamente por meio de filtros de mensagens que o bot entenderá qual informação ele espera de você e como deve processá-la.

No projeto em GitHub os filtros estão definidos no arquivo message_filters.R.

Código dos filtros 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, utilizamos a função escrita anteriormente get_state(), para solicitar o estado atual do chat. Essa função requer apenas 1 argumento, o id do chat.

Em seguida, o filtro wait_name processa mensagens quando o chat está no estado wait_name, e, consequentemente, o filtro wait_age processa mensagens quando o chat está no estado wait_age.

Código para iniciar o bot

O arquivo com os manipuladores chama-se handlers.R, e possui 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 que você inicie métodos para iniciar o diálogo, redefini-lo e solicitar o estado atual.

Depois, criamos 2 manipuladores de mensagens utilizando os filtros criados na etapa anterior, e adicionamos a eles o filtro !MessageFilters$command, para que possamos usar comandos em qualquer estado do chat.

Para que o bot possa solicitar dados de você e aguardar a inserção de alguma informação, será necessário registrar o estado atual do diálogo. A melhor maneira de fazer isso é usar um banco de dados embutido, como o SQLite.

Agora está tudo pronto para o início; o código principal para iniciar o bot está no arquivo bot.R.

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

# Vamos para a pasta do projeto
setwd(Sys.getenv('TG_BOT_PATH'))

# Lendo a configuração
cfg <- read.config('config.cfg')

# Criando a instância do bot
updater <- Updater(cfg$bot_settings$bot_token)

# Carregando os componentes do bot
source('db_bot_function.R') # funções para trabalhar com o BD
source('bot_methods.R')     # métodos do bot
source('message_filters.R') # filtros de mensagens
source('handlers.R') # manipuladores de mensagens

# Adicionando manipuladores ao dispatcher
updater <- updater +
  start_h +
  wait_age_h +
  wait_name_h +
  state_h +
  reset_h

# Iniciando o bot
updater$start_polling()

Como resultado, nosso bot ficou assim:
Escrevendo um bot no Telegram em R (parte 4): Construindo um diálogo sequencial e lógico com o bot

A qualquer momento, com o comando /state podemos solicitar o estado atual do chat, e com o comando /reset restaurar o chat ao estado original e iniciar o diálogo novamente.

Conclusão

Neste artigo, discutimos como usar bancos de dados dentro do bot e como construir diálogos lógicos sequenciais por meio da fixação do estado do chat.

Neste caso, examinamos o exemplo mais primitivo, para que você possa entender melhor a ideia de construir esses bots; na prática, você pode criar diálogos muito mais complexos.

No próximo artigo desta série, aprenderemos a limitar os direitos dos usuários do bot sobre o uso de seus diferentes métodos.

Fonte: habr.com

Compre hospedagem confiável para sites com proteção contra DDoS, servidores VPS VDS 🔥 Compre hospedagem confiável para sites com proteção contra DDoS, servidores VPS VDS | ProHoster