Att skriva en telegrambot i R (del 4): Bygg en konsekvent, logisk dialog med boten

Om du redan har läst föregående tre artiklar från den här serien, då vet du redan hur man skriver fullfjädrade telegramrobotar med ett tangentbord.

I den här artikeln kommer vi att lära oss hur man skriver en bot som upprätthåller en konsekvent dialog. De där. Boten kommer att ställa frågor till dig och vänta på att du anger information. Beroende på vilken data du anger kommer boten att utföra vissa åtgärder.

Också i den här artikeln kommer vi att lära oss hur man använder en databas under huven på boten, i vårt exempel kommer det att vara SQLite, men du kan använda vilken annan DBMS som helst. Jag skrev mer detaljerat om att interagera med databaser på R-språket i den här artikeln.

Att skriva en telegrambot i R (del 4): Bygg en konsekvent, logisk dialog med boten

Alla artiklar från serien "Att skriva en telegrambot i R"

  1. Vi skapar en bot och använder den för att skicka meddelanden i telegram
  2. Lägg till kommandostöd och meddelandefilter till boten
  3. Hur man lägger till tangentbordsstöd till en bot
  4. Bygga en konsekvent, logisk dialog med boten

Innehåll

Om du är intresserad av dataanalys kan du vara intresserad av min telegram и Youtube kanaler. Det mesta av innehållet är tillägnat R-språket.

  1. Inledning
  2. Bot byggprocess
  3. Botprojektets struktur
  4. Bot config
  5. Skapa en miljövariabel
  6. Skapa en databas
  7. Skrivfunktioner för att arbeta med databasen
  8. Botmetoder
  9. Meddelandefilter
  10. Handlare
  11. Bot lanseringskod
  12. Slutsats

Inledning

För att boten ska begära data från dig och vänta på att du ska ange någon information, måste du registrera det aktuella tillståndet för dialogen. Det bästa sättet att göra detta är att använda någon form av inbäddad databas, som SQLite.

De där. Logiken blir som följer. Vi anropar botmetoden, och boten begär sekventiellt viss information från oss, och vid varje steg väntar den på att denna information ska matas in och kan kontrollera den.

Vi kommer att skriva den enklaste möjliga boten, först kommer den att fråga efter ditt namn, sedan din ålder och sparar mottagna data i databasen. När du frågar efter ålder kommer den att kontrollera att de inmatade uppgifterna är ett nummer och inte text.

En sådan enkel dialog kommer bara att ha tre tillstånd:

  1. start är botens normala tillstånd, där den inte förväntar sig någon information från dig
  2. wait_name - tillstånd där boten väntar på att ett namn ska anges
  3. wait_age är det tillstånd där boten väntar på att din ålder ska anges, antalet hela år.

Bot byggprocess

Under artikeln kommer vi att bygga en bot steg för steg; hela processen kan schematiskt avbildas enligt följande:
Att skriva en telegrambot i R (del 4): Bygg en konsekvent, logisk dialog med boten

  1. Vi skapar en bot-konfiguration där vi kommer att lagra några inställningar. I vårt fall bot-token och sökvägen till databasfilen.
  2. Vi skapar en miljövariabel där sökvägen till projektet med boten kommer att lagras.
  3. Vi skapar själva databasen, och ett antal funktioner så att boten kan interagera med den.
  4. Vi skriver botmetoder, d.v.s. de funktioner den kommer att utföra.
  5. Lägger till meddelandefilter. Med hjälp av vilken boten kommer åt de nödvändiga metoderna, beroende på chattens nuvarande tillstånd.
  6. Vi lägger till hanterare som kopplar ihop kommandon och meddelanden med de nödvändiga botmetoderna.
  7. Låt oss starta boten.

Botprojektets struktur

För enkelhetens skull kommer vi att dela upp koden för vår bot och andra relaterade filer i följande struktur.

  • bot.R — huvudkoden för vår bot
  • db_bot_function.R — ett kodblock med funktioner för att arbeta med databasen
  • bot_methods.R — kod för botmetoder
  • meddelandefilter.R — meddelandefilter
  • hanterare.R - hanterare
  • config.cfg - bot config
  • create_db_data.sql — SQL-skript för att skapa en tabell med chattdata i databasen
  • create_db_state.sql — SQL-skript för att skapa en tabell över det aktuella chattläget i databasen
  • bot.db - botdatabas

Du kan se hela botprojektet, eller hämta från min repository på GitHub.

Bot config

Vi kommer att använda den vanliga som en konfiguration ini filen, följande formulär:

[bot_settings]
bot_token=ТОКЕН_ВАШЕГО_БОТА

[db_settings]
db_path=C:/ПУТЬ/К/ПАПКЕ/ПРОЕКТА/bot.db

I konfigurationen skriver vi bot-token och sökvägen till databasen, d.v.s. till filen bot.db; vi skapar själva filen i nästa steg.

För mer komplexa bots kan du skapa mer komplexa konfigurationer, dessutom är det inte nödvändigt att skriva en ini-konfiguration, du kan använda vilket annat format som helst inklusive JSON.

Skapa en miljövariabel

På varje PC kan mappen med botprojektet finnas i olika kataloger och på olika enheter, så i koden kommer sökvägen till projektmappen att ställas in via en miljövariabel TG_BOT_PATH.

Det finns flera sätt att skapa en miljövariabel, det enklaste är att skriva den i en fil .Omgivning.

Du kan skapa eller redigera den här filen med kommandot file.edit(path.expand(file.path("~", ".Renviron"))). Kör det och lägg till en rad i filen:

TG_BOT_PATH=C:/ПУТЬ/К/ВАШЕМУ/ПРОЕКТУ

Spara sedan filen .Omgivning och starta om RStudio.

Skapa en databas

Nästa steg är att skapa en databas. Vi behöver 2 bord:

  • chat_data — data som boten begärde från användaren
  • chat_state — aktuell status för alla chattar

Du kan skapa dessa tabeller med följande SQL-fråga:

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

Om du laddade ner botprojektet från GitHub, sedan för att skapa databasen kan du använda följande kod i 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'))

Skrivfunktioner för att arbeta med databasen

Vi har redan en konfigurationsfil redo och en databas skapad. Nu måste du skriva funktioner för att läsa och skriva data till denna databas.

Om du laddade ner projektet från GitHub, då kan du hitta funktionerna i filen db_bot_function.R.

Funktionskod för att arbeta med databasen

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

}

Vi skapade 4 enkla funktioner:

  • get_state() — hämta det aktuella chattläget från databasen
  • set_state() — skriv det aktuella chattläget till databasen
  • get_chat_data() — ta emot data som skickats av användaren
  • set_chat_data() — registrera data som tas emot från användaren

Alla funktioner är ganska enkla, de läser antingen data från databasen med kommandot dbGetQuery(), eller begå UPSERT operation (ändra befintliga data eller skriva nya data till databasen), med funktionen dbExecute().

Syntaxen för UPSERT-operationen är följande:

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

De där. i vårt tabellfält chat_id har en unik begränsning och är den primära nyckeln för tabeller. Inledningsvis försöker vi lägga till information i tabellen, och vi får ett felmeddelande om data för den aktuella chatten redan finns, i vilket fall vi helt enkelt uppdaterar informationen för denna chatt.

Därefter kommer vi att använda dessa funktioner i botens metoder och filter.

Botmetoder

Nästa steg i att bygga vår bot är att skapa metoder. Om du laddade ner projektet från GitHub, då finns alla metoder i filen bot_methods.R.

Bot metodkod

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

}

Vi skapade 5 metoder:

  • start — Starta en dialogruta
  • status — Hämta aktuell chattstatus
  • återställ — Återställ aktuell chattstatus
  • enter_name — Boten frågar efter ditt namn
  • enter_age — Boten frågar efter din ålder

metod start frågar efter ditt namn och ändrar chattstatus till vänta_namn, dvs. till standby för att ange ditt namn.

Därefter skickar du namnet och det bearbetas av metoden enter_name, boten hälsar dig, skriver det mottagna namnet i databasen och växlar chatten till status wait_age.

I det här skedet förväntar sig boten att du anger din ålder. Du skickar din ålder, boten kontrollerar meddelandet, om du skickade lite text istället för ett nummer, kommer det att säga: Ты ввёл некорректные данные, введи число, och väntar på att du anger dina data igen. Om du skickade ett nummer kommer boten att rapportera att den har accepterat din ålder, skriva mottagen data till databasen, rapportera all data som tagits emot från dig och återställa chattstatusen till sin ursprungliga position, d.v.s. V start.

Genom att anropa metoden state du kan begära aktuell chattstatus när som helst och med hjälp av reset återställa chatten till dess ursprungliga tillstånd.

Meddelandefilter

I vårt fall är detta en av de viktigaste delarna i att bygga en bot. Det är med hjälp av meddelandefilter som boten kommer att förstå vilken information den förväntar sig av dig och hur den ska behandlas.

I projektet på GitHub filter registreras i filen meddelandefilter.R.

Meddelandefilterkod:

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

I filter använder vi den tidigare skrivna funktionen get_state(), för att begära chattens aktuella status. Denna funktion kräver endast 1 argument, chatt-id.

Nästa filter vänta_namn behandlar meddelanden när chatten är i ett tillstånd wait_nameoch följaktligen filtret wait_age behandlar meddelanden när chatten är i ett tillstånd wait_age.

Handlare

Filen med hanterare kallas hanterare.R, och har följande kod:

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

Först skapar vi kommandohanterare som låter dig köra metoder för att starta en dialogruta, återställa den och fråga om det aktuella tillståndet.

Därefter skapar vi 2 meddelandehanterare med filtren som skapades i föregående steg och lägger till ett filter till dem !MessageFilters$command, så att vi kan använda kommandon i alla chattlägen.

Bot lanseringskod

Nu har vi allt klart för lansering, huvudkoden för att starta boten finns i filen 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()

Som ett resultat fick vi denna bot:
Att skriva en telegrambot i R (del 4): Bygg en konsekvent, logisk dialog med boten

När som helst med hjälp av kommandot /state vi kan fråga det aktuella chattläget och använda kommandot /reset återställ chatten till dess ursprungliga tillstånd och starta dialogen igen.

Slutsats

I den här artikeln kom vi på hur man använder en databas i en bot och hur man bygger logiska dialoger i följd genom att registrera chattstatusen.

I det här fallet tittade vi på det mest primitiva exemplet, så att det skulle vara lättare för dig att förstå idén med att bygga sådana bots; i praktiken kan du bygga mycket mer komplexa dialoger.

I nästa artikel i den här serien kommer vi att lära oss hur man begränsar botanvändares rättigheter att använda olika av dess metoder.

Källa: will.com

Lägg en kommentar