ProHoster > blog > administratie > Een telegrambot schrijven in R (deel 4): Een consistente, logische dialoog opbouwen met de bot
Een telegrambot schrijven in R (deel 4): Een consistente, logische dialoog opbouwen met de bot
Mocht je het vorige al gelezen hebben drie artikelen uit deze serie, dan weet je al hoe je volwaardige telegrambots met een toetsenbord schrijft.
In dit artikel zullen we leren hoe we een bot kunnen schrijven die een consistente dialoog onderhoudt. Die. De bot zal u vragen stellen en wachten tot u wat informatie invoert. Afhankelijk van de gegevens die u invoert, zal de bot enkele acties uitvoeren.
Ook in dit artikel zullen we leren hoe we een database onder de motorkap van de bot kunnen gebruiken, in ons voorbeeld zal het SQLite zijn, maar je kunt elk ander DBMS gebruiken. Ik heb meer gedetailleerd geschreven over de interactie met databases in de R-taal in dit artikel.
Alle artikelen uit de serie “Een telegrambot schrijven in R”
Als u geïnteresseerd bent in data-analyse, bent u wellicht geïnteresseerd in mijn telegram и youtube kanalen. Het grootste deel van de inhoud is gewijd aan de R-taal.
Om ervoor te zorgen dat de bot gegevens van u opvraagt en wacht tot u informatie invoert, moet u de huidige status van de dialoog vastleggen. De beste manier om dit te doen is door een soort ingebedde database te gebruiken, zoals SQLite.
Die. De logica zal als volgt zijn. We noemen de botmethode en de bot vraagt achtereenvolgens wat informatie van ons, en bij elke stap wacht hij tot deze informatie is ingevoerd en kan hij deze controleren.
We zullen de eenvoudigst mogelijke bot schrijven, eerst zal deze om uw naam vragen, vervolgens uw leeftijd, en de ontvangen gegevens in de database opslaan. Wanneer naar de leeftijd wordt gevraagd, wordt gecontroleerd of de ingevoerde gegevens een getal zijn en geen tekst.
Zo’n eenvoudige dialoog zal slechts drie toestanden kennen:
start is de normale toestand van de bot, waarin deze geen informatie van u verwacht
wait_name - status waarin de bot wacht tot een naam wordt ingevoerd
wait_age is de staat waarin de bot wacht tot uw leeftijd is ingevoerd, het aantal volledige jaren.
Bot-bouwproces
Tijdens het artikel gaan we stap voor stap een bot bouwen; het hele proces kan als volgt schematisch worden weergegeven:
We maken een botconfiguratie waarin we enkele instellingen opslaan. In ons geval het bottoken en het pad naar het databasebestand.
We maken een omgevingsvariabele aan waarin het pad naar het project met de bot wordt opgeslagen.
We creëren de database zelf en een aantal functies zodat de bot ermee kan communiceren.
We schrijven botmethoden, d.w.z. de functies die het zal uitvoeren.
Berichtfilters toevoegen. Met behulp waarvan de bot toegang krijgt tot de noodzakelijke methoden, afhankelijk van de huidige status van de chat.
We voegen handlers toe die opdrachten en berichten verbinden met de benodigde botmethoden.
Laten we de bot lanceren.
Bot-projectstructuur
Voor het gemak zullen we de code van onze bot en andere gerelateerde bestanden in de volgende structuur verdelen.
bot.R — de hoofdcode van onze bot
db_bot_function.R — een codeblok met functies voor het werken met een database
bot_methods.R - code van botmethoden
message_filters.R — berichtfilters
begeleiders.R - behandelaars
config.cfg - bot-configuratie
create_db_data.sql — SQL-script voor het maken van een tabel met chatgegevens in de database
create_db_state.sql — SQL-script voor het maken van een tabel met de huidige chatstatus in de database
In de configuratie schrijven we het bottoken en het pad naar de database, d.w.z. naar het bot.db-bestand; we zullen het bestand zelf in de volgende stap maken.
Voor complexere bots kun je complexere configuraties maken. Bovendien is het niet nodig om een ini-configuratie te schrijven, je kunt elk ander formaat gebruiken, inclusief JSON.
Maak een omgevingsvariabele
Op elke pc kan de map met het botproject zich in verschillende mappen en op verschillende schijven bevinden, dus in de code wordt het pad naar de projectmap ingesteld via een omgevingsvariabele TG_BOT_PATH.
Er zijn verschillende manieren om een omgevingsvariabele te maken; de eenvoudigste is om deze in een bestand te schrijven .Reviron.
U kunt dit bestand maken of bewerken met de opdracht file.edit(path.expand(file.path("~", ".Renviron"))). Voer het uit en voeg één regel toe aan het bestand:
TG_BOT_PATH=C:/ПУТЬ/К/ВАШЕМУ/ПРОЕКТУ
Sla het bestand vervolgens op .Reviron en start RStudio opnieuw.
Een databank maken
De volgende stap is het maken van een database. We hebben 2 tabellen nodig:
chat_data — gegevens die de bot bij de gebruiker heeft opgevraagd
chat_state — huidige status van alle chats
U kunt deze tabellen maken met behulp van de volgende 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
);
Als u het botproject hebt gedownload van GitHub, en om de database te maken, kunt u de volgende code in R 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'))
Functies schrijven om met de database te werken
We hebben al een configuratiebestand klaar en een database gemaakt. Nu moet u functies schrijven om gegevens naar deze database te lezen en te schrijven.
Als u het project hebt gedownload van GitHub, dan kun je de functies in het bestand vinden db_bot_function.R.
Functiecode 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() — haal de huidige chatstatus op uit de database
set_state() — schrijf de huidige chatstatus naar de database
get_chat_data() — gegevens ontvangen die door de gebruiker zijn verzonden
set_chat_data() — gegevens registreren die zijn ontvangen van de gebruiker
Alle functies zijn vrij eenvoudig: ze lezen gegevens uit de database met behulp van de opdracht dbGetQuery(), of plegen UPSERT bewerking (bestaande gegevens wijzigen of nieuwe gegevens naar de database schrijven), met behulp van de functie dbExecute().
De syntaxis voor de UPSERT-bewerking is als volgt:
INSERT INTO chat_data (chat_id, ${field})
VALUES(${chat_id}, '${value}')
ON CONFLICT(chat_id)
DO UPDATE SET ${field}='${value}';
Die. in ons tafelsveld chat_id heeft een uniciteitsbeperking en is de primaire sleutel van tabellen. In eerste instantie proberen we informatie aan de tabel toe te voegen en krijgen we een foutmelding als er al gegevens voor de huidige chat aanwezig zijn. In dat geval werken we eenvoudigweg de informatie voor deze chat bij.
Vervolgens zullen we deze functies gebruiken in de methoden en filters van de bot.
Bot-methoden
De volgende stap bij het bouwen van onze bot is het creëren van methoden. Als u het project hebt gedownload van GitHub, dan staan alle methoden in het bestand bot_methods.R.
Bot-methodecode
# ###########################################################
# 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 een dialoog
status — Haal de huidige chatstatus op
reset — Reset de huidige chatstatus
enter_name — De bot vraagt om uw naam
enter_age — De bot vraagt naar je leeftijd
werkwijze start vraagt om je naam en verandert de chatstatus in wacht_naam, d.w.z. stand-by staan voor het invoeren van uw naam.
Vervolgens verzend je de naam en deze wordt door de methode verwerkt enter_name, begroet de bot je, schrijft de ontvangen naam in de database en schakelt de chat over naar de status wacht_leeftijd.
In dit stadium verwacht de bot dat u uw leeftijd invoert. U verzendt uw leeftijd, de bot controleert het bericht, als u een sms verzendt in plaats van een nummer, staat er: Ты ввёл некорректные данные, введи числоen wacht tot u uw gegevens opnieuw invoert. Als u een nummer heeft verzonden, meldt de bot dat hij uw leeftijd heeft geaccepteerd, schrijft hij de ontvangen gegevens naar de database, rapporteert hij alle van u ontvangen gegevens en zet hij de chatstatus terug naar zijn oorspronkelijke positie, d.w.z. V start.
Door de methode aan te roepen state u kunt op elk moment de huidige chatstatus opvragen via de reset breng de chat terug naar de oorspronkelijke staat.
Berichtfilters
In ons geval is dit een van de belangrijkste onderdelen bij het bouwen van een bot. Met behulp van berichtenfilters begrijpt de bot welke informatie hij van u verwacht en hoe deze moet worden verwerkt.
In het project op GitHub filters worden in het bestand geregistreerd message_filters.R.
Berichtfiltercode:
# ###########################################################
# 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 filters gebruiken we de eerder geschreven functie get_state(), om de huidige status van de chat op te vragen. Deze functie vereist slechts 1 argument, chat-ID.
Volgende filter wacht_naam verwerkt berichten wanneer de chat zich in een status bevindt wait_name, en dienovereenkomstig het filter wacht_leeftijd verwerkt berichten wanneer de chat zich in een status bevindt wait_age.
Behandelaars
Het bestand met handlers wordt aangeroepen begeleiders.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 opdrachthandlers waarmee u methoden kunt uitvoeren om een dialoogvenster te starten, het opnieuw in te stellen en de huidige status op te vragen.
Vervolgens maken we twee berichthandlers met behulp van de filters die in de vorige stap zijn gemaakt, en voegen we er een filter aan toe !MessageFilters$command, zodat we opdrachten in elke chatstatus kunnen gebruiken.
Bot-lanceringscode
Nu hebben we alles klaar om te starten, de hoofdcode voor het starten van de bot staat in het bestand 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()
Als resultaat kregen we deze bot:
Gebruik op elk moment de opdracht /state we kunnen de huidige chatstatus opvragen en de opdracht gebruiken /reset breng de chat terug naar de oorspronkelijke staat en start de dialoog opnieuw.
Conclusie
In dit artikel hebben we ontdekt hoe we een database in een bot kunnen gebruiken en hoe we opeenvolgende logische dialogen kunnen opbouwen door de chatstatus vast te leggen.
In dit geval hebben we naar het meest primitieve voorbeeld gekeken, zodat het voor u gemakkelijker zou zijn om het idee van het bouwen van dergelijke bots te begrijpen; in de praktijk kun je veel complexere dialogen bouwen.
In het volgende artikel in deze serie zullen we leren hoe we de rechten van botgebruikers kunnen beperken om verschillende methoden te gebruiken.