Om du redan har bekantat dig med de tidigare i denna serie, så kan du redan skriva fullt fungerande Telegram-bots med tangentbord.
I den här artikeln kommer vi att lära oss att skriva en bot som stödjer en sekventiell dialog. Det vill säga, boten kommer att ställa frågor till dig och vänta på att du ska ange viss information. Beroende på de uppgifter du anger, kommer boten att utföra vissa åtgärder.
Vi kommer också att lära oss att använda en databas under huven på boten, i vårt exempel kommer det att vara SQLite, men du kan använda vilket databashanteringssystem som helst. Mer detaljerat om samverkan med databaser på R-språket har jag skrivit i .

Alla artiklar i serien "Skriver en Telegram-bot på R"
Innehåll
Om du är intresserad av dataanalys kan du vara intresserad av mina och kanaler. Största delen av innehållet handlar om R-språket.
Introduktion
För att boten ska kunna begära information från dig och vänta på att du ska ange viss information behöver du registrera det aktuella tillståndet i dialogen. Det bästa sättet att göra detta på är att använda en inbyggd databas, till exempel SQLite.
Det vill säga, logiken kommer att vara följande. Vi anropar bottens metod, och boten ber sekventiellt om viss information, och väntar på att den informationen ska anges, och kan kontrollera den.
Vi kommer att skriva en så enkel bot som möjligt, först kommer den att fråga efter ditt namn, sedan efter din ålder, och de inhämtade uppgifterna kommer att sparas i databasen. Vid begäran om ålder kommer den att kontrollera att den angivna informationen är ett nummer och inte text.
Denna enkla dialog kommer att ha totalt tre tillstånd:
- start — bottens normala tillstånd, där den inte väntar på någon information från dig
- wait_name — tillståndet då boten väntar på att du ska ange namn
- wait_age — tillståndet då boten väntar på att du ska ange din ålder, det vill säga antalet hela år.
Proccessen att bygga boten
Under artikeln kommer vi steg för steg att bygga boten, hela processen kan schematiskt illustreras på följande sätt:

- Skapa botkonfigurationen, där vi kommer att lagra vissa inställningar. I vårt fall är det botens token och sökvägen till databasen.
- Skapa en miljövariabel där sökvägen till projektet med boten kommer att lagras.
- Vi skapar själva databasen och en rad funktioner så att botten kan interagera med den.
- Vi skriver metoder för botten, det vill säga funktioner som den kommer att utföra.
- Vi lägger till meddelandefilter med vilka botten kommer att anropa de nödvändiga metoderna beroende på det aktuella tillståndet i chatt.
- Vi lägger till hanterare som kommer att koppla kommandon och meddelanden till de nödvändiga metoderna för botten.
- Vi startar botten.
Strukturen för botprojektet
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 — kodblock med funktioner för att arbeta med databasen
- bot_methods.R — metodkoden för botten
- message_filters.R — meddelandefilter
- handlers.R — hanterare
- config.cfg — botkonfiguration
- create_db_data.sql — SQL-skript för att skapa tabellen med chattdata i databasen
- create_db_state.sql — SQL-skript för att skapa tabellen med det aktuella tillståndet i chatt i databasen
- bot.db — botens databas
Hela botprojektet kan ses, eller från min .
Botkonfiguration
Som konfiguration kommer vi att använda en vanlig , av följande typ:
[bot_settings]
bot_token=DIN_BOT_TOKEN
[db_settings]
db_path=C:/SÖKVÄG/TILL/PROJEKTMAPPE/bot.dbI konfigurationen skriver vi botens token och sökvägen till databasen, det vill säga filen bot.db, som vi kommer att skapa i nästa steg.
För mer avancerade botar kan du skapa mer komplexa konfigurationer, och det är inte nödvändigt att just skriva en ini-konfiguration, du kan använda något annat format 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 anges genom en miljövariabel TG_BOT_PATH.
Du kan skapa en miljövariabel på flera sätt, det enklaste är att skriva den i filen .Renviron.
Skapa eller redigera denna fil kan göras med kommandot file.edit(path.expand(file.path("~", ".Renviron"))). Uträtta det och lägg till en rad i filen:
TG_BOT_PATH=C:/SÖKVÄG/TILL/DITT/PROJEKTSpara sedan filen .Renviron och starta om RStudio.
Skapa databasen
Nästa steg är att skapa databasen. Vi kommer att behöva 2 tabeller:
- chat_data — data som botten har begärt från användaren
- chat_state — aktuellt tillstånd för alla chattar
Dessa tabeller kan skapas med hjälp av 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 har laddat ner botprojektet från , så för att skapa databasen kan du använda följande kod i R-språket.
# Скрипт создания базы данных
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'))
Skriva funktioner för att arbeta med databasen
Vi har redan en konfigurationsfil och en databas skapad. Nu måste vi skriva funktioner för att läsa och skriva data i denna databas.
Om du har laddat ner projektet från , så kan du hitta funktionerna i filen db_bot_function.R.
Kod för funktioner som arbetar 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 har skapat 4 enkla funktioner:
get_state()— hämta det aktuella tillståndet av chatten från databasenset_state()— skriv det aktuella tillståndet av chatten till databasenget_chat_data()— hämta data som skickats av användarenset_chat_data()— skriv data mottagen från användaren
Alla funktioner är ganska enkla, de läser antingen data från databasen med kommandot dbGetQuery(), eller utför UPSERT en operation (ändra befintliga data eller skriv nya data i databasen) med funktionen dbExecute().
Syntaxen för UPSERT-operationen ser ut på följande sätt:
INSERT INTO chat_data (chat_id, ${field})
VALUES(${chat_id}, '${value}')
ON CONFLICT(chat_id)
DO UPDATE SET ${field}='${value}';Det vill säga, i våra tabeller har fältet chat_id ett unikt begränsning och är primärnyckeln i tabellerna. Vi försöker först lägga till informationen i tabellen och får ett fel om data för den aktuella chatten redan finns, i så fall uppdaterar vi helt enkelt informationen för denna chatt.
Därefter kommer vi att använda dessa funktioner i botens metoder och filter.
Bottens metoder
Nästa steg i att bygga vår bot blir att skapa metoder. Om du har laddat ner projektet från , så finns alla metoder i filen bot_methods.R.
Kod för botens metoder
# ###########################################################
# 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 har skapat 5 metoder:
- start — Starta dialogen
- state — Hämta det aktuella tillståndet av chatten
- reset — Återställ det aktuella tillståndet av chatten
- enter_name — Botten frågar efter ditt namn
- enter_age — Botten frågar efter din ålder
Metod start frågar efter ditt namn och ändrar chattens tillstånd till wait_name, dvs. i vänteläge för att få ditt namn.
Därefter skickar du namnet och det behandlas av metoden enter_name, botten hälsar på dig, sparar det mottagna namnet i databasen och ändrar chatten till tillståndet wait_age.
Vid detta skede väntar botten på att du ska ange din ålder. Du skickar din ålder, botten kontrollerar meddelandet, om du skickade något text istället för ett nummer, säger den: Du angav ogiltiga data, ange ett nummer, och kommer att vänta på att du matar in informationen igen. Om du har skickat ett nummer kommer botten att meddela att den har registrerat din ålder, spara mottagen information i databasen, meddela all information som den har fått från dig och återställa chattens tillstånd till ursprungligt, det vill säga till start.
Genom att anropa metoden state kan du när som helst begära chattens aktuella tillstånd, och med metoden återställning återställa chatten till ursprungligt tillstånd.
Meddelandefilter
I vårt fall är detta en av de viktigaste delarna i att bygga en bot. Genom meddelandefilter kommer botten att förstå vilken information den väntar på från dig och hur den ska behandlas.
I projektet på är filtren angivna i filen message_filters.R.
Koden för meddelandefiltren:
# ###########################################################
# 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 filtren använder vi den tidigare skrivna funktionen get_state(), för att begära chattens aktuella tillstånd. Denna funktion kräver bara ett argument, chat-id.
Därefter behandlar filtret wait_name meddelandena när chatten är i tillstånd wait_name, och därmed filtrera wait_age meddelandena när chatten är i tillstånd wait_age.
Hanterare
Filen med hanterarna heter handlers.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 gör att du kan starta metoder för att börja dialogen, återställa den och begära det aktuella tillståndet.
Därefter skapar vi 2 meddelandehanterare med hjälp av de filter vi skapade i det föregående steget, och lägger till filtret !MessageFilters$command, så att vi i vilket chattillstånd som helst kan använda kommandon.
Koden för att starta boten
Nu är allt klart för att starta, huvudsaklig kod för att starta botten finns i filen bot.R.
library(telegram.bot)
library(tidyverse)
library(RSQLite)
library(DBI)
library(configr)
# går till projektmappen
setwd(Sys.getenv('TG_BOT_PATH'))
# läser in konfigurationen
cfg <- read.config('config.cfg')
# skapar en instans av botten
updater <- Updater(cfg$bot_settings$bot_token)
# Laddar botens komponenter
source('db_bot_function.R') # funktioner för databashantering
source('bot_methods.R') # botens metoder
source('message_filters.R') # meddelandefilter
source('handlers.R') # meddelandehanterare
# Lägger till hanterarna i övervakaren
updater <- updater +
start_h +
wait_age_h +
wait_name_h +
state_h +
reset_h
# Startar botten
updater$start_polling()Som resultat har vi fått en bot som ser ut så här:

När som helst kan vi begära chattens aktuella tillstånd med kommandot /state , och med kommandot /reset återställa chatten till ursprungligt tillstånd och börja dialogen på nytt.
Slutsats
I den här artikeln har vi gått igenom hur man använder databaser inom botten, och hur man bygger sekventiella logiska dialoger genom att hålla koll på chattens tillstånd.
I det här fallet har vi tagit en mycket grundläggande exempel för att göra det lättare för dig att förstå idén bakom att bygga sådana bots. I praktiken kan du konstruera mycket mer komplexa dialoger.
I nästa artikel i denna serie kommer vi att lära oss att begränsa användarnas rättigheter att använda olika metoder i boten.
Källa: habr.com
