ProHoster > Blog > administracja > Pisanie bota telegramowego w R (część 4): Budowanie spójnego, logicznego dialogu z botem
Pisanie bota telegramowego w R (część 4): Budowanie spójnego, logicznego dialogu z botem
Jeśli już przeczytałeś poprzednie trzy artykuły z tej serii, to już wiesz, jak pisać pełnoprawne boty telegramowe za pomocą klawiatury.
W tym artykule dowiemy się jak napisać bota, który będzie prowadził spójny dialog. Te. Bot będzie zadawał Ci pytania i czekał, aż wpiszesz jakieś informacje. W zależności od wprowadzonych danych bot wykona pewne czynności.
Również w tym artykule dowiemy się, jak korzystać z bazy danych pod maską bota, w naszym przykładzie będzie to SQLite, ale możesz użyć dowolnego innego DBMS. O interakcji z bazami danych w języku R pisałem szerzej w ten artykuł.
Wszystkie artykuły z serii „Pisanie bota telegramowego w R”
Aby bot zażądał od Ciebie danych i czekał, aż wprowadzisz jakiekolwiek informacje, będziesz musiał zapisać aktualny stan dialogu. Najlepszym sposobem na osiągnięcie tego jest użycie wbudowanej bazy danych, takiej jak SQLite.
Te. Logika będzie następująca. Wywołujemy metodę bota, a bot sekwencyjnie żąda od nas pewnych informacji, a na każdym kroku czeka na wprowadzenie tych informacji i może je sprawdzić.
Napiszemy możliwie najprostszego bota, najpierw zapyta Cię o imię, potem o wiek, a otrzymane dane zapisze do bazy danych. Pytając o wiek, sprawdzi, czy wprowadzone dane są liczbą, a nie tekstem.
Taki prosty dialog będzie miał tylko trzy stany:
start to normalny stan bota, w którym nie oczekuje on od Ciebie żadnych informacji
wait_name - stan w którym bot czeka na podanie nazwy
wait_age to stan, w jakim bot czeka na podanie Twojego wieku, czyli liczby pełnych lat.
Proces tworzenia bota
W artykule krok po kroku zbudujemy bota, a cały proces można schematycznie przedstawić w następujący sposób:
Tworzymy konfigurację bota, w której będziemy przechowywać niektóre ustawienia. W naszym przypadku token bota i ścieżka do pliku bazy danych.
Tworzymy zmienną środowiskową, w której będzie zapisana ścieżka do projektu z botem.
Tworzymy samą bazę danych, oraz szereg funkcji, aby bot mógł z nią współdziałać.
Piszemy metody botów, tj. funkcje, jakie będzie pełnił.
Dodawanie filtrów wiadomości. Za pomocą którego bot uzyska dostęp do niezbędnych metod, w zależności od aktualnego stanu czatu.
Dodajemy procedury obsługi, które będą łączyć polecenia i wiadomości z niezbędnymi metodami bota.
Uruchommy bota.
Struktura projektu bota
Dla wygody kod naszego bota i innych powiązanych plików podzielimy na następującą strukturę.
bot.R — główny kod naszego bota
db_bot_funkcja.R — blok kodu z funkcjami do pracy z bazą danych
bot_methods.R — kod metod bota
filtr_wiadomości.R — filtry wiadomości
handlerzy.R - handlerzy
plik konfiguracyjny.cfg - konfiguracja bota
utwórz_db_data.sql — Skrypt SQL do tworzenia tabeli z danymi czatu w bazie danych
utwórz_stan_bazy_danych.sql — Skrypt SQL do tworzenia tabeli aktualnego stanu czatu w bazie danych
W konfiguracji wpisujemy token bota i ścieżkę do bazy danych, czyli: do pliku bot.db, sam plik utworzymy w kolejnym kroku.
W przypadku bardziej skomplikowanych botów można stworzyć bardziej złożone konfiguracje, poza tym nie jest konieczne pisanie konfiguracji ini, można użyć dowolnego innego formatu łącznie z JSON.
Utwórz zmienną środowiskową
Na każdym komputerze folder z projektem bota może znajdować się w różnych katalogach i na różnych dyskach, dlatego w kodzie ścieżka do folderu projektu zostanie ustawiona za pomocą zmiennej środowiskowej TG_BOT_PATH.
Istnieje kilka sposobów utworzenia zmiennej środowiskowej, najprostszym jest zapisanie jej w pliku .Renviron.
Możesz utworzyć lub edytować ten plik za pomocą polecenia file.edit(path.expand(file.path("~", ".Renviron"))). Wykonaj go i dodaj jedną linię do pliku:
TG_BOT_PATH=C:/ПУТЬ/К/ВАШЕМУ/ПРОЕКТУ
Następnie zapisz plik .Renviron i uruchom ponownie RStudio.
Tworzenie bazy danych
Następnym krokiem jest utworzenie bazy danych. Będziemy potrzebować 2 tabel:
chat_data — dane, o które bot poprosił użytkownika
chat_state — aktualny stan wszystkich czatów
Możesz utworzyć te tabele za pomocą następującego zapytania 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
);
Jeśli pobrałeś projekt bota z GitHub, następnie aby utworzyć bazę danych, możesz użyć następującego kodu w 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'))
Pisanie funkcji do pracy z bazą danych
Mamy już gotowy plik konfiguracyjny i utworzoną bazę danych. Teraz musisz napisać funkcje do odczytu i zapisu danych do tej bazy danych.
Jeśli pobrałeś projekt z GitHub, możesz znaleźć funkcje w pliku db_bot_funkcja.R.
Kod funkcji do pracy z bazą danych
# ###########################################################
# 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]])
}
Stworzyliśmy 4 proste funkcje:
get_state() — pobierz aktualny stan czatu z bazy danych
set_state() — zapisz aktualny stan czatu w bazie danych
get_chat_data() — odbierać dane przesłane przez użytkownika
set_chat_data() — zapisują dane otrzymane od użytkownika
Wszystkie funkcje są dość proste, albo odczytują dane z bazy za pomocą polecenia dbGetQuery()lub popełnij UPSERT operację (zmianę istniejących danych lub zapis nowych danych do bazy), korzystając z funkcji dbExecute().
Składnia operacji UPSERT jest następująca:
INSERT INTO chat_data (chat_id, ${field})
VALUES(${chat_id}, '${value}')
ON CONFLICT(chat_id)
DO UPDATE SET ${field}='${value}';
Te. w naszym polu tabel identyfikator_czatu ma ograniczenie niepowtarzalności i jest kluczem podstawowym tabel. Początkowo próbujemy dodać informacje do tabeli i pojawia się błąd, jeśli dane dla bieżącego czatu są już obecne, w takim przypadku po prostu aktualizujemy informacje dla tego czatu.
Następnie wykorzystamy te funkcje w metodach i filtrach bota.
Metody botów
Kolejnym krokiem w budowie naszego bota jest stworzenie metod. Jeśli pobrałeś projekt z GitHub, wówczas wszystkie metody znajdują się w pliku bot_methods.R.
Kod metody bota
# ###########################################################
# 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')
}
}
Stworzyliśmy 5 metod:
start — Rozpocznij okno dialogowe
stan — Uzyskaj bieżący stan czatu
reset — Zresetuj bieżący stan czatu
enter_name — Bot pyta o Twoje imię
enter_age — Bot pyta o Twój wiek
metoda start pyta o Twoje imię i zmienia stan czatu na nazwa_oczekiwania, tj. w trybie gotowości do wpisania swojego imienia i nazwiska.
Następnie wysyłasz nazwę i jest ona przetwarzana przez metodę enter_name, bot wita Cię, zapisuje otrzymaną nazwę do bazy danych i przełącza czat do stanu oczekiwanie_wiek.
Na tym etapie bot oczekuje od Ciebie podania swojego wieku. Wysyłasz swój wiek, bot sprawdza wiadomość, jeśli zamiast numeru wysłałeś SMS-a, powie: Ты ввёл некорректные данные, введи числоi będzie czekać na ponowne wprowadzenie danych. Jeśli wysłałeś numer, bot zgłosi, że przyjął Twój wiek, zapisze otrzymane dane do bazy, zgłosi wszystkie otrzymane od Ciebie dane i przywróci stan czatu do pierwotnej pozycji, tj. V start.
Wywołując metodę state w każdej chwili możesz zapytać o aktualny status czatu, a korzystając z reset przywróć czat do pierwotnego stanu.
Filtry wiadomości
W naszym przypadku jest to jedna z najważniejszych części budowy bota. To za pomocą filtrów wiadomości bot zrozumie, jakich informacji od Ciebie oczekuje i jak należy je przetwarzać.
W projekcie pt GitHub filtry są zarejestrowane w pliku filtr_wiadomości.R.
Kod filtra wiadomości:
# ###########################################################
# 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"
}
)
W filtrach wykorzystujemy wcześniej napisaną funkcję get_state(), aby zapytać o aktualny stan czatu. Ta funkcja wymaga tylko 1 argumentu, identyfikatora czatu.
Następny filtr nazwa_oczekiwania przetwarza wiadomości, gdy czat jest w stanie wait_namei odpowiednio filtr oczekiwanie_wiek przetwarza wiadomości, gdy czat jest w stanie wait_age.
Opiekunowie
Nazywa się plik z procedurami obsługi handlerzy.Ri ma następujący 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)
Najpierw tworzymy procedury obsługi poleceń, które umożliwią uruchamianie metod otwierania okna dialogowego, resetowania go i sprawdzania bieżącego stanu.
Następnie tworzymy 2 programy obsługi wiadomości, korzystając z filtrów utworzonych w poprzednim kroku i dodajemy do nich filtr !MessageFilters$command, dzięki czemu możemy używać poleceń w dowolnym stanie czatu.
Kod uruchamiania bota
Teraz mamy wszystko gotowe do uruchomienia, główny kod do uruchomienia bota znajduje się w pliku 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()
W rezultacie otrzymaliśmy tego bota:
W dowolnym momencie za pomocą polecenia /state możemy zapytać o aktualny stan czatu i za pomocą polecenia /reset przywróć czat do pierwotnego stanu i rozpocznij dialog od nowa.
wniosek
W tym artykule dowiedzieliśmy się, jak korzystać z bazy danych wewnątrz bota i jak budować sekwencyjne logiczne dialogi, rejestrując stan czatu.
W tym przypadku przyjrzeliśmy się najbardziej prymitywnemu przykładowi, aby łatwiej było Ci zrozumieć ideę budowania takich botów, w praktyce można budować znacznie bardziej złożone dialogi.
W kolejnym artykule z tej serii dowiemy się, jak ograniczyć uprawnienia użytkowników bota do korzystania z różnych jego metod.