R で電報ボットを作成する (パート 4): ボットとの一貫した論理的な対話の構築

すでに前回を読んでいる場合は、 XNUMXつの記事 このシリーズを読んだ方は、キーボードを使用して本格的な電報ボットを作成する方法をすでに知っています。

この記事では、一貫した対話を維持するボットの作成方法を学びます。 それらの。 ボットは質問をし、情報の入力を待ちます。 入力したデータに応じて、ボットはいくつかのアクションを実行します。

また、この記事では、ボットの内部でデータベースを使用する方法を学びます。この例では SQLite ですが、他の DBMS を使用することもできます。 R 言語でのデータベースとの対話については、次の記事で詳しく書きました。 この記事.

R で電報ボットを作成する (パート 4): ボットとの一貫した論理的な対話の構築

シリーズ「R で電報ボットを書く」のすべての記事

  1. ボットを作成し、それを使用して電報でメッセージを送信します
  2. コマンド サポートとメッセージ フィルターをボットに追加する
  3. ボットにキーボードのサポートを追加する方法
  4. ボットとの一貫した論理的な対話の構築

ページ内容

データ分析に興味がある場合は、私の記事に興味があるかもしれません。 電報 и ユーチューブ チャンネル。 コンテンツのほとんどは R 言語専用です。

  1. 導入
  2. ボット構築プロセス
  3. ボットプロジェクトの構造
  4. ボット構成
  5. 環境変数を作成する
  6. データベースの作成
  7. データベースを操作する関数を作成する
  8. ボットメソッド
  9. メッセージフィルター
  10. ハンドラー
  11. ボット起動コード
  12. まとめ

導入

ボットがユーザーにデータを要求し、ユーザーが情報を入力するのを待つためには、ダイアログの現在の状態を記録する必要があります。 これを行うための最良の方法は、SQLite などの組み込みデータベースを使用することです。

それらの。 ロジックは次のようになります。 ボット メソッドを呼び出し、ボットは順番に情報を要求し、各ステップでこの情報が入力されるのを待って確認します。

可能な限り単純なボットを作成します。最初に名前、次に年齢を尋ね、受信したデータをデータベースに保存します。 年齢を尋ねる場合、入力されたデータがテキストではなく数値であるかどうかがチェックされます。

このような単純な対話には XNUMX つの状態しかありません。

  1. start はボットの通常の状態であり、ユーザーからの情報を期待しません。
  2. wait_name - ボットが名前の入力を待機する状態
  3. wait_age は、ボットがあなたの年齢 (満年数) が入力されるのを待つ状態です。

ボット構築プロセス

この記事では、ボットを段階的に構築します。プロセス全体は次のように概略的に表すことができます。
R で電報ボットを作成する (パート 4): ボットとの一貫した論理的な対話の構築

  1. いくつかの設定を保存するボット構成を作成します。 この例では、ボット トークンとデータベース ファイルへのパスです。
  2. ボットが含まれるプロジェクトへのパスが保存される環境変数を作成します。
  3. データベース自体と、ボットがデータベースと対話できるようにするための多数の関数を作成します。
  4. ボットメソッドを書きます。 それが実行する機能。
  5. メッセージフィルターを追加します。 これを利用して、ボットはチャットの現在の状態に応じて必要なメソッドにアクセスします。
  6. コマンドとメッセージを必要なボット メソッドに接続するハンドラーを追加します。
  7. ボットを起動しましょう。

ボットプロジェクトの構造

便宜上、ボットのコードとその他の関連ファイルを次の構造に分割します。

  • bot.R — ボットのメインコード
  • db_bot_function.R — データベースを操作するための関数を含むコードのブロック
  • bot_methods.R — ボットメソッドのコード
  • message_filters.R — メッセージフィルター
  • ハンドラー.R - ハンドラー
  • config.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")))。 それを実行し、ファイルに XNUMX 行を追加します。

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}';

それらの。 テーブルフィールドに チャット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、ボットはあなたに挨拶し、受け取った名前をデータベースに書き込み、チャットを次の状態に切り替えます。 wait_age.

この段階で、ボットはあなたの年齢を入力することを期待しています。 年齢を送信すると、ボットがメッセージをチェックします。数字の代わりにテキストを送信した場合は、次のように表示されます。 Ты ввёл некорректные данные, введи число、データの再入力を待ちます。 あなたが番号を送信した場合、ボットはあなたの年齢を受け入れたことを報告し、受信したデータをデータベースに書き込み、あなたから受け取ったすべてのデータを報告し、チャットの状態を元の位置に戻します。 V start.

メソッドを呼び出すことで state 現在のチャット ステータスをいつでもリクエストできます。 reset チャットを元の状態に戻します。

メッセージフィルター

私たちの場合、これはボットを構築する上で最も重要な部分の XNUMX つです。 メッセージ フィルターの助けを借りて、ボットはユーザーからどのような情報を期待しているか、またその情報をどのように処理する必要があるかを理解します。

のプロジェクトでは 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 チャットが状態のときにメッセージを処理します 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, これにより、どのチャット状態でもコマンドを使用できるようになります。

ボット起動コード

これで、起動する準備がすべて整いました。ボットを起動するためのメイン コードはファイル内にあります。 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()

その結果、次のボットが得られました。
R で電報ボットを作成する (パート 4): ボットとの一貫した論理的な対話の構築

コマンドを使用していつでも /state 現在のチャット状態をクエリするには、次のコマンドを使用します。 /reset チャットを元の状態に戻し、再度対話を開始します。

まとめ

この記事では、ボット内でデータベースを使用する方法と、チャットの状態を記録することで連続した論理的な対話を構築する方法を理解しました。

この場合、そのようなボットの構築のアイデアを理解しやすくするために、最も原始的な例を検討しましたが、実際には、より複雑なダイアログを構築できます。

このシリーズの次の記事では、ボット ユーザーがそのさまざまな方法を使用する権利を制限する方法を学びます。

出所: habr.com

コメントを追加します