Prohoster > Blog > quản lý > Viết bot telegram trong R (phần 4): Xây dựng đoạn hội thoại nhất quán, logic với bot
Viết bot telegram trong R (phần 4): Xây dựng đoạn hội thoại nhất quán, logic với bot
Nếu bạn đã đọc phần trước ba bài viết từ loạt bài này thì bạn đã biết cách viết các bot điện tín chính thức bằng bàn phím.
Trong bài viết này, chúng ta sẽ tìm hiểu cách viết một bot có thể duy trì cuộc đối thoại nhất quán. Những thứ kia. Bot sẽ hỏi bạn các câu hỏi và chờ bạn nhập một số thông tin. Tùy thuộc vào dữ liệu bạn nhập, bot sẽ thực hiện một số hành động.
Cũng trong bài viết này, chúng ta sẽ tìm hiểu cách sử dụng cơ sở dữ liệu dưới dạng bot, trong ví dụ của chúng ta nó sẽ là SQLite, nhưng bạn có thể sử dụng bất kỳ DBMS nào khác. Tôi đã viết chi tiết hơn về việc tương tác với cơ sở dữ liệu bằng ngôn ngữ R trong bài viết này.
Tất cả các bài viết trong loạt bài “Viết bot telegram trong R”
Để bot yêu cầu dữ liệu từ bạn và đợi bạn nhập bất kỳ thông tin nào, bạn sẽ cần ghi lại trạng thái hiện tại của cuộc đối thoại. Cách tốt nhất để làm điều này là sử dụng một số loại cơ sở dữ liệu nhúng, chẳng hạn như SQLite.
Những thứ kia. Logic sẽ như sau. Chúng tôi gọi phương thức bot và bot tuần tự yêu cầu một số thông tin từ chúng tôi và ở mỗi bước, nó sẽ đợi thông tin này được nhập và có thể kiểm tra nó.
Chúng tôi sẽ viết bot đơn giản nhất có thể, đầu tiên nó sẽ hỏi tên của bạn, sau đó là tuổi của bạn và sẽ lưu dữ liệu nhận được vào cơ sở dữ liệu. Khi hỏi tuổi thì sẽ kiểm tra dữ liệu nhập vào là số chứ không phải văn bản.
Một cuộc đối thoại đơn giản như vậy sẽ chỉ có ba trạng thái:
bắt đầu là trạng thái bình thường của bot, trong đó nó không mong đợi bất kỳ thông tin nào từ bạn
wait_name - trạng thái bot chờ nhập tên
wait_age là trạng thái bot chờ nhập tuổi của bạn, số năm tròn.
Quá trình xây dựng bot
Trong bài viết này, chúng tôi sẽ xây dựng bot theo từng bước; toàn bộ quá trình có thể được mô tả dưới dạng sơ đồ như sau:
Chúng tôi tạo cấu hình bot trong đó chúng tôi sẽ lưu trữ một số cài đặt. Trong trường hợp của chúng tôi, mã thông báo bot và đường dẫn đến tệp cơ sở dữ liệu.
Chúng tôi tạo một biến môi trường trong đó đường dẫn đến dự án với bot sẽ được lưu trữ.
Chúng tôi tự tạo cơ sở dữ liệu và một số chức năng để bot có thể tương tác với nó.
Chúng tôi viết các phương thức bot, tức là các chức năng mà nó sẽ thực hiện.
Thêm bộ lọc tin nhắn. Với sự trợ giúp của bot, bot sẽ truy cập các phương thức cần thiết, tùy thuộc vào trạng thái trò chuyện hiện tại.
Chúng tôi thêm các trình xử lý sẽ kết nối các lệnh và thông báo bằng các phương thức bot cần thiết.
Hãy khởi chạy bot.
Cấu trúc dự án Bot
Để thuận tiện, chúng tôi sẽ chia mã của bot và các tệp liên quan khác thành cấu trúc sau.
bot.R — mã chính của bot của chúng tôi
db_bot_function.R — một khối mã có các chức năng làm việc với cơ sở dữ liệu
bot_method.R - mã của phương pháp bot
message_filters.R - bộ lọc tin nhắn
trình xử lý.R - người xử lý
config.cfg - cấu hình bot
create_db_data.sql — Tập lệnh SQL để tạo bảng có dữ liệu trò chuyện trong cơ sở dữ liệu
create_db_state.sql — Tập lệnh SQL để tạo bảng trạng thái trò chuyện hiện tại trong cơ sở dữ liệu
Trong cấu hình, chúng tôi viết mã thông báo bot và đường dẫn đến cơ sở dữ liệu, tức là. vào tệp bot.db, chúng ta sẽ tự tạo tệp đó trong bước tiếp theo.
Đối với các bot phức tạp hơn, bạn có thể tạo các cấu hình phức tạp hơn, ngoài ra không cần thiết phải viết cấu hình ini, bạn có thể sử dụng bất kỳ định dạng nào khác kể cả JSON.
Tạo một biến môi trường
Trên mỗi PC, thư mục chứa dự án bot có thể được đặt trong các thư mục khác nhau và trên các ổ đĩa khác nhau, vì vậy trong mã, đường dẫn đến thư mục dự án sẽ được đặt thông qua một biến môi trường TG_BOT_PATH.
Có một số cách để tạo biến môi trường, đơn giản nhất là ghi nó vào một tệp .viron.
Bạn có thể tạo hoặc chỉnh sửa tập tin này bằng lệnh file.edit(path.expand(file.path("~", ".Renviron"))). Thực thi nó và thêm một dòng vào tệp:
TG_BOT_PATH=C:/ПУТЬ/К/ВАШЕМУ/ПРОЕКТУ
Tiếp theo lưu tập tin .viron và khởi động lại RStudio.
Tạo cơ sở dữ liệu
Bước tiếp theo là tạo cơ sở dữ liệu. Chúng ta sẽ cần 2 bảng:
chat_data - dữ liệu mà bot yêu cầu từ người dùng
chat_state - trạng thái hiện tại của tất cả các cuộc trò chuyện
Bạn có thể tạo các bảng này bằng truy vấn SQL sau:
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
);
Nếu bạn đã tải xuống dự án bot từ GitHub, sau đó để tạo cơ sở dữ liệu, bạn có thể sử dụng đoạn mã sau trong 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'))
Viết các hàm để làm việc với cơ sở dữ liệu
Chúng tôi đã có sẵn tệp cấu hình và cơ sở dữ liệu được tạo. Bây giờ bạn cần viết các hàm để đọc và ghi dữ liệu vào cơ sở dữ liệu này.
Nếu bạn đã tải xuống dự án từ GitHub, sau đó bạn có thể tìm thấy các hàm trong tệp db_bot_function.R.
Mã chức năng làm việc với cơ sở dữ liệu
# ###########################################################
# 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]])
}
Chúng tôi đã tạo 4 chức năng đơn giản:
get_state() - lấy trạng thái trò chuyện hiện tại từ cơ sở dữ liệu
set_state() - ghi trạng thái trò chuyện hiện tại vào cơ sở dữ liệu
get_chat_data() - nhận dữ liệu do người dùng gửi
set_chat_data() - ghi lại dữ liệu nhận được từ người dùng
Tất cả các chức năng đều khá đơn giản, chúng đọc dữ liệu từ cơ sở dữ liệu bằng lệnh dbGetQuery(), hoặc cam kết UPSERT thao tác (thay đổi dữ liệu hiện có hoặc ghi dữ liệu mới vào cơ sở dữ liệu), sử dụng hàm dbExecute().
Cú pháp của hoạt động UPSERT như sau:
INSERT INTO chat_data (chat_id, ${field})
VALUES(${chat_id}, '${value}')
ON CONFLICT(chat_id)
DO UPDATE SET ${field}='${value}';
Những thứ kia. trong trường bảng của chúng tôi trò chuyện_id có ràng buộc duy nhất và là khóa chính của bảng. Ban đầu, chúng tôi cố gắng thêm thông tin vào bảng và gặp lỗi nếu dữ liệu cho cuộc trò chuyện hiện tại đã có sẵn, trong trường hợp đó, chúng tôi chỉ cần cập nhật thông tin cho cuộc trò chuyện này.
Tiếp theo, chúng ta sẽ sử dụng các hàm này trong các phương thức và bộ lọc của bot.
Phương pháp bot
Bước tiếp theo trong việc xây dựng bot của chúng ta là tạo các phương thức. Nếu bạn đã tải xuống dự án từ GitHub, thì tất cả các phương thức đều có trong tệp bot_method.R.
Mã phương thức bot
# ###########################################################
# 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')
}
}
Chúng tôi đã tạo ra 5 phương pháp:
bắt đầu - Bắt đầu một hộp thoại
state - Nhận trạng thái trò chuyện hiện tại
đặt lại - Đặt lại trạng thái trò chuyện hiện tại
enter_name - Bot hỏi tên của bạn
enter_age - Bot hỏi tuổi của bạn
Phương thức start hỏi tên của bạn và thay đổi trạng thái trò chuyện thành chờ_tên, I E. để chờ nhập tên của bạn.
Tiếp theo, bạn gửi tên và nó được xử lý theo phương thức enter_name, bot chào bạn, ghi tên nhận được vào cơ sở dữ liệu và chuyển cuộc trò chuyện sang trạng thái chờ_tuổi.
Ở giai đoạn này, bot mong đợi bạn nhập vào độ tuổi của mình. Bạn gửi tuổi của mình, bot kiểm tra tin nhắn, nếu bạn gửi văn bản thay vì số thì nó sẽ báo: Ты ввёл некорректные данные, введи числоvà sẽ đợi bạn nhập lại dữ liệu của mình. Nếu bạn gửi một số, bot sẽ báo cáo rằng nó đã chấp nhận tuổi của bạn, ghi dữ liệu đã nhận vào cơ sở dữ liệu, báo cáo tất cả dữ liệu nhận được từ bạn và đưa trạng thái trò chuyện về vị trí ban đầu, tức là. V. start.
Bằng cách gọi phương thức state bạn có thể yêu cầu trạng thái trò chuyện hiện tại bất kỳ lúc nào và sử dụng reset đưa cuộc trò chuyện về trạng thái ban đầu.
Bộ lọc tin nhắn
Trong trường hợp của chúng tôi, đây là một trong những phần quan trọng nhất trong việc xây dựng bot. Với sự trợ giúp của các bộ lọc tin nhắn, bot sẽ hiểu thông tin nào nó mong đợi từ bạn và cách xử lý thông tin đó.
Trong dự án về GitHub bộ lọc được đăng ký trong tập tin message_filters.R.
Mã lọc tin nhắn:
# ###########################################################
# 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"
}
)
Trong các bộ lọc, chúng tôi sử dụng hàm đã viết trước đó get_state(), để yêu cầu trạng thái hiện tại của cuộc trò chuyện. Hàm này chỉ yêu cầu 1 đối số, id trò chuyện.
Bộ lọc tiếp theo chờ_tên xử lý tin nhắn khi cuộc trò chuyện ở trạng thái wait_name, và theo đó bộ lọc chờ_tuổi xử lý tin nhắn khi cuộc trò chuyện ở trạng thái wait_age.
Trình xử lý
Tệp có trình xử lý được gọi trình xử lý.R, và có đoạn mã sau:
# ###########################################################
# 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)
Trước tiên, chúng tôi tạo các trình xử lý lệnh cho phép bạn chạy các phương thức để bắt đầu hộp thoại, đặt lại hộp thoại và truy vấn trạng thái hiện tại.
Tiếp theo, chúng tôi tạo 2 trình xử lý tin nhắn bằng cách sử dụng các bộ lọc đã tạo ở bước trước và thêm bộ lọc cho chúng !MessageFilters$command, để chúng ta có thể sử dụng lệnh ở bất kỳ trạng thái trò chuyện nào.
Mã khởi chạy bot
Bây giờ chúng ta đã sẵn sàng mọi thứ để khởi chạy, mã chính để khởi chạy bot nằm trong tệp 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()
Kết quả là chúng tôi đã nhận được bot này:
Bất cứ lúc nào sử dụng lệnh /state chúng ta có thể truy vấn trạng thái trò chuyện hiện tại và sử dụng lệnh /reset đưa cuộc trò chuyện về trạng thái ban đầu và bắt đầu lại cuộc đối thoại.
Kết luận
Trong bài viết này, chúng tôi đã tìm ra cách sử dụng cơ sở dữ liệu bên trong bot và cách xây dựng các cuộc đối thoại logic tuần tự bằng cách ghi lại trạng thái trò chuyện.
Trong trường hợp này, chúng tôi đã xem xét ví dụ nguyên thủy nhất để bạn dễ hiểu hơn về ý tưởng xây dựng các bot như vậy; trong thực tế, bạn có thể xây dựng các cuộc đối thoại phức tạp hơn nhiều.
Trong bài viết tiếp theo của loạt bài này, chúng ta sẽ tìm hiểu cách hạn chế quyền của người dùng bot trong việc sử dụng các phương pháp khác nhau của nó.