ProHoster > Blog > administração > Escrevendo um bot de telegrama em R (parte 4): Construindo um diálogo lógico e consistente com o bot
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.
Todos os artigos da série “Escrevendo um bot de telegrama em R”
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.
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:
start é o estado normal do bot, no qual ele não espera nenhuma informação sua
wait_name - estado em que o bot espera que um nome seja inserido
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:
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.
Criamos uma variável de ambiente na qual será armazenado o caminho para o projeto com o bot.
Criamos o próprio banco de dados e uma série de funções para que o bot possa interagir com ele.
Escrevemos métodos de bot, ou seja, as funções que irá desempenhar.
Adicionando filtros de mensagens. Com a ajuda do qual o bot acessará os métodos necessários, dependendo do estado atual do chat.
Adicionamos manipuladores que conectarão comandos e mensagens aos métodos de bot necessários.
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
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:
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.