R로 텔레그램 봇 작성(4부): 봇과 일관되고 논리적인 대화 구축

이미 이전 글을 읽어보셨다면 기사 세 개 이 시리즈를 통해 키보드를 사용하여 본격적인 텔레그램 봇을 작성하는 방법을 이미 알고 계실 것입니다.

이 기사에서는 일관된 대화를 유지하는 봇을 작성하는 방법을 배웁니다. 저것들. 봇은 사용자에게 질문을 하고 사용자가 몇 가지 정보를 입력할 때까지 기다립니다. 입력한 데이터에 따라 봇이 몇 가지 작업을 수행합니다.

또한 이 기사에서는 봇 내부에서 데이터베이스를 사용하는 방법을 배웁니다. 이 예에서는 SQLite이지만 다른 DBMS를 사용할 수도 있습니다. 나는 R 언어로 데이터베이스와 상호 작용하는 것에 대해 더 자세히 썼습니다. 이 기사.

R로 텔레그램 봇 작성(4부): 봇과 일관되고 논리적인 대화 구축

"R로 텔레그램 봇 작성" 시리즈의 모든 기사

  1. 우리는 봇을 만들고 그것을 사용하여 텔레그램으로 메시지를 보냅니다.
  2. 봇에 명령 지원 및 메시지 필터 추가
  3. 봇에 키보드 지원을 추가하는 방법
  4. 봇과 일관되고 논리적인 대화 구축

내용

데이터 분석에 관심이 있다면 제 글에도 관심이 있으실 겁니다. 전보 и 유튜브 채널. 대부분의 콘텐츠는 R 언어에 전념합니다.

  1. 소개
  2. 봇 구축 과정
  3. 봇 프로젝트 구조
  4. 봇 구성
  5. 환경 변수 만들기
  6. 데이터베이스 만들기
  7. 데이터베이스 작업을 위한 함수 작성
  8. 봇 방법
  9. 메시지 필터
  10. 핸들러
  11. 봇 실행 코드
  12. 결론

소개

봇이 귀하에게 데이터를 요청하고 귀하가 정보를 입력할 때까지 기다리려면 대화의 현재 상태를 기록해야 합니다. 이를 수행하는 가장 좋은 방법은 SQLite와 같은 일종의 내장 데이터베이스를 사용하는 것입니다.

저것들. 논리는 다음과 같습니다. 우리는 봇 메소드를 호출하고, 봇은 우리에게 순차적으로 몇 가지 정보를 요청하고, 각 단계에서 이 정보가 입력되기를 기다리고 확인할 수 있습니다.

우리는 가능한 가장 간단한 봇을 작성할 것입니다. 먼저 귀하의 이름과 나이를 묻고 수신된 데이터를 데이터베이스에 저장합니다. 나이를 물을 때 입력한 데이터가 텍스트가 아닌 숫자인지 확인합니다.

이러한 간단한 대화에는 세 가지 상태만 있습니다.

  1. start는 봇의 정상적인 상태로, 사용자로부터 어떤 정보도 기대하지 않습니다.
  2. wait_name - 봇이 이름이 입력될 때까지 기다리는 상태
  3. wait_age는 봇이 나이가 입력될 때까지 기다리는 상태, 즉 전체 연수입니다.

봇 구축 과정

이 기사에서 우리는 봇을 단계별로 구축할 것이며 전체 프로세스는 다음과 같이 개략적으로 설명될 수 있습니다.
R로 텔레그램 봇 작성(4부): 봇과 일관되고 논리적인 대화 구축

  1. 일부 설정을 저장할 봇 구성을 만듭니다. 우리의 경우에는 봇 토큰과 데이터베이스 파일 경로입니다.
  2. 봇이 있는 프로젝트의 경로가 저장될 환경 변수를 만듭니다.
  3. 우리는 데이터베이스 자체를 생성하고 봇이 데이터베이스와 상호 작용할 수 있도록 다양한 기능을 생성합니다.
  4. 우리는 봇 메소드를 작성합니다. 그것이 수행할 기능.
  5. 메시지 필터를 추가합니다. 이를 통해 봇은 현재 채팅 상태에 따라 필요한 방법에 액세스하게 됩니다.
  6. 필요한 봇 메서드를 사용하여 명령과 메시지를 연결하는 핸들러를 추가합니다.
  7. 봇을 실행해 보겠습니다.

봇 프로젝트 구조

편의상 봇의 코드와 기타 관련 파일을 다음 구조로 나누겠습니다.

  • 봇.R — 우리 봇의 주요 코드
  • db_bot_function.R — 데이터베이스 작업을 위한 함수가 포함된 코드 블록
  • bot_methods.R — 봇 메소드 코드
  • message_filters.R — 메시지 필터
  • 핸들러 .R - 핸들러
  • 구성.cfg - 봇 구성
  • create_db_data.sql — 데이터베이스에 채팅 데이터가 포함된 테이블을 생성하기 위한 SQL 스크립트
  • create_db_state.sql — 데이터베이스에 현재 채팅 상태의 테이블을 생성하기 위한 SQL 스크립트
  • bot.db - 봇 데이터베이스

전체 봇 프로젝트를 보거나 다운로드 내로부터 GitHub의 저장소.

봇 구성

우리는 일반적인 것을 구성으로 사용하겠습니다 ini 파일, 다음 형식:

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

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

구성에서 우리는 봇 토큰과 데이터베이스 경로를 작성합니다. bot.db 파일에 추가합니다. 다음 단계에서 파일 자체를 생성하겠습니다.

더 복잡한 봇의 경우 더 복잡한 구성을 생성할 수 있습니다. 또한 ini 구성을 작성할 필요가 없으며 JSON을 포함한 다른 형식을 사용할 수 있습니다.

환경 변수 만들기

각 PC에서 봇 프로젝트가 있는 폴더는 다른 디렉터리와 다른 드라이브에 있을 수 있으므로 코드에서 프로젝트 폴더의 경로는 환경 변수를 통해 설정됩니다. TG_BOT_PATH.

환경 변수를 생성하는 방법에는 여러 가지가 있습니다. 가장 간단한 방법은 파일에 작성하는 것입니다. .렌바이론.

다음 명령을 사용하여 이 파일을 생성하거나 편집할 수 있습니다. file.edit(path.expand(file.path("~", ".Renviron"))). 이를 실행하고 파일에 한 줄을 추가합니다.

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

다음으로 파일을 저장하세요. .렌바이론 RStudio를 다시 시작하세요.

데이터베이스 만들기

다음 단계는 데이터베이스를 생성하는 것입니다. 테이블 2개가 필요합니다:

  • chat_data — 봇이 사용자에게 요청한 데이터
  • chat_state — 모든 채팅의 현재 상태

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

봇 프로젝트를 다운로드한 경우 GitHub의, 데이터베이스를 생성하려면 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'))

데이터베이스 작업을 위한 함수 작성

이미 구성 파일이 준비되어 있고 데이터베이스가 생성되었습니다. 이제 이 데이터베이스에서 데이터를 읽고 쓰는 함수를 작성해야 합니다.

프로젝트를 다운로드한 경우 GitHub의, 그러면 파일에서 함수를 찾을 수 있습니다 db_bot_function.R.

데이터베이스 작업을 위한 함수 코드

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

}

우리는 4가지 간단한 함수를 만들었습니다:

  • get_state() — 데이터베이스에서 현재 채팅 상태를 가져옵니다.
  • set_state() — 현재 채팅 상태를 데이터베이스에 기록합니다.
  • get_chat_data() — 사용자가 보낸 데이터 수신
  • set_chat_data() — 사용자로부터 받은 기록 데이터

모든 기능은 매우 간단하며 다음 명령을 사용하여 데이터베이스에서 데이터를 읽습니다. dbGetQuery()또는 커밋 UPSERT 함수를 사용하여 작업(기존 데이터 변경 또는 데이터베이스에 새 데이터 쓰기) dbExecute().

UPSERT 작업의 구문은 다음과 같습니다.

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

저것들. 우리 테이블 필드에서 chat_id 고유성 제약 조건이 있으며 테이블의 기본 키입니다. 처음에는 테이블에 정보를 추가하려고 시도하고 현재 채팅에 대한 데이터가 이미 있으면 오류가 발생합니다. 이 경우 이 채팅에 대한 정보를 업데이트하기만 하면 됩니다.

다음으로 봇의 메서드와 필터에서 이러한 기능을 사용하겠습니다.

봇 방법

봇 구축의 다음 단계는 메서드를 만드는 것입니다. 프로젝트를 다운로드한 경우 GitHub의이면 모든 메소드가 파일에 있습니다. bot_methods.R.

봇 메소드 코드

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

}

우리는 5가지 메소드를 만들었습니다:

  • start — 대화 상자 시작
  • state — 현재 채팅 상태를 가져옵니다.
  • 재설정 — 현재 채팅 상태를 재설정합니다.
  • enter_name — 봇이 귀하의 이름을 묻습니다.
  • enter_age — 봇이 나이를 묻습니다.

방법 start 이름을 묻고 채팅 상태를 다음으로 변경합니다. wait_name, 즉. 이름 입력을 대기합니다.

다음으로 이름을 보내고 메소드에 의해 처리됩니다. enter_name, 봇이 인사하고 수신된 이름을 데이터베이스에 기록하고 채팅을 다음 상태로 전환합니다. 대기 연령.

이 단계에서 봇은 귀하가 나이를 입력할 것으로 예상합니다. 나이를 보내면 봇이 메시지를 확인합니다. 숫자 대신 텍스트를 보낸 경우 다음과 같이 표시됩니다. Ты ввёл некорректные данные, введи число, 데이터를 다시 입력할 때까지 기다립니다. 귀하가 번호를 보낸 경우 봇은 귀하의 나이를 수락했다고 보고하고, 수신된 데이터를 데이터베이스에 기록하며, 귀하로부터 받은 모든 데이터를 보고하고 채팅 상태를 원래 위치로 되돌립니다. V start.

메소드를 호출하여 state 언제든지 현재 채팅 상태를 요청할 수 있으며 reset 채팅을 원래 상태로 되돌립니다.

메시지 필터

우리의 경우 이는 봇을 구축하는 데 가장 중요한 부분 중 하나입니다. 메시지 필터를 사용하면 봇이 사용자에게 기대하는 정보와 처리 방법을 이해할 수 있습니다.

에 있는 프로젝트에서는 GitHub의 필터가 파일에 등록되어 있습니다. message_filters.R.

메시지 필터 코드:

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

필터에서는 이전에 작성된 함수를 사용합니다. get_state(), 채팅의 현재 상태를 요청합니다. 이 함수에는 채팅 ID라는 1개의 인수만 필요합니다.

다음 필터 wait_name 채팅 상태에서 메시지를 처리합니다. wait_name, 그리고 그에 따라 필터 대기 연령 채팅 상태에서 메시지를 처리합니다. wait_age.

핸들러

핸들러가 있는 파일은 다음과 같습니다. 핸들러 .R이며 다음 코드가 있습니다.

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

먼저 대화 상자를 시작하고, 재설정하고, 현재 상태를 쿼리하는 메서드를 실행할 수 있는 명령 처리기를 만듭니다.

다음으로, 이전 단계에서 생성한 필터를 사용하여 2개의 메시지 핸들러를 생성하고 여기에 필터를 추가합니다. !MessageFilters$command, 모든 채팅 상태에서 명령을 사용할 수 있습니다.

봇 실행 코드

이제 시작할 준비가 모두 완료되었습니다. 봇을 시작하기 위한 기본 코드는 파일에 있습니다. 봇.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()

결과적으로 우리는 다음과 같은 봇을 얻었습니다.
R로 텔레그램 봇 작성(4부): 봇과 일관되고 논리적인 대화 구축

언제든지 명령을 사용하여 /state 현재 채팅 상태를 쿼리하고 다음 명령을 사용할 수 있습니다. /reset 채팅을 원래 상태로 되돌리고 대화를 다시 시작하세요.

결론

이 기사에서는 봇 내부에서 데이터베이스를 사용하는 방법과 채팅 상태를 기록하여 순차적인 논리적 대화를 구축하는 방법을 알아냈습니다.

이 경우에는 그러한 봇을 만드는 아이디어를 더 쉽게 이해할 수 있도록 가장 원시적인 예를 살펴보았으며 실제로는 훨씬 더 복잡한 대화를 만들 수 있습니다.

이 시리즈의 다음 기사에서는 봇 사용자의 다양한 방법 사용 권한을 제한하는 방법을 알아봅니다.

출처 : habr.com

코멘트를 추가