Русский
Русский
English
Статистика
Реклама

Язык r

Пишем telegram бота на языке R (часть 1) Создаём бота, и отправляем с его помощью сообщения в telegram

13.08.2020 10:12:29 | Автор: admin

Аудитория telegram ежедневно растёт с геометрической прогрессией, этому способствует удобство мессенджера, наличие каналов, чатов, и конечно возможность создавать ботов.


Боты могут использоваться в совершенно разных целях, от автоматизации коммуникации с вашими клиентами до управления вашими собственными задачами.


По сути через бота можно используя telegram выполнять любые операции: отправлять, либо запрашивать данные, запускать задачи на сервере, собирать информацию в базу данных, отправлять электронные письма и так далее.


Я планирую написать серию статей, о том, как на языке R работать с telegram bot API, и писать ботов под свои нужды.



В этой, первой статье мы разберёмся как создать телеграм бота, и отправлять с его помощью уведомления в telegram.


В результате у нас получится бот, который будет проверять статус последнего выполнения всех задач в планировщике заданий Windows, и отправлять вам уведомления, если какие-то завершились ошибкой.


Но цель этой серии статей заключается не в том, что бы научить вас писать бота под определённую, узкую задачу, а в целом познакомить с синтаксисом пакета telegram.bot, и примерами кода, с помощью которых вы сможете писать ботов, под решение собственных задач.


Содержание


Если вы интересуетесь анализом данных возможно вам будут интересны мои telegram и youtube каналы. Большая часть контента которых посвящены языку R.


  1. Создание телеграм бота
  2. Установка пакета для работы с телеграм ботом на R
  3. Отправка сообщений из R в Telegram
  4. Настройка расписания запуска проверки задач
  5. Заключение

Создание телеграм бота


Для начала нам необходимо создать бота. Делается это с помощью специального бота BotFather, переходим по ссылке и пишем боту /start.


После чего вы получите сообщение со списком команд:


Сообщение от BotFather
I can help you create and manage Telegram bots. If you're new to the Bot API, please see the manual (http://personeltest.ru/aways/core.telegram.org/bots).You can control me by sending these commands:/newbot - create a new bot/mybots - edit your bots [beta]Edit Bots/setname - change a bot's name/setdescription - change bot description/setabouttext - change bot about info/setuserpic - change bot profile photo/setcommands - change the list of commands/deletebot - delete a botBot Settings/token - generate authorization token/revoke - revoke bot access token/setinline - toggle inline mode (http://personeltest.ru/aways/core.telegram.org/bots/inline)/setinlinegeo - toggle inline location requests (http://personeltest.ru/aways/core.telegram.org/bots/inline#location-based-results)/setinlinefeedback - change inline feedback (http://personeltest.ru/aways/core.telegram.org/bots/inline#collecting-feedback) settings/setjoingroups - can your bot be added to groups?/setprivacy - toggle privacy mode (http://personeltest.ru/aways/core.telegram.org/bots#privacy-mode) in groupsGames/mygames - edit your games (http://personeltest.ru/aways/core.telegram.org/bots/games) [beta]/newgame - create a new game (http://personeltest.ru/aways/core.telegram.org/bots/games)/listgames - get a list of your games/editgame - edit a game/deletegame - delete an existing game

Для создания нового бота отправляем команду /newbot.


BotFather попросит вас ввести имя и логин бота.


BotFather, [25.07.20 09:39]Alright, a new bot. How are we going to call it? Please choose a name for your bot.Alexey Seleznev, [25.07.20 09:40]My Test BotBotFather, [25.07.20 09:40]Good. Now let's choose a username for your bot. It must end in `bot`. Like this, for example: TetrisBot or tetris_bot.Alexey Seleznev, [25.07.20 09:40]@my_test_bot

Имя вы можете ввести произвольное, а логин должен заканчиваться на bot.


Если вы всё сделали правильно, то получите следующее сообщение:


Done! Congratulations on your new bot. You will find it at t.me/my_test_bot. You can now add a description, about section and profile picture for your bot, see /help for a list of commands. By the way, when you've finished creating your cool bot, ping our Bot Support if you want a better username for it. Just make sure the bot is fully operational before you do this.Use this token to access the HTTP API:123456789:abcdefghijklmnopqrstuvwxyzFor a description of the Bot API, see this page: https://core.telegram.org/bots/api

Далее вам понадобится полученный API токен, в моём примере это 123456789:abcdefghijklmnopqrstuvwxyz.


На этом шаге подготовительные работы по созданию бота завершены.


Установка пакета для работы с телеграм ботом на R


Я предполагаю, что у вас уже установлен язык R, и среда разработки RStudio. Если это не так, то вы можете посмотреть данный видео урок о том, как их установить.


Для работы с Telegram Bot API мы будем использовать R пакет telegram.bot.


Установка пакетов в R осуществляется функцией install.packages(), поэтому для установки нужного нам пакета используйте команду install.packages("telegram.bot").


Более подробно узнать об установке различных пакетов можно из этого видео.


После установки пакета его необходимо подключить:


library(telegram.bot)

Отправка сообщений из R в Telegram


Созданного вами бота можно найти в Telegram по заданному при создании логину, в моём случае это @my_test_bot.


Отправьте боту любое сообщение, например "Привет бот". На данный момент это нам надо для того, что бы получить id вашего с ботом чата.


Теперь в R пишем следующий код.


library(telegram.bot)# создаём экземпляр ботаbot <- Bot(token = "123456789:abcdefghijklmnopqrstuvwxyz")# Запрашиваем информацию о ботеprint(bot$getMe())# Получаем обновления бота, т.е. список отправленных ему сообщенийupdates <- bot$getUpdates()# Запрашиваем идентификатор чата# Примечание: перед запросом обновлений вы должны отправить боту сообщениеchat_id <- updates[[1L]]$from_chat_id()

Изначально мы создаём экземпляр нашего бота функцией Bot(), в качестве аргумента в неё необходимо передать полученный ранее токен. Хранить токен в коде считается не лучшей практикой, поэтому вы можете хранить его в переменной среды, и считывать его из неё. По умолчанию в пакете telegram.bot реализована поддержка переменных среды следующего наименования: R_TELEGRAM_BOT_ИМЯ_ВАШЕГО_БОТА. Вместо ИМЯ_ВАШЕГО_БОТА подставьте имя которое вы задали при создании, в моём случае будет переменная R_TELEGRAM_BOT_My Test Bot. Далее вы можете использовать сохранённый в переменной среды токен с помощью функции bot_token(), т.е. вот так:


bot <- Bot(token = bot_token("My Test Bot"))

Метод getUpdates()позволяет нам получить обновления бота, т.е. сообщения которые были ему отправлены. Метод from_chat_id(), позволяет получить идентификатор чата, из которого было отправлено сообщение. Этот идентификатор нам нужен для отправки сообщений от бота.


Помимо id чата из объекта полученного методом getUpdates() вы получаете и некоторую другую полезную информацию. Например, информацию о пользователе, отправившем сообщение.


updates[[1L]]$message$from

$id[1] 000000000$is_bot[1] FALSE$first_name[1] "Alexey"$last_name[1] "Seleznev"$username[1] "AlexeySeleznev"$language_code[1] "ru"

Итак, на данном этапе у нас уже есть всё, что необходимо для отправки сообщения от бота в телеграм. Воспользуемся методом sendMessage(), в который необходимо передать идентификатор чата, текст сообщения, и тип разметки текста сообщения. Тип разметки может быть Markdown или HTML и устанавливается аргументом parse_mode.


# Отправка сообщенияbot$sendMessage(chat_id,                text = "Привет, *жирный текст* _курсив_",                parse_mode = "Markdown")

Основы форматирования Markdown разметки:


  • Жирный шрифт выделяется с помощью знака *:
    • пример: *жирный шритф*
    • результат: жирный шритф
  • Курсив задаётся нижним подчёркиванием:
    • пример: _курсив_
    • результат: курсив
  • Моноширинный шрифт, которым обычно выделяется программный код, задаётся с помощью апострофов `:
    • пример: `моноширинный шрифт`
    • результат: моноширинный шрифт

Основы форматирования HTML разметки:
В HTML вы заворачиваете часть текста, которую надо выделать, в теги, пример <тег>текст</тег>.


  • <тег> открывающий тег
  • </тег> закрывающий тег

Теги HTML разметки


  • <b> жирный шрифт
    • пример: <b>жирный шрифт</b>
    • результат жирный шрифт
  • <i> курсив
    • пример: <i>курсив</i>
    • результат: курсив
  • <code> моноширинный шрифт
    • пример: <code>моноширинный шрифт</code>
    • результат: моноширинный шрифт

Помимо текста вы можете отправлять и другой контент используя специальные методы:


# Отправить изображениеbot$sendPhoto(chat_id,  photo = "https://telegram.org/img/t_logo.png")# Отправка голосового сообщенияbot$sendAudio(chat_id,  audio = "http://www.largesound.com/ashborytour/sound/brobob.mp3")# Отправить документbot$sendDocument(chat_id,  document = "https://github.com/ebeneditos/telegram.bot/raw/gh-pages/docs/telegram.bot.pdf")# Отправить стикерbot$sendSticker(chat_id,  sticker = "https://www.gstatic.com/webp/gallery/1.webp")# Отправить видеоbot$sendVideo(chat_id,  video = "http://techslides.com/demos/sample-videos/small.mp4")# Отправить gif анимациюbot$sendAnimation(chat_id,  animation = "https://media.giphy.com/media/sIIhZliB2McAo/giphy.gif")# Отправить локациюbot$sendLocation(chat_id,  latitude = 51.521727,  longitude = -0.117255)# Имитация действия в чатеbot$sendChatAction(chat_id,  action = "typing")

Т.е. например с помощью метода sendPhoto() вы можете отправить сохранённый в виде изображения график, который вы построили с помощью пакета ggplot2.


Проверка планировщика задач Windows, и отправка уведомления о задачах, работа которых была завершена аварийно


Для работы с планировщиком заданий Windows вам необходимо установить пакет taskscheduleR, и для удобства работы с данными установим пакет dplyr.


# Установка пакетовinstall.packages(c('taskscheduleR', 'dplyr'))# Подключение пакетовlibrary(taskscheduleR)library(dplyr)

Далее с помощью функции taskscheduler_ls() мы запрашиваем информацию о задачах из нашего планировщика. С помощью функции filter() из пакета dplyr мы убираем из списка задач те, которые были успешно выполненны и имеют статус последнего результата 0, и те, которые ещё ни разу не запускались и имеют статус 267011, выключенные задачи, и задачи которые выполняются в данный момент.


# запрашиваем список задачtask <- task <- taskscheduler_ls() %>%        filter(! `Last Result`  %in% c("0", "267011") &                `Scheduled Task State` == "Enabled" &                Status != "Running") %>%        select(TaskName) %>%        unique() %>%        unlist() %>%        paste0(., collapse = "\n")

В объекте task у нас теперь список задач, работа которых завершилась ошибкой, этот список нам надо отправить в Telegram.


Если рассмотреть каждую команду подробнее, то:


  • filter() фильтрует список задач, по описанным выше условиям
  • select() оставляет в таблице только одно поле с названием задач
  • unique() убирает дубли названий
  • unlist() переводит выбранный столбец таблицы в вектор
  • paste0() соединяет названия задач в одну строку, и ставит в качестве разделителя знак перевода строки, т.е. \n.

Всё что нам остаётся отправить этот результат в телеграм.


bot$sendMessage(chat_id,                text = task,                parse_mode = "Markdown")

Итак, на данный момент код бота выглядит вот так:


Код бота проверяющего задачи
# Подключение пакетаlibrary(telegram.bot)library(taskscheduleR)library(dplyr)# инициализируем ботаbot <- Bot(token = "123456789:abcdefghijklmnopqrstuvwxyz")# идентификатор чатаchat_id <- 123456789# запрашиваем список задачtask <- taskscheduler_ls() %>%        filter(! `Last Result`  %in% c("0", "267011")  &               `Scheduled Task State` == "Enabled" &                Status != "Running") %>%        select(TaskName) %>%        unique() %>%        unlist() %>%        paste0(., collapse = "\n")# если есть проблемные задачи отправляем сообщениеif ( task != "" ) {  bot$sendMessage(chat_id,                  text = task,                  parse_mode = "Markdown"  )}

При использовании приведённого выше примера подставьте в код токен вашего бота и ваш идентификатор чата.


Вы можете добавлять условия фильтрации задач, например проверяя только те задачи, которые были созданны вами, исключая системные.


Так же вы можете вынести различные настройки в отдельный файл конфигурации, и хранить в нём id чата и токен. Читать конфиг можно например с помощью пакета configr.


Пример ini конфига
[telegram_bot];настройки телеграм бота и чата, в который будут приходить уведомленияchat_id=12345678bot_token=123456789:abcdefghijklmnopqrstuvwxyz"

Пример чтения переменных из конфига в R
library(configr)# чтение конфинаconfig <- read.config('C:/путь_к_конфигу/config.cfg', rcmd.parse = TRUE)bot_token <- config$telegram_bot$bot_tokenchat_id     <- config$telegram_bot$chat_id

Настраиваем расписание запуска проверки задач


Наиболее подробно процесс настройки запуска скриптов по расписанию описан в этой статье. Тут я лишь опишу шаги, которые для этого необходимо выполнить. Если какой-то из шагов вам не понятен, то обратитесь к статье на которую я указал ссылку.


Предположим, что мы сохранили код нашего бота в файл check_bot.R. Для того, что бы запланировать регулярный запуск этого файла выполните следующие шаги:


  1. Пропишите в системную переменную Path путь к папке в которой установлен R, в Windows путь будет примерно таким: C:\Program Files\R\R-4.0.2\bin.
  2. Создайте исполняемый bat файл, в котором будет всего одна строка R CMD BATCH C:\rscripts\check_bot\check_bot.R. Замените C:\rscripts\check_bot\check_bot.R на полный путь к вашему R файлу.
  3. Далее настройте с помощью планировщика задач Windows расписание запуска, например на каждые пол часа.

Заключение


В этой статье мы разобрались с тем, как создать бота, и отправлять с его помощью различные уведомления в telegram.


Я описал задачу контроля планировщика заданий Windows, но вы можете использовать материал этой статьи для отправки любых уведомлений, от прогноза погоды до котировок акций на фондовой бирже, т.к. R позволяет вам подключиться к огромному количеству источников данных.


В следующей статье мы с вами разберёмся с тем, как добавить боту команды и клавиатуру, для того, что он мог не только отправлять уведомления, но и выполнять более сложные действия.

Подробнее..

Пишем telegram бота на языке R (часть 2) Добавляем боту поддержку команд и фильтры сообщений

25.08.2020 10:05:02 | Автор: admin

В предыдущей публикации мы разобрались как создать бота, инициализировали экземпляр класса Bot и ознакомились с методами отправки сообщений с его помощью.


В этой статье я продолжаю данную тему, поэтому приступать к чтению данной статьи я рекомендую только после прочтения первой части.


В этот раз мы разберёмся как оживить нашего бота и добавим ему поддержку команд, а также познакомимся с классом Updater.


В ходе статьи мы напишем нескольких простых ботов, последний будет по заданной дате и коду страны определять является ли день в данной стране выходным или рабочим согласно производственного календаря. Но, как и прежде цель статьи ознакомить вас с интерфейсом пакета telegram.bot для решения ваших собственных задач.



Все статьи из серии "Пишем telegram бота на языке R"


  1. Создаём бота, и отправляем с его помощью сообщения в telegram
  2. Добавляем боту поддержку команд и фильтры сообщений

Содержание


  1. Класс Updater
  2. Handlers обработчики
  3. Добавляем первую команду боту, обработчик команд
  4. Обработчик текстовых сообщений и фильтры
  5. Добавление команд с параметрами
  6. Запускаем бота в фоновом режиме
  7. Заключение

Класс Updater


Updater это класс, который упрощает вам разработку телеграм бота, и использует под капотом класс Dispetcher. Назначение класса Updater заключается в том, что бы получить обновления от бота (в предыдущей статье мы использовали для этой цели метод getUpdates()), и передать их далее в Dispetcher.


В свою очередь Dispetcher содержит в себе созданные вами обработчики, т.е. объекты класса Handler.


Handlers обработчики


С помощью обработчиков вы добавляете в Dispetcher реакции бота на различные события. На момент написания статьи в telegram.bot добавлены следующие типы обработчиков:


  • MessageHandler Обработчик сообщений
  • CommandHandler Обработчик команд
  • CallbackQueryHandler Обработчик данных отправляемых из Inline клавиатур
  • ErrorHandler Обработчик ошибок при запросе обновлений от бота

Добавляем первую команду боту, обработчик команд


Если вы никогда ранее не использовали ботов, и не в курсе, что такое команда, то команды боту необходимо отправлять с помощью прямого слеша / в качестве префикса.


Начнём мы с простых команд, т.е. научим нашего бота здороваться по команде /hi.


Код 1: Учим бота здороваться
library(telegram.bot)# создаём экземпляр класса Updaterupdater <- Updater('ТОКЕН ВАШЕГО БОТА')# Пишем метод для приветсвияsay_hello <- function(bot, update) {  # Имя пользователя с которым надо поздароваться  user_name <- update$message$from$first_name  # Отправка приветственного сообщения  bot$sendMessage(update$message$chat_id,                   text = paste0("Моё почтение, ", user_name, "!"),                   parse_mode = "Markdown")}# создаём обработчик hi_hendler <- CommandHandler('hi', say_hello)# добаляем обработчик в диспетчерupdater <- updater + hi_hendler# запускаем ботаupdater$start_polling()

Запустите приведённый выше пример кода, предварительно заменив 'ТОКЕН ВАШЕГО БОТА' на реальный токен, который вы получили при создании бота через BotFather (о создании бота я рассказывал в первой статье).

Метод start_polling() класса Updater, который используется в конце кода, запускает бесконечный цикл запроса и обработки обновлений от бота.


Теперь откроем телеграм, и напишем нашему боту первую команду /hi.



Теперь наш бот понимает команду /hi, и умеет с нами здороваться.


Схематически процесс построения такого простейшего бота можно изобразить следующим образом.



  1. Создаём экземпляр класса Updater;
  2. Создаём методы, т.е. функции которые будет выполнять наш бот. В примере кода это функция say_hello(). Функции, которые вами будут использоваться как методы бота должны иметь два обязательных аргумента bot и update, и один необязательный args. Аргумент bot, это и есть ваш бот, с его помощью вы можете отвечать на сообщения, отправлять сообщения, или использовать любые другие доступные боту методы. Аргумент update это то, что бот получил от пользователя, по сути, то что в первой статье мы получали методом getUpdates(). Аргумент args позволяет вам обрабатывать дополнительные данные отправленные пользователем вместе с командой, к этой теме мы ещё вернёмся немного позже;
  3. Создаём обработчики, т.е. связываем какие-то действия пользователя с созданными на прошлом шаге методами. По сути обработчик это триггер, событие которое вызывает какую-то функцию бота. В нашем примере таким триггером является отправка команды /hi, и реализуется командой hi_hendler <- CommandHandler('hi', say_hello). Первый аргумент функции CommandHandler() позволяет вам задать команду, в нашем случае hi, на которую будет реагировать бот. Второй аргумент позволяет указать метод бота, мы будем вызывать метод say_hello, который будет выполняться если пользователь вызвал указанную в первом аргументе команду;
  4. Далее добавляем созданный обработчик в диспетчер нашего экземпляра класса Updater. Добавлять обработчики можно несколькими способами, в примере выше я использовал простейший, с помощью знака +, т.е. updater <- updater + hi_hendler. То же самое можно сделать с помощью метода add_handler(), который относится к классу Dispatcher, найти этот метод можно так: updater$dispatcher$add_handler();
  5. Запускаем бота с помощью команды start_polling().

Обработчик текстовых сообщений и фильтры


Как отправлять боту команды мы разобрались, но иногда нам требуется, что бы бот реагировал не только на команды, но и на какие-то обычные, текстовые сообщения. Для этого необходимо использовать обработчики сообщений MessageHandler.


Обычный MessageHandler будет реагировать на абсолютно все входящие сообщения. Поэтому зачастую обработчики сообщений используются вместе с фильтрами. Давайте научим бота здороваться не только по команде /hi, но и всегда, когда в сообщении отправленном боту встречается одно из следующих слов: привет, здравствуй, салют, хай, бонжур.


Пока мы не будем писать какие-то новые методы, т.к. у нас уже есть метод с помощью которого бот с нами здоровается. От нас требуется только создать нужный фильтр и обработчик сообщений.


Код 2: Добавляем обработчик текстовых сообщений и фильтр
library(telegram.bot)# создаём экземпляр класса Updaterupdater <- Updater('ТОКЕН ВАШЕГО БОТА')# Пишем метод для приветсвия## команда приветвияsay_hello <- function(bot, update) {  # Имя пользователя с которым надо поздароваться  user_name <- update$message$from$first_name  # Отправляем приветсвенное сообщение  bot$sendMessage(update$message$chat_id,                   text = paste0("Моё почтение, ", user_name, "!"),                  parse_mode = "Markdown",                  reply_to_message_id = update$message$message_id)}# создаём фильтрыMessageFilters$hi <- BaseFilter(function(message) {  # проверяем, встречается ли в тексте сообщения слова: привет, здравствуй, салют, хай, бонжур  grepl(x           = message$text,         pattern     = 'привет|здравствуй|салют|хай|бонжур',        ignore.case = TRUE)  })# создаём обработчик hi_hendler <- CommandHandler('hi', say_hello) # обработчик команды hihi_txt_hnd <- MessageHandler(say_hello, filters = MessageFilters$hi)# добаляем обработчики в диспетчерupdater <- updater +              hi_hendler +             hi_txt_hnd# запускаем ботаupdater$start_polling()

Запустите приведённый выше пример кода, предварительно заменив 'ТОКЕН ВАШЕГО БОТА' на реальный токен, который вы получили при создании бота через BotFather (о создании бота я рассказывал в первой статье).

Теперь попробуем отправить боту несколько сообщений, в которых будут встречаться перечисленные ранее слова приветствия:


Итак, в первую очередь мы научили бота не просто здороваться, а отвечать на приветствие. Сделали мы это с помощью аргумента reply_to_message_id, который доступен в методе sendMessage(), в который необходимо передать id сообщения на которое требуется ответить. Получить id сообщения можно вот так: update$message$message_id.


Но главное, что мы сделали добавили боту фильтр с помощью функции BaseFilter():


# создаём фильтрыMessageFilters$hi <- BaseFilter(   # анонимная фильтрующая функция  function(message) {    # проверяем, встречается ли в тексте сообщения слова приветствия    grepl(x           = message$text,           pattern     = 'привет|здравствуй|салют|хай|бонжур',          ignore.case = TRUE)  })

Как вы могли заметить, фильтры необходимо добавлять в объект MessageFilters, в котором изначально уже есть небольшой набор готовых фильтров. В нашем примере в объект MessageFilters мы добавили элемент hi, это новый фильтр.


В функцию BaseFilter() вам необходимо передать фильтрующую функцию. По сути, фильтр это просто функция, которая получает экземпляр сообщения и возвращает TRUE или FALSE. В нашем примере, мы написали простейшую функцию, которая с помощью базовой функции grepl() проверяет текст сообщения, и если он соответствует регулярному выражению привет|здравствуй|салют|хай|бонжур возвращает TRUE.


Далее мы создаём обработчик сообщений hi_txt_hnd <- MessageHandler(say_hello, filters = MessageFilters$hi). Первый аргумент функции MessageHandler() метод, который будет вызывать обработчик, а второй аргумент это фильтр по которому он будет вызываться. В нашем случае это созданный нами фильтр MessageFilters$hi.


Ну и в итоге, мы добавляем в диспетчер созданный только, что обработчик hi_txt_hnd.


updater <- updater +              hi_hendler +             hi_txt_hnd

Как я уже писал выше, в пакете telegram.bot и объекте MessageFilters уже есть набор встроенных фильтров, которые вы можете использовать:


  • all Все сообщения
  • text Текстовые сообщения
  • command Команды, т.е. сообщения которые начинаются на /
  • reply Сообщения, которые являются ответом на другое сообщение
  • audio Сообщения в которых содержится аудио файл
  • document Сообщения с отправленным документом
  • photo Сообщения с отправленными изображениями
  • sticker Сообщения с отправленным стикером
  • video Сообщения с видео
  • voice Голосовые сообщения
  • contact Сообщения в которых содержится контант телеграм пользователя
  • location Сообщения с геолокацией
  • venue Пересылаемые сообщения
  • game Игры

Если вы хотите совместить некоторые фильтры в одном обработчике просто используйте знак | в качестве логического ИЛИ, и знак & в качестве логического И. Например, если вы хотите что бы бот вызывал один и тот же метод когда он получает видео, изображение или документ используйте следующий пример создания обработчика сообщений:


handler <- MessageHandler(callback,   MessageFilters$video | MessageFilters$photo | MessageFilters$document)

Добавление команд с параметрами


Мы уже знаем, что такое команды, как их создавать и как заставить бота выполнить нужную команду. Но в некоторых случаях помимо названия команды, нам необходимо передать некоторые данные для её выполнения.


Ниже пример бота, который по заданной дате и стране возвращает вам тип дня из производственного календаря.


Приведённый ниже бот использует API производственного календаря isdayoff.ru.


Код 3: Бот, который сообщает по дате и стране
library(telegram.bot)# создаём экземпляр класса Updaterupdater <- Updater('1165649194:AAFkDqIzQ6Wq5GV0YU7PmEZcv1gmWIFIB_8')# Пишем метод для приветсвия## команда приветвияcheck_date <- function(bot, update, args) {  # входящие данные  day     <- args[1]  # дата  country <- args[2]  # страна  # проверка введённых параметров  if ( !grepl('\\d{4}-\\d{2}-\\d{2}', day) ) {    # Send Custom Keyboard    bot$sendMessage(update$message$chat_id,                     text = paste0(day, " - некорреткная дата, введите дату в формате ГГГГ-ММ-ДД"),                    parse_mode = "Markdown")  } else {    day <- as.Date(day)    # переводим в формат POSIXtl    y <- format(day, "%Y")    m <- format(day, "%m")    d <- format(day, "%d")  }  # страна для проверки  ## проверяем задана ли страна  ## если не задана устанавливаем ru  if ( ! country %in% c('ru', 'ua', 'by', 'kz', 'us') ) {    # Send Custom Keyboard    bot$sendMessage(update$message$chat_id,                     text = paste0(country, " - некорретктный код страны, возможнные значения: ru, by, kz, ua, us. Запрошены данные по России."),                    parse_mode = "Markdown")    country <- 'ru'  }  # запрос данных из API  # компоновка HTTP запроса  url <- paste0("https://isdayoff.ru/api/getdata?",                "year=",  y, "&",                "month=", m, "&",                "day=",   d, "&",                "cc=",    country, "&",                "pre=1&",                "covid=1")  # получаем ответ  res <- readLines(url)  # интрепретация ответа  out <- switch(res,                 "0"   = "Рабочий день",                "1"   = "Нерабочий день",                "2"   = "Сокращённый рабочий день",                "4"   = "covid-19",                "100" = "Ошибка в дате",                "101" = "Данные не найдены",                "199" = "Ошибка сервиса")  # отправляем сообщение  bot$sendMessage(update$message$chat_id,                   text = paste0(day, " - ", out),                  parse_mode = "Markdown")}# создаём обработчик date_hendler <- CommandHandler('check_date', check_date, pass_args = TRUE)# добаляем обработчик в диспетчерupdater <- updater + date_hendler# запускаем ботаupdater$start_polling()

Запустите приведённый выше пример кода, предварительно заменив 'ТОКЕН ВАШЕГО БОТА' на реальный токен, который вы получили при создании бота через BotFather (о создании бота я рассказывал в первой статье).

Мы создали бота, который в арсенале имеет всего один метод check_date, данный метод вызывается одноимённой командой.


Но, помимо имени команды, данный метод ждёт от вас введения двух параметров, код страны и дату. Далее бот проверяется, является ли заданный день в указанной стране выходным, сокращённым или рабочим согласно официального производственного календаря.


Что бы создаваемый нами метод принимал дополнительные параметры вместе с командой, используйте аргумент pass_args = TRUE в функции CommandHandler(), и при создании метода, помимо обязательных аргументов bot, update создайте опциональный args. Созданный таким образом метод будет принимать параметры, которые вы передаёте боту после названия команды. Параметры необходимо между собой разделять пробелом, в метод они поступят в виде текстового вектора.


Давайте запустим, и протестируем нашего бота.



Запускаем бота в фоновом режиме


Последний шаг который нам осталось выполнить запустить бота в фоновом режиме.


Для этого следуйте по описанному ниже алгоритму:


  1. Сохраните код бота в файл с расширением R. При работе в RStudio это делается через меню File, командой Save As....
  2. Добавьте путь к папке bin, которая в свою очередь находится в папке в которую вы установили язык R в переменную Path, инструкция тут.
  3. Создайте обычный текстовый файл, в котором пропишите 1 строку: R CMD BATCH C:\Users\Alsey\Documents\my_bot.R. Вместо C:\Users\Alsey\Documents\my_bot.R пропишите путь к своему скрипту бота. При этом важно, что бы в пути не встречалась кириллица и пробелы, т.к. это может вызвать проблемы при запуске бота. Сохраните его, и замените его расширение с txt на bat.
  4. Откройте планировщик заданий Windows, есть множество способов это сделать, например откройте любую папку и в адресс введите %windir%\system32\taskschd.msc /s. Другие способы запуска можно найти тут.
  5. В верхнем правом меню планировщика нажмите "Создать задачу...".
  6. На вкладке "Общие" задайте произвольное имя вашей задаче, и переключатель перевидите в состояние "Выполнять для всех пользователей".
  7. Перейдите на вкладку "Действия", нажмите "Создать". В поле "Программа или сценарий" нажмите "Обзор", найдите созданный на втором шаге bat файл, и нажмите ОК.
  8. Жмём ОК, при необходимости вводим пароль от вашей учётной записи операционной системы.
  9. Находим в планировщике созданную задачу, выделяем и в нижнем правом углу жмём кнопку "Выполнить".

Наш бот запущен в фоновом режиме, и будет работать до тех пор, пока вы не остановите задачу, или не выключите ваш ПК или сервер на котором его запустили.


Заключение


В этой статье мы разобрались как написать полноценного бота, который не только умеет отправлять сообщения, но и реагировать на входящие сообщения и команды. Полученных знананий уже достаточно для решения большинства ваших задач.


В следующей статье речь пойдёт о том, как добавить боту клавиатуру, для более удобной работы.

Подробнее..

Пишем telegram бота на языке R (часть 3) Как добавить боту поддержку клавиатуры

08.09.2020 10:16:23 | Автор: admin

Это третья статья из серии "Пишем telegram бота на языке R". В предыдущих публикациях мы научились создавать телеграм бота, отправлять через него сообщения, добавили боту команды и фильтры сообщений. Поэтому перед тем как приступить к чтению данной статьи я крайне рекомендую ознакомиться с предыдущими, т.к. тут я уже не буду останавливать на описанных ранее основах ботостроения.


В этой статье мы повысим юзабилити нашего бота за счёт добавления клавиатуры, которая сделает интерфейс бота интуитивно понятным, и простым в использовании.



Все статьи из серии "Пишем telegram бота на языке R"


  1. Создаём бота, и отправляем с его помощью сообщения в telegram
  2. Добавляем боту поддержку команд и фильтры сообщений
  3. Как добавить боту поддержку клавиатуры

Содержание


Если вы интересуетесь анализом данных возможно вам будут интересны мои telegram и youtube каналы. Большая часть контента которых посвящены языку R.


  1. Какие типы клавиатур поддерживает телеграм бот
  2. Reply клавиатура
  3. Inline клавиатура
    3.1. Пример простейшего бота с поддержкой InLine кнопок
    3.2. Пример бота, который сообщает текущую погоду по выбранному городу
    3.3. Пример бота, который выводит список самых свежих статей со ссылками по-указанному Хабу из habr.com
  4. Заключение

Какие типы клавиатур поддерживает телеграм бот


На момент написания статьи telegram.bot позволяет вам создать клавиатуры двух типов:


  • Reply Основная, обычная клавиатура, которая находится под панелью ввода текста сообщения. Такая клавиатура просто отправляет боту текстовое сообщение, и в качестве текста отправит тот текст, который написан на самой кнопке.
  • Inline Клавиатура привязанная к конкретному сообщению бота. Данная клавиатура отправляет боту данные, привязанные к нажатой кнопке, эти данные могут отличаться от текста, написанного на самой кнопке. И обрабатываются такие кнопки через CallbackQueryHandler.

Для того, что бы бот открыл клавиатуру необходимо при отправке сообщения через метод sendMessage(), передать созданную ранее клавиатуру в аргумент reply_markup.


Ниже мы разберём несколько примеров.


Reply клавиатура


Как я уже писал выше, это основная клавиатура управления ботом.


Пример создания Reply клавиатуры из официальной справки
bot <- Bot(token = "TOKEN")chat_id <- "CHAT_ID"# Create Custom Keyboardtext <- "Aren't those custom keyboards cool?"RKM <- ReplyKeyboardMarkup(  keyboard = list(    list(KeyboardButton("Yes, they certainly are!")),    list(KeyboardButton("I'm not quite sure")),    list(KeyboardButton("No..."))  ),  resize_keyboard = FALSE,  one_time_keyboard = TRUE)# Send Custom Keyboardbot$sendMessage(chat_id, text, reply_markup = RKM)

Выше приведён пример из официальной справки пакета telegram.bot. Для создания клавиатуры используется функция ReplyKeyboardMarkup(), которая в свою очередь принимает список списков кнопок, которые создаются функцией KeyboardButton().


Почему в ReplyKeyboardMarkup() необходимо передавать не просто список, а список списков? Дело в том, что вы передаёте основной список, и в нём отдельными списками вы задаёте каждый ряд кнопок, т.к. в один ряд можно расположить несколько кнопок.


Аргумент resize_keyboard позволяет автоматически подбирать оптимальный размер кнопок клавиатуры, а аргумент one_time_keyboard позволяет скрывать клавиатуру после каждого нажатия на кнопку.


Давайте напишем простейшего бота, у которого будет 3 кнопки:


  • Чат ID Запросить чат ID диалога с ботом
  • Моё имя Запросить своё имя
  • Мой логин Запросить своё имя пользователя в телеграм

Код 1: Простой бот с Reply клавиатурой
library(telegram.bot)# создаём экземпляр класса Updaterupdater <- Updater('ТОКЕН ВАШЕГО БОТА')# создаём методы## метод для запуска клавиатурыstart <- function(bot, update) {  # создаём клавиатуру  RKM <- ReplyKeyboardMarkup(    keyboard = list(      list(KeyboardButton("Чат ID")),      list(KeyboardButton("Моё имя")),      list(KeyboardButton("Мой логин"))    ),    resize_keyboard = FALSE,    one_time_keyboard = TRUE  )  # отправляем клавиатуру  bot$sendMessage(update$message$chat_id,                  text = 'Выберите команду',                   reply_markup = RKM)}## метод возвразающий id чатаchat_id <- function(bot, update) {  bot$sendMessage(update$message$chat_id,                   text = paste0("Чат id этого диалога: ", update$message$chat_id),                  parse_mode = "Markdown")}## метод возвращающий имяmy_name <- function(bot, update) {  bot$sendMessage(update$message$chat_id,                   text = paste0("Вас зовут ", update$message$from$first_name),                  parse_mode = "Markdown")}## метод возвращающий логинmy_username <- function(bot, update) {  bot$sendMessage(update$message$chat_id,                   text = paste0("Ваш логин ", update$message$from$username),                  parse_mode = "Markdown")}# создаём фильтры## сообщения с текстом Чат IDMessageFilters$chat_id <- BaseFilter(function(message) {  # проверяем текст сообщения  message$text == "Чат ID"})## сообщения с текстом Моё имяMessageFilters$name <- BaseFilter(function(message) {  # проверяем текст сообщения  message$text == "Моё имя"})## сообщения с текстом Мой логинMessageFilters$username <- BaseFilter(function(message) {  # проверяем текст сообщения  message$text == "Мой логин")# создаём обработчикиh_start    <- CommandHandler('start', start)h_chat_id  <- MessageHandler(chat_id, filters = MessageFilters$chat_id)h_name     <- MessageHandler(my_name, filters = MessageFilters$name)h_username <- MessageHandler(my_username, filters = MessageFilters$username)# добавляем обработчики в диспетчерupdater <- updater +             h_start +            h_chat_id +            h_name +            h_username# запускаем бота updater$start_polling()

Запустите приведённый выше пример кода, предварительно заменив 'ТОКЕН ВАШЕГО БОТА' на реальный токен, который вы получили при создании бота через BotFather (о создании бота я рассказывал в первой статье).

После запуска задайте боту команду /start, т.к. именно её мы определили для запуска клавиатуры.



Если на данный момент вам сложно разобрать приведённый пример кода, с созданием методов, фильтров и обработчиков, то следует вернуться к предыдущей статье, в которой я подробно всё это описал.


Мы создали 4 метода:


  • start Запуск клавиатуры
  • chat_id Запрос идентификатора чата
  • my_name Запрос своего имени
  • my_username Запрос своего логина

В объект MessageFilters добавили 3 фильтра сообщений, по их тексту:


  • chat_id Сообщения с текстом "Чат ID"
  • name Сообщения с текстом "Моё имя"
  • username Сообщения с текстом "Мой логин"

И создали 4 обработчика, которые по заданным командам и фильтрам будут выполнять указанные методы.


# создаём обработчикиh_start    <- CommandHandler('start', start)h_chat_id  <- MessageHandler(chat_id, filters = MessageFilters$chat_id)h_name     <- MessageHandler(my_name, filters = MessageFilters$name)h_username <- MessageHandler(my_username, filters = MessageFilters$username)

Сама клавиатура создаётся внутри метода start() командой ReplyKeyboardMarkup().


RKM <- ReplyKeyboardMarkup(    keyboard = list(      list(KeyboardButton("Чат ID")),      list(KeyboardButton("Моё имя")),      list(KeyboardButton("Мой логин"))    ),    resize_keyboard = FALSE,    one_time_keyboard = TRUE)

В нашем случае все кнопки мы расположили друг под другом, но мы можем расположить их в один ряд, внеся изменения в список списков кнопок. Т.к. один ряд внутри клавиатуры создаётся через вложенный список кнопок, то для того, что бы вывести наши кнопки в один ряд надо переписать часть кода по построению клавиатуры вот так:


RKM <- ReplyKeyboardMarkup(    keyboard = list(      list(          KeyboardButton("Чат ID"),          KeyboardButton("Моё имя"),          KeyboardButton("Мой логин")     )    ),    resize_keyboard = FALSE,    one_time_keyboard = TRUE)


Отправляется клавиатура в чат методом sendMessage(), в аргументе reply_markup.


  bot$sendMessage(update$message$chat_id,                  text = 'Выберите команду',                   reply_markup = RKM)

Inline клавиатура


Как я уже писал выше, Inline клавиатура привязана к конкретному сообщению. С ней работать несколько сложнее чем с основной клавиатурой.


Изначально вам необходимо добавить боту метод, для вызова Inline клавиатуры.


Для ответа на нажатие Inline кнопки также можно использовать метод бота answerCallbackQuery(), который может вывести уведомление в интерфейсе telegram, пользователю нажавшему Inline кнопку.


Данные отправленные с Inline кнопки не являются текстом, поэтому для их обработки необходимо создать специальный обработчик с помощью команды CallbackQueryHandler().


Код построения Inline клавиатуры который приводится в официальной справке пакета telegram.bot.


Код построения Inline клавиатуры из официальной справки
# Initialize botbot <- Bot(token = "TOKEN")chat_id <- "CHAT_ID"# Create Inline Keyboardtext <- "Could you type their phone number, please?"IKM <- InlineKeyboardMarkup(  inline_keyboard = list(    list(      InlineKeyboardButton(1),      InlineKeyboardButton(2),      InlineKeyboardButton(3)    ),    list(      InlineKeyboardButton(4),      InlineKeyboardButton(5),      InlineKeyboardButton(6)    ),    list(      InlineKeyboardButton(7),      InlineKeyboardButton(8),      InlineKeyboardButton(9)    ),    list(      InlineKeyboardButton("*"),      InlineKeyboardButton(0),      InlineKeyboardButton("#")    )  ))# Send Inline Keyboardbot$sendMessage(chat_id, text, reply_markup = IKM)

Строить Inline клавиатуру необходимо с помощью команды InlineKeyboardMarkup(), по такому же принципу, как и Reply клавиатуру. В InlineKeyboardMarkup() необходимо передать список, списков Inline кнопок, каждая отдельная кнопка создаётся функцией InlineKeyboardButton().


Inline кнопка может либо передавать боту какие-то данные с помощью аргумента callback_data, либо открывать какую-либо HTML страницу, заданную с помощью аргумента url.


В результате будет список, в котором каждый элемент так же является списком Inline кнопок, которые необходимо объединить в один ряд.


Далее мы рассмотрим несколько примеров ботов с Inline кнопками.


Пример простейшего бота с поддержкой InLine кнопок


Для начала мы напишем бота для экспресс тестирования на covid-19. По команде /test, он будет отправлять вам клавиатуру с двумя кнопками, в зависимости от нажатой кнопки он будет присылать вам сообщение с результатами вашего тестирования.


Код 2: Простейший бот с Inline клавиатурой
library(telegram.bot)# создаём экземпляр класса Updaterupdater <- Updater('ТОКЕН ВАШЕГО БОТА')# метод для отправки InLine клавиатурыtest <- function(bot, update) {  # создаём InLine клавиатуру  IKM <- InlineKeyboardMarkup(    inline_keyboard = list(      list(        InlineKeyboardButton("Да", callback_data = 'yes'),        InlineKeyboardButton("Нет", callback_data = 'no')      )    )  )  # Отправляем клавиатуру в чат  bot$sendMessage(update$message$chat_id,                   text = "Вы болете коронавирусом?",                   reply_markup = IKM)}# метод для обработки нажатия кнопкиanswer_cb <- function(bot, update) {  # полученные данные с кнопки  data <- update$callback_query$data  # получаем имя пользователя, нажавшего кнопку  uname <- update$effective_user()$first_name  # обработка результата  if ( data == 'no' ) {    msg <- paste0(uname, ", поздравляю, ваш тест на covid-19 отрицательный.")  } else {    msg <- paste0(uname, ", к сожалени ваш тест на covid-19 положительный.")  }  # Отправка сообщения  bot$sendMessage(chat_id = update$from_chat_id(),                  text = msg)  # сообщаем боту, что запрос с кнопки принят  bot$answerCallbackQuery(callback_query_id = update$callback_query$id) }# создаём обработчикиinline_h      <- CommandHandler('test', test)query_handler <- CallbackQueryHandler(answer_cb)# добавляем обработчики в диспетчерupdater <- updater + inline_h + query_handler# запускаем ботаupdater$start_polling()

Запустите приведённый выше пример кода, предварительно заменив 'ТОКЕН ВАШЕГО БОТА' на реальный токен, который вы получили при создании бота через BotFather (о создании бота я рассказывал в первой статье).

Результат:


Мы создали два метода:


  • test Для отправки в чат Inline клавиатуры
  • answer_cb Для обработки отправленных с клавиатуры данных.

Данные, которые будут отправлены с каждой кнопки задаются в аргументе callback_data, при создании кнопки. Получить отправленные с кнопки данные можно с помощью конструкции update$callback_query$data, внутри метода answer_cb.


Что бы бот реагировал на Inline клавиатуру, метод answer_cb обрабатывается специальным обработчиком: CallbackQueryHandler(answer_cb). Который запускает указанный метод по нажатию Inline кнопки. Обработчик CallbackQueryHandler принимает два аргумента:


  • callback Метод который необходимо запустить
  • pattern Фильтр по данным, которые привязаны к кнопке с помощью аргумента callback_data.

Соответвенно с помощью аргумента pattern мы можем под нажатие каждой кнопки написать отдельный метод:


Код 3: Разделяем методы под каждую Inline кнопку
library(telegram.bot)# создаём экземпляр класса Updaterupdater <- Updater('ТОКЕН ВАШЕГО БОТА')# метод для отправки InLine клавиатурыtest <- function(bot, update) {    # создаём InLine клавиатуру  IKM <- InlineKeyboardMarkup(    inline_keyboard = list(      list(        InlineKeyboardButton("Да", callback_data = 'yes'),        InlineKeyboardButton("Нет", callback_data = 'no')      )    )  )  # Отправляем клавиатуру в чат  bot$sendMessage(update$message$chat_id,                   text = "Вы болете коронавирусом?",                   reply_markup = IKM)}# метод для обработки нажатия кнопки Даanswer_cb_yes <- function(bot, update) {  # получаем имя пользователя, нажавшего кнопку  uname <- update$effective_user()$first_name  # обработка результата  msg <- paste0(uname, ", к сожалени ваш текст на covid-19 положительный.")  # Отправка сообщения  bot$sendMessage(chat_id = update$from_chat_id(),                  text = msg)  # сообщаем боту, что запрос с кнопки принят  bot$answerCallbackQuery(callback_query_id = update$callback_query$id) }# метод для обработки нажатия кнопки Нетanswer_cb_no <- function(bot, update) {  # получаем имя пользователя, нажавшего кнопку  uname <- update$effective_user()$first_name  msg <- paste0(uname, ", поздравляю, ваш текст на covid-19 отрицательный.")  # Отправка сообщения  bot$sendMessage(chat_id = update$from_chat_id(),                  text = msg)  # сообщаем боту, что запрос с кнопки принят  bot$answerCallbackQuery(callback_query_id = update$callback_query$id) }# создаём обработчикиinline_h          <- CommandHandler('test', test)query_handler_yes <- CallbackQueryHandler(answer_cb_yes, pattern = 'yes')query_handler_no  <- CallbackQueryHandler(answer_cb_no, pattern = 'no')# добавляем обработчики в диспетчерupdater <- updater +             inline_h +             query_handler_yes +            query_handler_no# запускаем ботаupdater$start_polling()

Запустите приведённый выше пример кода, предварительно заменив 'ТОКЕН ВАШЕГО БОТА' на реальный токен, который вы получили при создании бота через BotFather (о создании бота я рассказывал в первой статье).

Теперь мы написали 2 отдельных метода, т.е. по одному методу, под нажатие каждой кнопки, и использовали аргумент pattern, при создании их обработчиков:


query_handler_yes <- CallbackQueryHandler(answer_cb_yes, pattern = 'yes')query_handler_no  <- CallbackQueryHandler(answer_cb_no, pattern = 'no')

Заканчивается код метода answer_cb командой bot$answerCallbackQuery(callback_query_id = update$callback_query$id), которая сообщает боту, что данные с inline клавиатуры получены.


Пример бота, который сообщает текущую погоду по выбранному городу


Давайте попробуем написать бота, который запрашивает данные о погоде.


Логика его работы будет следующая. Изначально командой /start вы вызываете основную клавиатуру, в которой присутствует всего одна кнопка "Погода". Нажав на эту кнопку вы получаете сообщение с Inline клавиатурой, для выбора города, по которому требуется узнать текущую погоду. Выбираете один из городов, и получаете текущую погоду.


В этом примере кода мы будем использовать несколько дополнительных пакетов:


  • httr пакет для работы с HTTP запросами, на основе которых построена работа с любым API. В нашем случае мы будем использовать бесплатный API openweathermap.org.
  • stringr пакет для работы с текстом, в нашем случае мы будем его использовать для формирования сообщения о погоде в выбранном городе.

Код 4: Бот, который сообщает текущую погоду по выбранному городу
library(telegram.bot)library(httr)library(stringr)# создаём экземпляр класса Updaterupdater <- Updater('ТОКЕН ВАШЕГО БОТА')# создаём методы## метод для запуска основной клавиатурыstart <- function(bot, update) {  # создаём клавиатуру  RKM <- ReplyKeyboardMarkup(    keyboard = list(      list(        KeyboardButton("Погода")      )    ),    resize_keyboard = TRUE,    one_time_keyboard = TRUE  )  # отправляем клавиатуру  bot$sendMessage(update$message$chat_id,                  text = 'Выберите команду',                   reply_markup = RKM)}## Метод вызова Inine клавиатурыweather <- function(bot, update) {  IKM <- InlineKeyboardMarkup(    inline_keyboard = list(      list(        InlineKeyboardButton(text = 'Москва', callback_data = 'New York,us'),        InlineKeyboardButton(text = 'Санкт-Петербург', callback_data = 'Saint Petersburg'),        InlineKeyboardButton(text = 'Нью-Йорк', callback_data = 'New York')      ),      list(        InlineKeyboardButton(text = 'Екатеринбург', callback_data = 'Yekaterinburg,ru'),        InlineKeyboardButton(text = 'Берлин', callback_data = 'Berlin,de'),        InlineKeyboardButton(text = 'Париж', callback_data = 'Paris,fr')      ),      list(        InlineKeyboardButton(text = 'Рим', callback_data = 'Rome,it'),        InlineKeyboardButton(text = 'Одесса', callback_data = 'Odessa,ua'),        InlineKeyboardButton(text = 'Киев', callback_data = 'Kyiv,fr')      ),      list(        InlineKeyboardButton(text = 'Токио', callback_data = 'Tokyo'),        InlineKeyboardButton(text = 'Амстердам', callback_data = 'Amsterdam,nl'),        InlineKeyboardButton(text = 'Вашингтон', callback_data = 'Washington,us')      )    )  )  # Send Inline Keyboard  bot$sendMessage(chat_id = update$message$chat_id,                   text = "Выберите город",                   reply_markup = IKM)}# метод для сообщения погодыanswer_cb <- function(bot, update) {  # получаем из сообщения город  city <- update$callback_query$data  # отправляем запрос  ans <- GET('https://api.openweathermap.org/data/2.5/weather',              query = list(q     = city,                          lang  = 'ru',                          units = 'metric',                          appid = '4776568ccea136ffe4cda9f1969af340'))   # парсим ответ  result <- content(ans)  # формируем сообщение  msg <- str_glue("{result$name} погода:\n",                  "Текущая температура: {result$main$temp}\n",                  "Скорость ветра: {result$wind$speed}\n",                  "Описание: {result$weather[[1]]$description}")  # отправляем информацию о погоде  bot$sendMessage(chat_id = update$from_chat_id(),                  text    = msg)  bot$answerCallbackQuery(callback_query_id = update$callback_query$id) }# создаём фильтры## сообщения с текстом ПогодаMessageFilters$weather <- BaseFilter(function(message) {  # проверяем текст сообщения  message$text == "Погода"})# создаём обработчикиh_start         <- CommandHandler('start', start)h_weather       <- MessageHandler(weather, filters = MessageFilters$weather)h_query_handler <- CallbackQueryHandler(answer_cb)# добавляем обработчики в диспетчерupdater <- updater +               h_start +              h_weather +              h_query_handler# запускаем ботаupdater$start_polling()

Запустите приведённый выше пример кода, предварительно заменив 'ТОКЕН ВАШЕГО БОТА' на реальный токен, который вы получили при создании бота через BotFather (о создании бота я рассказывал в первой статье).

В результате наш бот будет работать примерно так:


Схематически данного бота можно изобрать вот так:


Мы создали 3 метода, доступные внутри нашего погодного бота:


  • start Запуск основной клавиатуры бота
  • weather Запуск Inline клавиатуры для выбора города
  • answer_cb Основной метод, который по заданному городу запрашивает в API погоду, и отправляет её в чат.

Метод start у нас запускается командой /start, что реализовано обработчиком CommandHandler('start', start).


Для запуска метода weather мы создали одноимённый фильтр:


# создаём фильтры## сообщения с текстом ПогодаMessageFilters$weather <- BaseFilter(function(message) {  # проверяем текст сообщения  message$text == "Погода"})

И вызываем этот метод следующим обработчиком сообщений: MessageHandler(weather, filters = MessageFilters$weather).


И в конце концов, основной наш метод answer_cb реагирует на нажатие Inline кнопок, что реализовано специальным обработчиком: CallbackQueryHandler(answer_cb).


Внутри метода answer_cb, мы считываем отправленные с клавиатуры данные и записываем их в переменную city: city <- update$callback_query$data. После чего запрашиваем из API данные о погоде, формируем и отправляем сообщение, и в конце концов используем метод answerCallbackQuery для того, что бы сообщить боту, о том, что мы обработали нажатие Inline кнопки.


Пример бота, который выводит список самых свежих статей со ссылками по-указанному Хабу из habr.com.


Данного бота я привожу для того, что бы показать вам, как вывести Inline кнопки которые ведут на веб страницы.


Логика данного бота схожа с предыдущим, изначально мы запускаем основную клавиатуру командой /start. Далее бот даёт нам на выбор список из 6 хабов, мы выбираем интересующий нас хаб, и получаем 5 самых свежих публикаций из выбранного Хаба.


Как вы понимаете, в данном случае нам необходимо получить список статей, и для этого мы будем использовать специальный пакет habR, который позволяет запрашивать из хабры статьи и некоторую статистику по ним в R.


Установить пакет habR можно только из github, для чего вам понадобится дополнительный пакет devtools. Для установки воспользуйтесь приведённым ниже кодом.


install.packages('devtools')devtools::install_github('selesnow/habR')

Теперь рассмотрим код построения описанного выше бота:


Код 5: Бот который выводит список наиболее свежих статей по выбранному Хабу
library(telegram.bot)library(habR)# создаём экземпляр класса Updaterupdater <- Updater('ТОКЕН ВАШЕГО БОТА')# создаём методы## метод для запуска основной клавиатурыstart <- function(bot, update) {  # создаём клавиатуру  RKM <- ReplyKeyboardMarkup(    keyboard = list(      list(        KeyboardButton("Список статей")      )    ),    resize_keyboard = TRUE,    one_time_keyboard = TRUE  )  # отправляем клавиатуру  bot$sendMessage(update$message$chat_id,                  text = 'Выберите команду',                   reply_markup = RKM)}## Метод вызова Inine клавиатурыhabs <- function(bot, update) {  IKM <- InlineKeyboardMarkup(    inline_keyboard = list(      list(        InlineKeyboardButton(text = 'R', callback_data = 'R'),        InlineKeyboardButton(text = 'Data Mining', callback_data = 'data_mining'),        InlineKeyboardButton(text = 'Data Engineering', callback_data = 'data_engineering')      ),      list(        InlineKeyboardButton(text = 'Big Data', callback_data = 'bigdata'),        InlineKeyboardButton(text = 'Python', callback_data = 'python'),        InlineKeyboardButton(text = 'Визуализация данных', callback_data = 'data_visualization')      )    )  )  # Send Inline Keyboard  bot$sendMessage(chat_id = update$message$chat_id,                   text = "Выберите Хаб",                   reply_markup = IKM)}# метод для сообщения погодыanswer_cb <- function(bot, update) {  # получаем из сообщения город  hub <- update$callback_query$data  # сообщение о том, что данные по кнопке получены  bot$answerCallbackQuery(callback_query_id = update$callback_query$id,                           text = 'Подождите несколько минут, запрос обрабатывается')   # сообщение о том, что надо подождать пока бот получит данные  mid <- bot$sendMessage(chat_id = update$from_chat_id(),                         text    = "Подождите несколько минут пока, я соберу данные по выбранному Хабу")  # парсим Хабр  posts <- head(habr_hub_posts(hub, 1), 5)  # удаляем сообщение о том, что надо подождать  bot$deleteMessage(update$from_chat_id(), mid$message_id)   # формируем список кнопок  keys <- lapply(1:5, function(x) list(InlineKeyboardButton(posts$title[x], url = posts$link[x])))  # формируем клавиатуру  IKM <- InlineKeyboardMarkup(    inline_keyboard =  keys     )  # отправляем информацию о погоде  bot$sendMessage(chat_id = update$from_chat_id(),                  text    = paste0("5 наиболее свежих статей из Хаба ", hub),                  reply_markup = IKM)}# создаём фильтры## сообщения с текстом ПогодаMessageFilters$hubs <- BaseFilter(function(message) {  # проверяем текст сообщения  message$text == "Список статей"})# создаём обработчикиh_start         <- CommandHandler('start', start)h_hubs          <- MessageHandler(habs, filters = MessageFilters$hubs)h_query_handler <- CallbackQueryHandler(answer_cb)# добавляем обработчики в диспетчерupdater <- updater +   h_start +  h_hubs  +  h_query_handler# запускаем ботаupdater$start_polling()

Запустите приведённый выше пример кода, предварительно заменив 'ТОКЕН ВАШЕГО БОТА' на реальный токен, который вы получили при создании бота через BotFather (о создании бота я рассказывал в первой статье).

В итоге мы получим вот такой результат:


Список доступных для выбора Хабов мы вбили хардкодом, в методе habs:


## Метод вызова Inine клавиатурыhabs <- function(bot, update) {  IKM <- InlineKeyboardMarkup(    inline_keyboard = list(      list(        InlineKeyboardButton(text = 'R', callback_data = 'r'),        InlineKeyboardButton(text = 'Data Mining', callback_data = 'data_mining'),        InlineKeyboardButton(text = 'Data Engineering', callback_data = 'data_engineering')      ),      list(        InlineKeyboardButton(text = 'Big Data', callback_data = 'bigdata'),        InlineKeyboardButton(text = 'Python', callback_data = 'python'),        InlineKeyboardButton(text = 'Визуализация данных', callback_data = 'data_visualization')      )    )  )  # Send Inline Keyboard  bot$sendMessage(chat_id = update$message$chat_id,                   text = "Выберите Хаб",                   reply_markup = IKM)}

Список статей из указанного Хаба мы получаем командой habr_hub_posts(), из пакета habR. При этом указываем, что нам не требуется список статей за всё время, а только первая страница на которой располагаются 20 статей. Из полученной таблицы с помощью команды head() оставляем только 5 самых верхних, которые и являются самыми свежими статьями.


  # парсим Хабр  posts <- head(habr_hub_posts(hub, 1), 5)

Логика очень схожа с предыдущим ботом, но в данном случае Inline клавиатуру со списком статей мы генерируем динамически с помощью функции lapply().


  # формируем список кнопок  keys <- lapply(1:5, function(x) list(InlineKeyboardButton(posts$title[x], url = posts$link[x])))  # формируем клавиатуру  IKM <- InlineKeyboardMarkup(    inline_keyboard =  keys     )

В текст кнопки мы подставляем название статьи posts$title[x], а в аргумент url ссылку на статью: url = posts$link[x].


Далее, создаём фильтр, обработчики и запускаем нашего бота.


Заключение


Теперь написанные вами боты будут значительно удобней в работе, за счёт того, что управление ими будет осуществляться с клавиатуры, а не вводом команд. Как минимум при взаимодействии с ботом через смартфон клавиатура ощутимо упростит процесс его использования.


В следующей статье мы разберёмся как строить логический диалог с ботом, и работать с базами данных.

Подробнее..

Пишем telegram бота на языке R (часть 4) Построение последовательного, логического диалога с ботом

22.09.2020 10:23:41 | Автор: admin

Если вы уже ознакомились с предыдущими тремя статьями из данной серии, то вы уже умеете писать полноценных telegram ботов с клавиатурой.


В этой статье мы с вами научимся писать бота, который будет поддерживать последовательный диалог. Т.е. бот будет задавать вам вопросы, и ждать от вас ввода какой-либо информации. В зависимости от введённых вами данных бот будет выполнять некоторые действия.


Также в данной статье мы научимся использовать под капотом бота базы данных, в нашем примере это будет SQLite, но вы можете использовать любую другую СУБД. Более подробно о взаимодействии с базами данных на языке R я писал в этой статье.



Все статьи из серии "Пишем telegram бота на языке R"


  1. Создаём бота, и отправляем с его помощью сообщения в telegram
  2. Добавляем боту поддержку команд и фильтры сообщений
  3. Как добавить боту поддержку клавиатуры
  4. Построение последовательного, логического диалога с ботом

Содержание


Если вы интересуетесь анализом данных возможно вам будут интересны мои telegram и youtube каналы. Большая часть контента которых посвящены языку R.


  1. Введение
  2. Процесс построения бота
  3. Структура проекта бота
  4. Конфиг бота
  5. Создаём переменную среды
  6. Создаём базу данных
  7. Пишем функции для работы с базой данных
  8. Методы бота
  9. Фильтры сообщений
  10. Обработчики
  11. Код запуска бота
  12. Заключение

Введение


Для того, что бы бот мог запрашивать от вас данные, и ждать ввод какой-либо информации вам потребуется фиксировать текущее состояние диалога. Лучший способ это делать, использовать какую нибудь встраиваемую базу данных, например 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')# подключение к SQLitecon <- 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 dataget_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 dialogstart <- 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 statestate <- 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 statereset <- function(bot, update) {  set_state(chat_id = update$message$chat_id, state = 'start')}# enter usernameenter_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 ageenter_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 handlersstart_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 переводить чат в исходное состояние и начинать диалог заново.


Заключение


В этой статье мы разобрались как использовать внутри бота базы данных, и как строить последовательные логические диалоги за счёт фиксации состояния чата.


В данном случае мы рассмотрели самый примитивный пример, для того, что бы вам проще было понять идею построения таких ботов, на практике вы можете строить гораздо более сложные диалоги.


В следующей статье из этой серии мы научимся ограничивать пользователям бота права на использования различных его методов.

Подробнее..

Пишем telegram бота на языке R (часть 5) Управление правами пользователей бота

01.10.2020 10:13:11 | Автор: admin

В предыдущих статьях мы достаточно подробно разобрали тему ботостроения, от отправки первого сообщения до программирования логического диалога с ботом.


Это последняя статья из данной серии, в которой мы разберёмся с тем, как управлять правами использования отдельных методов бота на различных уровнях.



Все статьи из серии "Пишем telegram бота на языке R"


  1. Создаём бота, и отправляем с его помощью сообщения в telegram
  2. Добавляем боту поддержку команд и фильтры сообщений
  3. Как добавить боту поддержку клавиатуры
  4. Построение последовательного, логического диалога с ботом
  5. Управление правами пользователей бота

Содержание


Если вы интересуетесь анализом данных возможно вам будут интересны мои telegram и youtube каналы. Большая часть контента которых посвящены языку R.


  1. Введение
  2. Ограничиваем права пользователя с помощью фильтров сообщений
    2.1. Ограничиваем права на уровне имени пользователя
    2.2. Ограничиваем права на уровне чата
  3. Ограничиваем права пользователя внутри кода методов
    3.1. Ограничиваем права на уровне имени пользователя
    3.2. Ограничиваем права на уровне чата
  4. Заключение

Введение


Ваш бот может выполнять совершенно любые задачи, и автоматизировать как некоторые внутренние процессы, так и наладить коммуникации с клиентами.


Т.е. бот может использоваться в многопользовательском режиме. При этом, вам может понадобиться разграничить права на использование бота. Например, некоторые пользователи смогут использовать абсолютно все возможности бота, а некоторым вы предоставите ограниченные права.


Ограничить права можно не только на уровне пользователя, но и на уровне отдельных чатов.


Мы создадим простейшего бота, у которого в арсенале будет всего 2 метода:


  • say_hello команда приветствия
  • what_time команда, по которой бот сообщает текущую дату и время

Базовый код бота
library(telegram.bot)# создаём экземпляр класса Updaterupdater <- Updater('ТОКЕН ВАШЕГО БОТА')# Пишем метод для приветсвия## команда приветствияsay_hello <- function(bot, update) {  # Имя пользователя с которым надо поздароваться  user_name <- update$message$from$first_name  # Отправка сообщения  bot$sendMessage(update$message$chat_id,                   text = paste0("Моё почтение, ", user_name, "!"),                  parse_mode = "Markdown",                  reply_to_message_id = update$message$message_id)}## команда по которой бот возвращает системную дату и времяwhat_time <- function(bot, update) {  # Запрашиваем текущее время  cur_time <- as.character(Sys.time())  # Отправка сообщения  bot$sendMessage(update$message$chat_id,                   text = paste0("Текущее время, ", cur_time),                                parse_mode = "Markdown",                                reply_to_message_id = update$message$message_id)}# обработчикиh_hello <- CommandHandler('say_hello', say_hello)h_time  <- CommandHandler('what_time', what_time)# добавляем обработчики в диспетчерupdater <- updater + h_hello + h_time# запускаем бота updater$start_polling()

Запустите приведённый выше пример кода, предварительно заменив 'ТОКЕН ВАШЕГО БОТА' на реальный токен, который вы получили при создании бота через BotFather (о создании бота я рассказывал в первой статье).

В статье мы разберёмся с тем, как разными способами, и на разных уровнях ограничить использование методов этого бота.


Ограничиваем права пользователя с помощью фильтров сообщений


Из предыдущих публикаций мы уже разобрались с тем, что такое фильтры сообщений. Но ранее мы использовали их в основном для, того, что бы вызывать какие-то методы бота через обычное сообщение, а не команду и для прослушиваний сообщений в определённом состоянии чата.


В этот раз мы научимся с помощью фильтров ограничивать возможности по использованию методов бота, на разных уровнях.


Ограничиваем права на уровне имени пользователя


Для создания собственных фильтров вам необходимо с помощью функции BaseFilter() добавить новый элемент в объект MessageFilters. Более подробно об этом я рассказывал во второй статье из данной серии.


В анонимную функцию, которую вы прописываете внутри BaseFilter() передаётся всего один аргумент message. Это сообщение которое вы, или другой пользователей отправляет боту, со всеми его метаданными. Данный объект имеет следующую структуру:


$message_id[1] 1174$from$from$id[1] 194336771$from$is_bot[1] FALSE$from$first_name[1] "Alexey"$from$last_name[1] "Seleznev"$from$username[1] "AlexeySeleznev"$from$language_code[1] "ru"$chat$chat$id[1] 194336771$chat$first_name[1] "Alexey"$chat$last_name[1] "Seleznev"$chat$username[1] "AlexeySeleznev"$chat$type[1] "private"$date[1] 1601295189$text[1] "отправленный пользователем текст"$chat_id[1] 194336771$from_user[1] 194336771

Соответственно вы можете разделять права пользователей бота, и его методов используя любую, отправляемую с сообщением информацию. Для того, что бы ограничить круг пользователей которые могут использовать методы вашего бота достаточно создать фильтр:


## список пользователей, с полными правамиMessageFilters$admins <- BaseFilter(  function(message) {    # проверяем от кого отправлено сообщение    message$from$username %in% c('AlexeySeleznev', 'user1', 'user2') })

Где c('AlexeySeleznev', 'user1', 'user2') вектор, с именами пользователей, которые могут использовать все функции бота. Далее этот фильтр мы используем при создании обработчиков.


## фильтр для вызова команды say_helloMessageFilters$say_hello <- BaseFilter(  function(message) {    # проверяем от кого отправлено сообщение    message$text == '/say_hallo'  })## фильтр для вызова команды what_timeMessageFilters$what_time <- BaseFilter(  function(message) {    # проверяем от кого отправлено сообщение    message$text == '/what_time'  })# обработчикиh_hello <- MessageHandler(say_hello, MessageFilters$admins & MessageFilters$say_hello)h_time  <- MessageHandler(what_time, MessageFilters$admins & MessageFilters$what_time)

Теперь нашего бота могут использовать пользователи с логинами AlexeySeleznev, user1, user2. На сообщения отправленные боту другими пользователями он никак не будет реагировать.


Изменённый код нашего бота на данный момент выглядит вот так:


Код бота, в котором с помощью фильтров был ограничен круг пользователей
library(telegram.bot)# создаём экземпляр класса Updaterupdater <- Updater('ТОКЕН ВАШЕГО БОТА')# Пишем метод для приветсвия## команда приветствияsay_hello <- function(bot, update) {  # Имя пользователя с которым надо поздароваться  user_name <- update$message$from$first_name  # Отправка сообщения  bot$sendMessage(update$message$chat_id,                   text = paste0("Моё почтение, ", user_name, "!"),                  parse_mode = "Markdown",                  reply_to_message_id = update$message$message_id)}## команда по которой бот возвращает системную дату и времяwhat_time <- function(bot, update) {  # Запрашиваем текущее время  cur_time <- as.character(Sys.time())  # Отправка сообщения  bot$sendMessage(update$message$chat_id,                   text = paste0("Текущее время, ", cur_time),                                parse_mode = "Markdown",                                reply_to_message_id = update$message$message_id)}# фильтры## список пользователей, с полными правамиMessageFilters$admins <- BaseFilter(  function(message) {    # проверяем от кого отправлено сообщение    message$from$username %in% c('AlexeySeleznev', 'user1', 'user2') })## фильтр для вызова команды say_helloMessageFilters$say_hello <- BaseFilter(  function(message) {    # проверяем от кого отправлено сообщение    message$text == '/say_hallo'  })## фильтр для вызова команды what_timeMessageFilters$what_time <- BaseFilter(  function(message) {    # проверяем от кого отправлено сообщение    message$text == '/what_time'  })# обработчикиh_hello <- MessageHandler(say_hello, MessageFilters$admins & MessageFilters$say_hello)h_time  <- MessageHandler(what_time, MessageFilters$admins & MessageFilters$what_time)# добавляем обработчики в диспетчерupdater <- updater + h_hello + h_time# запускаем бота updater$start_polling()

Ограничиваем права на уровне чата


Тем же способом мы можем создать фильтр не только по списку пользователей, но и по конкретному чату. Для этого достаточно создать ещё один фильтр:


## список чатов в которых разрешено использовать ботаMessageFilters$chats <- BaseFilter(  function(message) {    # проверяем от кого отправлено сообщение    message$chat_id %in% c(194336771, 0, 1)  })## фильтр для вызова команды say_helloMessageFilters$say_hello <- BaseFilter(  function(message) {    # проверяем от кого отправлено сообщение    message$text == '/say_hallo'  })## фильтр для вызова команды what_timeMessageFilters$what_time <- BaseFilter(  function(message) {    # проверяем от кого отправлено сообщение    message$text == '/what_time'  })# обработчикиh_hello <- MessageHandler(say_hello, MessageFilters$admins & MessageFilters$chats & MessageFilters$say_hello)h_time  <- MessageHandler(what_time, MessageFilters$admins & MessageFilters$chats & MessageFilters$what_time)

Ограничиваем права пользователя внутри кода методов


Так же вы можете ограничить использование методов не прибегая к созданию дополнительных фильтров, а прописав все условия внутри каждого метода.


Ограничиваем права на уровне имени пользователя


Давайте создадим функцию, которая будет принимать данные для проверки имени пользователя.


# функция для проверки прав пользователяbot_check_usernames <-   function(admins, username) {   username %in% admins }

В аргумент admins далее нам надо передавать список пользователей, которым разрешено использовать данный метод, а в аргумент username имя пользователя, которого надо проверить в списке.


Далее в каждый метод, который мы хотим защитить с помощью конструкции IF добавляем проверку, разрешено пользователю использовать данный метод или нет. В случае если у пользователя нет таких прав бот будет сообщать об этом в чате.


Давайте, для примера, я исключу себя из списка пользователей, которым разрешено использовать метод what_time.


Код бота, в котором ограничены права пользователя внутри кода методов
library(telegram.bot)# создаём экземпляр класса Updaterupdater <- Updater('ТОКЕН ВАШЕГО БОТА')# Пишем метод для приветсвия## команда приветствияsay_hello <- function(bot, update) {  # Имя пользователя с которым надо поздароваться  user_name <- update$message$from$username  # проверяем разрешено ли использовать данному пользователю этот метод  if ( bot_check_usernames(c('AlexeySeleznev', 'user1', 'user2'), user_name) ) {    # Отправка сообщения    bot$sendMessage(update$message$chat_id,                     text = paste0("Моё почтение, ", user_name, "!"),                    parse_mode = "Markdown",                    reply_to_message_id = update$message$message_id)  } else {    # Отправка сообщения    bot$sendMessage(update$message$chat_id,                     text = paste0("У вас нет прав для использования этого метода!"),                    parse_mode = "Markdown",                    reply_to_message_id = update$message$message_id)  }}## команда по которой бот возвращает системную дату и времяwhat_time <- function(bot, update) {  # проверяем разрешено ли использовать данному пользователю этот метод  if ( bot_check_usernames(c('user1', 'user2'), update$message$from$username) ) {    # Запрашиваем текущее время    cur_time <- as.character(Sys.time())    # Отправка сообщения о том что у пользователя не достаточно прав    bot$sendMessage(update$message$chat_id,                     text = paste0("Текущее время, ", cur_time),                                  parse_mode = "Markdown",                                  reply_to_message_id = update$message$message_id)  } else {    # Отправка сообщения о том что у пользователя не достаточно прав    bot$sendMessage(update$message$chat_id,                     text = paste0("У вас нет прав для использования этого метода!"),                    parse_mode = "Markdown",                    reply_to_message_id = update$message$message_id)  }}# обработчикиh_hello <- CommandHandler('say_hello', say_hello)h_time  <- CommandHandler('what_time', what_time)# добавляем обработчики в диспетчерupdater <- updater + h_hello + h_time# запускаем бота updater$start_polling()

Результат будет следующим:


Теперь бот не просто игнорирует сообщения от обычных пользователей, а сообщает им, что у них недостаточно прав использовать какой либо метод.


Ограничиваем права на уровне чата


Думаю у вас уже не возникнет трудностей при доработке ваших методов, таким образом, что бы их можно было использовать только в определённых чатах, тем не менее приведу пример.


Напишем функцию, которая будет проверять входит ли текущий чат в список разрешенных.


bot_check_chat_id <-   function(allowed_chats, current_chat) {     current_chat %in% allowed_chats }

Далее используем эту функции внутри наших методов:


Код бота с распределением прав пользователя на уровне пользователей и чатов
library(telegram.bot)# создаём экземпляр класса Updaterupdater <- Updater('ТОКЕН ВАШЕГО БОТА')# Пишем метод для приветсвия## команда приветствияsay_hello <- function(bot, update) {  # Имя пользователя с которым надо поздароваться  user_name <- update$message$from$username  # проверяем разрешено ли использовать данному пользователю этот метод  if ( bot_check_usernames(c('AlexeySeleznev', 'user1', 'user2'), user_name)        &       bot_check_chat_id(c(194336771, 1, 2), update$message$chat_id)) {    # Отправка сообщения    bot$sendMessage(update$message$chat_id,                     text = paste0("Моё почтение, ", user_name, "!"),                    parse_mode = "Markdown",                    reply_to_message_id = update$message$message_id)  } else {    # Отправка сообщения    bot$sendMessage(update$message$chat_id,                     text = paste0("У вас нет прав для использования этого метода!"),                    parse_mode = "Markdown",                    reply_to_message_id = update$message$message_id)  }}## команда по которой бот возвращает системную дату и времяwhat_time <- function(bot, update) {  # проверяем разрешено ли использовать данному пользователю этот метод  if ( bot_check_usernames(c('AlexeySeleznev', 'user1', 'user2'), update$message$from$username)       &       bot_check_chat_id(c(194336771, 1, 2), update$message$chat_id)) {    # Запрашиваем текущее время    cur_time <- as.character(Sys.time())    # Отправка сообщения о том что у пользователя не достаточно прав    bot$sendMessage(update$message$chat_id,                     text = paste0("Текущее время, ", cur_time),                                  parse_mode = "Markdown",                                  reply_to_message_id = update$message$message_id)  } else {    # Отправка сообщения о том что у пользователя не достаточно прав    bot$sendMessage(update$message$chat_id,                     text = paste0("У вас нет прав для использования этого метода!"),                    parse_mode = "Markdown",                    reply_to_message_id = update$message$message_id)  }}# обработчикиh_hello <- CommandHandler('say_hello', say_hello)h_time  <- CommandHandler('what_time', what_time)# добавляем обработчики в диспетчерupdater <- updater + h_hello + h_time# запускаем бота updater$start_polling()

Заключение


На этом серия статей о построении telegram ботов завершается. Я старался структурировать и подавать материал достаточно сжато, убрав всю воду, но при этом сделать так, что бы материал был вам понятен. Очень надеюсь на то, что мне это удалось.


Успехов вам в ботостроении. В комментариях можете написать примеры ваших ботов, и как вы их на практике используете.

Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru