Creando un bot de telegram en el lenguaje R (parte 4): Estableciendo un diálogo secuencial y lógico con el bot

Si ya has revisado los artículos anteriores de esta serie, ya sabes cómo escribir bots de telegram con teclado. 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" este artículo.

Creando un bot de telegram en el lenguaje R (parte 4): Estableciendo un diálogo secuencial y lógico con el bot

Creamos un bot y enviamos mensajes a telegram a través de él

  1. Añadimos soporte de comandos y filtros de mensajes al bot
  2. Cómo agregar soporte de teclado al bot
  3. Construcción de un diálogo secuencial y lógico con el bot
  4. Si te interesa el análisis de datos, quizás te interesen mis

Contenido

canales de youtube. La mayor parte del contenido está dedicado al lenguaje R. telegram y Proceso de construcción del bot Estructura del proyecto del bot

  1. Introducción
  2. Configuración del bot
  3. Creamos una variable de entorno
  4. Creamos una base de datos
  5. Escribimos funciones para trabajar con la base de datos
  6. Métodos del bot
  7. Filtros de mensajes
  8. Controladores
  9. Código para iniciar el bot
  10. 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.
  11. 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.
  12. Conclusión

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

  1. wait_age — estado en el que el bot espera que introduzcas tu edad, número de años completos.
  2. wait_name — estado en el que el bot está esperando la introducción del nombre
  3. 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:
Creando un bot de telegram en el lenguaje R (parte 4): Estableciendo un diálogo secuencial y lógico con el bot

  1. 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.
  2. Creamos una variable de entorno que almacenará la ruta del proyecto con el bot.
  3. Creamos la base de datos en sí y una serie de funciones para que el bot pueda interactuar con ella.
  4. Escribimos los métodos del bot, es decir, las funciones que realizará.
  5. 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.
  6. Añadimos controladores que conectarán comandos y mensajes con los métodos correspondientes del bot.
  7. 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 descargar desde mi repositorio en GitHub.

Creamos una base de datos

Como configuración, utilizaremos un archivo ini, de la siguiente forma:

[bot_settings]
bot_token=SU_TOKEN_DEL_BOT

[db_settings]
db_path=C:/RUTA/AL/CARPETA/DEL/PROYECTO/bot.db

En 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/PROYECTO

Luego 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 GitHub, 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 GitHub, 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 datos
  • set_state() — escribir el estado actual del chat en la base de datos
  • get_chat_data() — obtener los datos enviados por el usuario
  • set_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 GitHub, 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 GitHub 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:
Creando un bot de telegram en el lenguaje R (parte 4): Estableciendo un diálogo secuencial y lógico con el 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

Compra un hosting fiable para sitios web con protección contra DDoS, servidores VPS VDS 🔥 Compra un hosting fiable para sitios web con protección contra DDoS, servidores VPS VDS | ProHoster