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.

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

Tất cả các bài viết trong loạt bài “Viết bot telegram trong R”

  1. Chúng tôi tạo bot và sử dụng nó để gửi tin nhắn trong telegram
  2. Thêm bộ lọc tin nhắn và hỗ trợ lệnh vào bot
  3. Cách thêm hỗ trợ bàn phím cho bot
  4. Xây dựng cuộc đối thoại nhất quán, hợp lý với bot

nội dung

Nếu bạn quan tâm đến phân tích dữ liệu, bạn có thể quan tâm đến tôi điện tín и youtube kênh truyền hình. Hầu hết nội dung được dành cho ngôn ngữ R.

  1. Giới thiệu
  2. Quá trình xây dựng bot
  3. Cấu trúc dự án Bot
  4. Cấu hình bot
  5. Tạo một biến môi trường
  6. Tạo cơ sở dữ liệu
  7. Viết các hàm để làm việc với cơ sở dữ liệu
  8. Phương pháp bot
  9. Bộ lọc tin nhắn
  10. Trình xử lý
  11. Mã khởi chạy bot
  12. Kết luận

Giới thiệu

Để 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:

  1. 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
  2. wait_name - trạng thái bot chờ nhập tên
  3. 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:
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

  1. 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.
  2. 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ữ.
  3. 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ó.
  4. 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.
  5. 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.
  6. 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.
  7. 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
  • bot.db - cơ sở dữ liệu bot

Bạn có thể xem toàn bộ dự án bot hoặc tải về từ tôi kho lưu trữ trên GitHub.

Cấu hình bot

Chúng tôi sẽ sử dụng cái thông thường làm cấu hình tập tin ini, có dạng sau:

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

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

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:
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

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ó.

Nguồn: www.habr.com

Thêm một lời nhận xét