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

Pisanie bota telegramowego w R (część 4): Budowanie spójnego, logicznego dialogu z botem

Wszystkie artykuły z serii „Pisanie bota telegramowego w R”

  1. Tworzymy bota i używamy go do wysyłania wiadomości w telegramie
  2. Dodaj obsługę poleceń i filtry wiadomości do bota
  3. Jak dodać obsługę klawiatury do bota
  4. Budowanie spójnego, logicznego dialogu z botem

Zawartość

Jeśli interesuje Cię analiza danych, być może zainteresuje Cię mój telegram и youtube kanały. Większość treści jest poświęcona językowi R.

  1. Wprowadzenie
  2. Proces tworzenia bota
  3. Struktura projektu bota
  4. Konfiguracja bota
  5. Utwórz zmienną środowiskową
  6. Tworzenie bazy danych
  7. Pisanie funkcji do pracy z bazą danych
  8. Metody botów
  9. Filtry wiadomości
  10. Opiekunowie
  11. Kod uruchamiania bota
  12. wniosek

Wprowadzenie

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:

  1. start to normalny stan bota, w którym nie oczekuje on od Ciebie żadnych informacji
  2. wait_name - stan w którym bot czeka na podanie nazwy
  3. 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:
Pisanie bota telegramowego w R (część 4): Budowanie spójnego, logicznego dialogu z botem

  1. Tworzymy konfigurację bota, w której będziemy przechowywać niektóre ustawienia. W naszym przypadku token bota i ścieżka do pliku bazy danych.
  2. Tworzymy zmienną środowiskową, w której będzie zapisana ścieżka do projektu z botem.
  3. Tworzymy samą bazę danych, oraz szereg funkcji, aby bot mógł z nią współdziałać.
  4. Piszemy metody botów, tj. funkcje, jakie będzie pełnił.
  5. 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.
  6. Dodajemy procedury obsługi, które będą łączyć polecenia i wiadomości z niezbędnymi metodami bota.
  7. 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
  • bot.db - baza danych botów

Możesz wyświetlić cały projekt bota lub pobranie od mojego repozytorium na GitHubie.

Konfiguracja bota

Użyjemy zwykłego jako konfiguracji plik ini, następująca forma:

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

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

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:
Pisanie bota telegramowego w R (część 4): Budowanie spójnego, logicznego dialogu z botem

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.

Źródło: www.habr.com

Dodaj komentarz