Si ya has revisado los artículos anteriores En este artículo aprenderemos a escribir un bot que mantenga un diálogo secuencial. Es decir, el bot te hará preguntas y esperará que introduzcas alguna información. Dependiendo de los datos que introduzcas, el bot llevará a cabo ciertas acciones.
También en este artículo aprenderemos a utilizar bases de datos detrás del bot; en nuestro ejemplo, será SQLite, pero puedes usar cualquier otro SGBD. Escribí más sobre la interacción con bases de datos en el lenguaje R en
Todos los artículos de la serie "Escribiendo un bot de telegram en el lenguaje R" .

Creamos un bot y enviamos mensajes a telegram a través de él
Contenido
canales de youtube. La mayor parte del contenido está dedicado al lenguaje R. y Estructura del proyecto del bot
Introducción
Escribiremos un bot lo más simple posible; primero preguntará tu nombre y luego tu edad, guardando los datos obtenidos en la base de datos. Al preguntar la edad, verificará que los datos introducidos sean un número y no texto.
Este simple diálogo tendrá solo tres estados:
start — estado normal del bot, en el que no espera información de ti
wait_name — estado en el que el bot espera que introduzcas tu nombre
- wait_age — estado en el que el bot espera que introduzcas tu edad, número de años completos.
- wait_name — estado en el que el bot está esperando la introducción del nombre
- wait_age — estado en el que el bot está esperando la introducción de su edad, cantidad de años completos.
Configuración del bot
A lo largo del artículo, construiremos un bot paso a paso, y todo el proceso se puede representar esquemáticamente de la siguiente manera:

- Creamos la configuración del bot, donde almacenaremos algunas configuraciones. En nuestro caso, el token del bot y la ruta al archivo de la base de datos.
- Creamos una variable de entorno que almacenará la ruta del proyecto con el bot.
- Creamos la base de datos en sí y una serie de funciones para que el bot pueda interactuar con ella.
- Escribimos los métodos del bot, es decir, las funciones que realizará.
- Añadimos filtros de mensajes. A través de los cuales el bot llamará a los métodos adecuados, dependiendo del estado actual del chat.
- Añadimos controladores que conectarán comandos y mensajes con los métodos correspondientes del bot.
- Iniciamos el bot.
Creamos una variable de entorno
Para mayor comodidad, organizaremos el código de nuestro bot y otros archivos relacionados en la siguiente estructura.
- bot.R — código principal de nuestro bot
- db_bot_function.R — bloque de código con funciones para trabajar con la base de datos
- bot_methods.R — código de los métodos del bot
- message_filters.R — filtros de mensajes
- handlers.R — controladores
- config.cfg — configuración del bot
- create_db_data.sql — script SQL para crear la tabla con los datos del chat en la base de datos
- create_db_state.sql — script SQL para crear la tabla con el estado actual del chat en la base de datos
- bot.db — base de datos del bot
Se puede ver todo el proyecto del bot o desde mi .
Creamos una base de datos
Como configuración, utilizaremos un archivo , de la siguiente forma:
[bot_settings]
bot_token=SU_TOKEN_DEL_BOT
[db_settings]
db_path=C:/RUTA/AL/CARPETA/DEL/PROYECTO/bot.dbEn la configuración, escribimos el token del bot y la ruta a la base de datos, es decir, al archivo bot.db, que crearemos en el siguiente paso.
Para bots más complejos, también se pueden crear configuraciones más elaboradas, y no es necesario escribir exactamente una configuración ini, puedes usar cualquier otro formato incluyendo JSON.
Escribimos funciones para trabajar con la base de datos
En cada PC, la carpeta del proyecto del bot puede estar en diferentes directorios y en diferentes discos, por lo que en el código la ruta a la carpeta del proyecto se establecerá a través de una variable de entorno TG_BOT_PATH.
Se puede crear una variable de entorno de varias maneras, la más sencilla es escribirla en el archivo .Renviron.
Puedes crear o editar este archivo usando el comando file.edit(path.expand(file.path("~", ".Renviron"))). Ejecútalo y agrega en el archivo una línea:
TG_BOT_PATH=C:/RUTA/AL/TU/PROYECTOLuego guarda el archivo .Renviron y reinicia RStudio.
Métodos del bot
El siguiente paso es crear la base de datos. Necesitaremos 2 tablas:
- chat_data — datos que el bot solicitó al usuario
- chat_state — estado actual de todos los chats
Estas tablas se pueden crear con la siguiente 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
);
Si descargaste el proyecto del bot de , puedes usar el siguiente código en R para crear la base de datos.
# Скрипт создания базы данных
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'))
Filtros de mensajes
Ya tenemos un archivo de configuración y se creó la base de datos. Ahora necesitamos escribir funciones para leer y escribir datos en esta base.
Si descargaste el proyecto de , puedes encontrar las funciones en el archivo db_bot_function.R.
Código de funciones para trabajar con la base de datos
# ###########################################################
# 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]])
}Hemos creado 4 funciones simples:
get_state()— obtener el estado actual del chat de la base de datosset_state()— escribir el estado actual del chat en la base de datosget_chat_data()— obtener los datos enviados por el usuarioset_chat_data()— escribir los datos recibidos del usuario
Todas las funciones son bastante simples, o bien leen datos de la base usando el comando dbGetQuery(), o realizan UPSERT una operación (modificación de datos existentes o escritura de nuevos datos en la base), mediante la función dbExecute().
La sintaxis de la operación UPSERT es la siguiente:
INSERT INTO chat_data (chat_id, ${field})
VALUES(${chat_id}, '${value}')
ON CONFLICT(chat_id)
DO UPDATE SET ${field}='${value}';Es decir, en nuestras tablas, el campo chat_id tiene una restricción de unicidad y es la clave primaria de las tablas. Inicialmente, intentamos agregar información a la tabla y obtenemos un error si los datos del chat actual ya están presentes; en ese caso, simplemente actualizamos la información de este chat.
A partir de aquí, usaremos estas funciones en los métodos y filtros del bot.
Controladores
El siguiente paso en la construcción de nuestro bot será crear métodos. Si descargaste el proyecto de , todos los métodos se encuentran en el archivo bot_methods.R.
Código de métodos del 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')
}
}Hemos creado 5 métodos:
- start — Iniciar diálogo
- state — Obtener el estado actual del chat
- reset — Restablecer el estado actual del chat
- enter_name — El bot pregunta tu nombre
- enter_age — El bot pregunta tu edad
El método start pregunta tu nombre y cambia el estado del chat a wait_name, es decir, en modo de espera de tu entrada de nombre.
Luego, envías tu nombre y se procesa mediante el método enter_name, el bot te saluda, guarda el nombre recibido en la base, y cambia el chat a estado wait_age.
En esta etapa, el bot espera que ingreses tu edad. Envías tu edad, el bot verifica el mensaje; si enviaste algún texto en vez de un número, te dirá: Has ingresado datos incorrectos, por favor ingresa un número, y estará esperando que vuelvas a ingresar los datos. Si envías un número, el bot te informará que ha recibido tu edad, registrará los datos obtenidos y devolverá el estado del chat a su posición original, es decir, a start.
Llamando al método state en cualquier momento puedes solicitar el estado actual del chat, y mediante el método reset restablecer el chat a su estado original.
Código para iniciar el bot
En nuestro caso, esta es una de las partes más importantes en la construcción del bot. Es mediante los filtros de mensajes que el bot entenderá qué información está esperando de ti y cómo debe procesarla.
En el proyecto en los filtros están definidos en el archivo message_filters.R.
Código de los filtros de mensajes:
# ###########################################################
# 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 los filtros utilizamos la función escrita anteriormente get_state(), para solicitar el estado actual del chat. Esta función solo requiere un argumento, el id del chat.
Luego, el filtro wait_name procesa los mensajes cuando el chat está en estado wait_name, y por lo tanto, el filtro wait_age procesa los mensajes cuando el chat está en estado wait_age.
Para que el bot pueda solicitarte datos y esperar la entrada de información, necesitarás registrar el estado actual del diálogo. La mejor manera de hacerlo es usar alguna base de datos integrada, como SQLite.
El archivo con los controladores se llama handlers.R, y tiene el siguiente 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)Primero creamos controladores de comandos que te permitirán activar métodos para iniciar un diálogo, reiniciarlo y solicitar el estado actual.
Luego creamos 2 controladores de mensajes utilizando los filtros creados en el paso anterior, y les agregamos el filtro !MessageFilters$command, para que podamos usar comandos en cualquier estado del chat.
Es decir, la lógica será la siguiente. Llamamos al método del bot y el bot solicita secuencialmente cierta información, mientras que en cada paso espera la entrada de esta información y puede realizar su validación.
Ahora estamos listos para el lanzamiento, el código principal para iniciar el bot se encuentra en el archivo bot.R.
library(telegram.bot)
library(tidyverse)
library(RSQLite)
library(DBI)
library(configr)
# cambiamos al directorio del proyecto
setwd(Sys.getenv('TG_BOT_PATH'))
# leemos la configuración
cfg <- read.config('config.cfg')
# creamos una instancia del bot
updater <- Updater(cfg$bot_settings$bot_token)
# Carga de componentes del bot
source('db_bot_function.R') # funciones para trabajar con la base de datos
source('bot_methods.R') # métodos del bot
source('message_filters.R') # filtros de mensajes
source('handlers.R') # controladores de mensajes
# Agregamos controladores al despachador
updater <- updater +
start_h +
wait_age_h +
wait_name_h +
state_h +
reset_h
# Iniciamos el bot
updater$start_polling()Como resultado, hemos obtenido este bot:

En cualquier momento, mediante el comando /state podemos solicitar el estado actual del chat, y mediante el comando /reset restablecer el chat a su estado original y comenzar el diálogo de nuevo.
Conclusión
En este artículo hemos analizado cómo usar bases de datos dentro del bot y cómo construir diálogos lógicos secuenciales mediante la fijación del estado del chat.
En este caso, hemos revisado el ejemplo más primitivo, para que te sea más fácil entender la idea de construir tales bots; en la práctica, puedes crear diálogos mucho más complejos.
En el próximo artículo de esta serie aprenderemos a restringir a los usuarios del bot en cuanto al uso de varios de sus métodos.
Fuente: habr.com
