Een telegram-bot schrijven in de taal R (deel 4): Het opbouwen van een coherent, logisch gesprek met de bot

Als je de vorige drie artikelen uit deze serie al hebt gelezen, dan weet je al hoe je volledige Telegram-bots met een toetsenbord kunt schrijven.

In dit artikel leren we samen een bot te maken die een doorlopend gesprek ondersteunt. Dat betekent dat de bot je vragen stelt en wacht op jouw invoer. Afhankelijk van de gegevens die je invoert, zal de bot bepaalde acties uitvoeren.

Ook leren we in dit artikel hoe we databases onder de motorkap van de bot kunnen gebruiken; in ons voorbeeld gebruiken we SQLite, maar je kunt elke andere DBMS gebruiken. Meer details over het werken met databases in R heb ik geschreven in dit artikel.

Een telegram-bot schrijven in de taal R (deel 4): Het opbouwen van een coherent, logisch gesprek met de bot

Alle artikelen uit de serie "We schrijven een telegram bot in R"

  1. We maken een bot en verzenden met behulp daarvan berichten naar telegram
  2. We voegen ondersteuning voor commando's en berichtfilters aan de bot toe
  3. Hoe voeg je ondersteuning voor toetsenborden toe aan de bot
  4. Het opbouwen van een logisch, sequentieel gesprek met de bot

Inhoud

Als u geïnteresseerd bent in data-analyse, is het mogelijk dat mijn telegram en youtube kanalen interessant voor u zijn. Het grootste deel van de inhoud is gewijd aan de taal R.

  1. Inleiding
  2. Het proces van het bouwen van de bot
  3. De structuur van het botproject
  4. Botconfiguratie
  5. We maken een omgevingsvariabele aan
  6. We maken een database aan
  7. We schrijven functies voor het werken met de database
  8. Botmethoden
  9. Berichtfilters
  10. Handlers
  11. Code om de bot te starten
  12. Conclusie

Inleiding

Om de bot toestemming te geven om gegevens van je op te vragen en te wachten op invoer, moet je de huidige toestand van het gesprek vastleggen. De beste manier om dit te doen, is met een ingebouwde database, zoals SQLite.

Dat wil zeggen, de logica zal als volgt zijn. We roepen de methode van de bot aan en de bot vraagt ons stapsgewijs om bepaalde informatie, waarbij hij bij elke stap wacht op deze invoer en deze kan verifiëren.

We gaan een zo eenvoudig mogelijke bot schrijven. Eerst vraagt hij je naam, daarna je leeftijd, en de verkregen gegevens worden opgeslagen in de database. Bij het vragen naar de leeftijd controleert hij dat de invoer een getal is, en geen tekst.

Zo'n eenvoudig gesprek heeft drie toestanden:

  1. start — de normale toestand van de bot, waarin hij geen informatie van je verwacht
  2. wait_name — de toestand waarin de bot wacht op het invoeren van de naam
  3. wait_age — de toestand waarin de bot wacht op het invoeren van je leeftijd, het aantal volledige jaren.

Het proces van het bouwen van de bot

In de loop van dit artikel bouwen we samen de bot stap voor stap, het hele proces kan schematisch als volgt worden weergegeven:
Een telegram-bot schrijven in de taal R (deel 4): Het opbouwen van een coherent, logisch gesprek met de bot

  1. We maken de configuratie van de bot aan, waarin we enkele instellingen opslaan. In ons geval is dat de token van de bot en het pad naar het databasebestand.
  2. We maken een omgevingsvariabele aan waar het pad naar het project met de bot wordt opgeslagen.
  3. We create the database itself and a number of functions so that the bot can interact with it.
  4. We write the bot's methods, i.e., the functions it will execute.
  5. We add message filters through which the bot will call the necessary methods depending on the current state of the chat.
  6. We add handlers that will link commands and messages with the bot's necessary methods.
  7. We launch the bot.

De structuur van het botproject

For convenience, we will divide the code of our bot and other related files into the following structure.

  • bot.R — the main code of our bot
  • db_bot_function.R — block of code with functions for working with the database
  • bot_methods.R — code for the bot's methods
  • message_filters.R — message filters
  • handlers.R — handlers
  • config.cfg — bot configuration
  • create_db_data.sql — SQL script for creating a table with chat data in the database
  • create_db_state.sql — SQL script for creating a table for the current state of the chat in the database
  • bot.db — the bot's database

You can view the entire bot project, or downloaden from my de repository op GitHub.

Botconfiguratie

As a configuration, we will use a standard ini file, of the following format:

[bot_settings]
bot_token=YOUR_BOT_TOKEN

[db_settings]
db_path=C:/PATH/TO/PROJECT/FOLDER/bot.db

In the configuration, we write the bot's token and the path to the database, i.e., to the bot.db file, which we will create in the next step.

For more complex bots, you can create more complex configurations, and it is not necessary to write an ini configuration; you can use any other format including JSON.

We maken een omgevingsvariabele aan

On each PC, the bot project folder may be located in different directories and on different drives, so in the code, the path to the project folder will be defined through the environment variable TG_BOT_PATH.

You can create an environment variable in several ways, the simplest being to write it in the file .Renviron.

You can create or edit this file using the command file.edit(path.expand(file.path("~", ".Renviron"))). Execute it and add one line to the file:

TG_BOT_PATH=C:/PATH/TO/YOUR/PROJECT

Next, save the file .Renviron and restart RStudio.

We maken een database aan

The next step is to create the database. We will need 2 tables:

  • chat_data — data that the bot requested from the user
  • chat_state — the current state of all chats

You can create these tables using the following SQL query:

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

If you downloaded the bot project from GitHub, dan voor het aanmaken van de database kunt u de volgende code in de R-taal gebruiken.

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

We schrijven functies voor het werken met de database

We hebben al een configuratiebestand en is er een database aangemaakt. Nu moeten we functies schrijven voor het lezen en schrijven van gegevens naar deze database.

Als u het project hebt gedownload van GitHub, dan kunt u de functies vinden in het bestand db_bot_function.R.

De code van de functies voor het werken met de database

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

}

We hebben 4 eenvoudige functies gemaakt:

  • get_state() — het huidige chatstatus uit de database ophalen
  • set_state() — het huidige chatstatus in de database opslaan
  • get_chat_data() — de gegevens ophalen die door de gebruiker zijn verzonden
  • set_chat_data() — de gegevens opslaan die van de gebruiker zijn ontvangen

Alle functies zijn vrij eenvoudig, ze lezen ofwel gegevens uit de database met behulp van de opdracht dbGetQuery(), of uitvoeren UPSERT een bewerking (wijziging van bestaande gegevens of opslaan van nieuwe gegevens in de database) met behulp van de functie dbExecute().

De syntaxis van de UPSERT-bewerking ziet er als volgt uit:

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

Dat wil zeggen, in onze tabellen heeft het veld chat_id een beperking op uniciteit en is het de primaire sleutel van de tabellen. Eerst proberen we informatie aan de tabel toe te voegen, en we krijgen een fout als de gegevens van de huidige chat al aanwezig zijn, in dat geval werken we gewoon de informatie over deze chat bij.

Deze functies zullen we vervolgens gebruiken in de methoden en filters van de bot.

Botmethoden

De volgende stap in het bouwen van onze bot is het maken van methoden. Als u het project heeft gedownload van GitHub, dan bevinden alle methoden zich in het bestand bot_methods.R.

De code van de botmethoden

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

}

We hebben 5 methoden gemaakt:

  • start — Start het gesprek
  • state — Haal de huidige chatstatus op
  • reset — Reset de huidige chatstatus
  • enter_name — De bot vraagt om uw naam
  • enter_age — De bot vraagt om uw leeftijd

Methode start vraagt om uw naam en zet de chatstatus naar wait_name, dat wil zeggen, in de wachtmodus voor het invoeren van uw naam.

Daarna stuurt u uw naam en deze wordt verwerkt door de methode enter_name, de bot begroet u, slaat de ontvangen naam op in de database en zet de chatstatus naar wait_age.

In dit stadium wacht de bot op uw invoer van uw leeftijd. U stuurt uw leeftijd, de bot controleert het bericht, als u in plaats van een getal een tekst heeft verzonden, zal hij zeggen: U heeft onjuiste gegevens ingevoerd, voer een getal in, en zal op uw herhaalde invoer van gegevens wachten. Als u een getal hebt verzonden, zal de bot bevestigen dat hij uw leeftijd heeft ontvangen, de verkregen gegevens in de database opslaan, alle ontvangen informatie aan u teruggeven en de chatstatus terugbrengen naar de oorspronkelijke staat, d.w.z. naar start.

Door de methode aan te roepen state kunt u op elk moment de huidige status van de chat opvragen, en met de methode – functie van de STM datasheets) de chat terugbrengen naar de oorspronkelijke staat.

Berichtfilters

In ons geval is dit een van de belangrijkste onderdelen van de bot. Juist met behulp van berichtenfilters begrijpt de bot welke informatie hij van u verwacht en hoe deze moet worden behandeld.

In het project op GitHub zijn de filters vastgelegd in het bestand message_filters.R.

De code van de berichtenfilters:

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

In de filters gebruiken we de eerder geschreven functie get_state(), om de huidige status van de chat op te vragen. Deze functie vereist slechts 1 parameter, het id van de chat.

Daarna verwerkt de filter wait_name berichten wanneer de chat zich in de staat bevindt wait_name, en dienovereenkomstig filter wait_age berichten wanneer de chat zich in de staat bevindt wait_age.

Handlers

Het bestand met de handlers heet handlers.R, en heeft de volgende code:

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

Eerst maken we de command handlers aan die u in staat stellen methoden te starten voor het begin van de dialoog, het resetten ervan en het opvragen van de huidige status.

Daarna maken we 2 berichthandlers aan met behulp van de eerder gemaakte filters en voegen we een filter toe !MessageFilters$command, zodat we in elke staat van de chat commando's kunnen gebruiken.

Code om de bot te starten

Nu zijn we er klaar voor om te starten, de hoofdcode om de bot te draaien bevindt zich in het bestand bot.R.

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

# ga naar de projectmap
setwd(Sys.getenv('TG_BOT_PATH'))

# lees de configuratie
cfg <- read.config('config.cfg')

# maak een bot-exemplaar aan
updater <- Updater(cfg$bot_settings$bot_token)

# Laad de componenten van de bot
source('db_bot_function.R') # functies voor werken met de database
source('bot_methods.R')     # botmethoden
source('message_filters.R') # berichtenfilters
source('handlers.R') # berichthandlers

# Voeg handlers toe aan de dispatcher
updater <- updater +
  start_h +
  wait_age_h +
  wait_name_h +
  state_h +
  reset_h

# Start de bot
updater$start_polling()

Als resultaat hebben we deze bot:
Een telegram-bot schrijven in de taal R (deel 4): Het opbouwen van een coherent, logisch gesprek met de bot

Op elk moment kunnen we met behulp van het commando /state de huidige status van de chat opvragen, en met behulp van het commando /reset de chat terugbrengen naar de oorspronkelijke staat en de dialoog opnieuw beginnen.

Conclusie

In dit artikel hebben we gekeken naar hoe we databases binnen de bot kunnen gebruiken, en hoe we sequentiële logische dialogen kunnen opbouwen door de status van de chat te fixeren.

In dit geval hebben we het simpelste voorbeeld besproken, zodat u het idee van het bouwen van dergelijke bots beter begrijpt; in de praktijk kunt u veel complexere dialogen opzetten.

In het volgende artikel van deze serie leren we hoe we de gebruikers van de bot rechten kunnen beperken voor het gebruik van verschillende methoden.

Bron: habr.com

Koop betrouwbare webhosting met bescherming tegen DDoS, VPS VDS servers 🔥 Koop betrouwbare webhosting met bescherming tegen DDoS, VPS VDS servers | ProHoster