Writing a telegram bot in R language (part 4): Building a consistent, logical dialogue with a bot

If you have already read the previous three articles in this series, then you already know how to write full-fledged telegram bots with a keyboard.



In this article, we will learn how to write a bot that will maintain a consistent dialogue. Those. the bot will ask you questions and wait for you to enter any information. Depending on the data you entered, the bot will perform some actions.



Also in this article we will learn how to use a database under the hood of a bot, in our example it will be SQLite, but you can use any other DBMS. I wrote in more detail about interacting with databases in the R language in this article .





All articles from the series "Writing a telegram bot in the R language"



  1. We create a bot and send messages to telegram using it
  2. Add command support and message filters to the bot
  3. How to add keyboard support to your bot
  4. Building a consistent, logical dialogue with the bot
  5. Bot user rights management


Content



telegram youtube . R.







, , - . , , SQLite.



.. . , - , , .



, , , . , , .



:



  1. start β€” ,
  2. wait_name β€” ,
  3. wait_age β€” , , .




, :



  1. , . , .
  2. , .
  3. , , .
  4. , .. .
  5. . , .
  6. , .
  7. .




, .



  • bot.R β€”
  • db_bot_function.R β€”
  • bot_methods.R β€”
  • message_filters.R β€”
  • handlers.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.





, , TG_BOT_PATH.



, β€” .Renviron.



, file.edit(path.expand(file.path("~", ".Renviron"))). :



TG_BOT_PATH=C:////


.Renviron 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 β€”
  • reset β€”
  • enter_name β€”
  • enter_age β€”


start , wait_name, .. .



, enter_name, , , wait_age.



. , , - : , , . , , , , , .. 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(), , . 1 , id .



wait_name wait_name, wait_age wait_age.





handlers.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()


, :

image



/state , /reset .





, .



In this case, we examined the most primitive example, in order to make it easier for you to understand the idea of ​​building such bots, in practice you can build much more complex dialogues.



In the next article in this series, we will learn how to restrict the rights of bot users to use various methods.




All Articles