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

Пошаговый туториал по написанию Telegram бота на Ruby (native)

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

И вот пока я занимался написанием этого бота то познакомился с библиотекой (gem) telegram-bot-ruby, научился её использовать вместе с gem 'sqlite3-ruby и кроме того проникся многими возможностями Telegram ботов чем и хочу поделится с уважаемыми читателями этого форума, внести вклад так сказать.

Много людей хочет писать Telegram боты, ведь это весело и просто.

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

Сразу кидаю ссылку на свой репозиторий по этому посту:here,
Ибо во время тестирования были баги, которые я мог сюда и не перенести, вдруг чего смотреть прямо в репозиторий.

В следствии прочтения этого топика, я надеюсь читатель сможет улучшить своего уже написаного бота, или прямо сейчас скачать Ruby, Telegram и создать что-то новое и прекрасное. Ведь как уже было сказано в Декларации Киберпространства:

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

  • Предлагаю начать :

    У меня версия Ruby - 2.7.2, но не исключено что всё будет работать и с более ранними/поздними версиями.

  • Примерная структура приложения будет выглядеть вот так

  • Первым делом создадим Gemfile - основной держатель зависимостей для сторонних gems в Ruby.

  • ФайлGemfile:

    source 'https://rubygems.org'gem 'json'gem 'net-http-persistent', '~> 2.9'gem 'sqlite3'#gem для БДgem 'telegram-bot-ruby'#основной гем для создания соеденения с Telegram ботом
    

    Сохраняем файл и выполняем в терминале операцию

    bundle install
    

    Увидим успешную установку всех гемов (ну это же прелесть Ruby) и на этом сGemfileбудет покончено.

  • Если вы (как и я) лабораторная крыса GitHuba, то создаем .gitignoreдля нашего репозитория, у меня прописан классический для продуктов JetBrains файл:

  • Файл .gitignore:

    /.idea/
    
  • Далее создадим первый класс в корне проекта, называем как хотим этот класс будет выступать в роли инициализатора, в моем случае этоFishSocket:

  • файлFishSocket.rb:

    require 'telegram/bot'require './library/mac-shake'require './library/database'require './modules/listener'require './modules/security'require './modules/standart_messages'require './modules/response'Entry point classclass FishSocket  include Database  def initialize    super    # Initialize BD    Database.setup    # Establishing webhook via @gem telegram/bot, using API-KEY    Telegram::Bot::Client.run(TelegramOrientedInfo::APIKEY) do |bot|      # Start time variable, for exclude message what was sends before bot starts      startbottime = Time.now.toi      # Active socket listener      bot.listen do |message|        # Processing the new income message    #if that message sent after bot run.        Listener.catchnewmessage(message,bot) if Listener::Security.messageisnew(startbottime,message)      end    end  endendBot startFishSocket.new
    

    Как видим в этот файле упомянуты сразу 5 различных файлов :Gem telegram/bot,Модули mac-shake, listener, security, database.

  • Поэтому предлагаю сразу их создать и показать что к чему:

  • Файлmac-shake.rb:

    # frozenstringliteral: truemodule TelegramOrientedInfoAPIKEY = ''end
    
  • Как видим в этом файле используется API-KEY для связи с нашим ботом, предлагаю сразу его получить, для этого обратимся к боту от Telegram API :@BotFather

    API-Key который нам вернул бот, следует вставить в константу API-Key, упомянутую ранее.

  • Файлsecurity.rb:

    class FishSocket  module Listener    # Module for checks    module Security      def messageisnew(starttime, message)        messagetime = (defined? message.date) ? message.date : message.message.date        messagetime.toi > starttime      end  def message_too_far    message_date = (defined? Listener.message.date) ? Listener.message.date : Listener.message.message.date    message_delay = Time.now.to_i - message_date.to_i    # if message delay less then 5 min then processing message, else ignore    message_delay > (5 * 60)  end  module_function :message_is_new, :message_too_farendendend
    

    В этом файле происходит две проверки : на то, что бы сообщение было отпарвлено после старта бота (не обрабатывать команды которые были отпраленны в прошлой сессии). И вторая проверка, что бы не обрабатывать сообщение которым больше 5 минут (вдруг вы добавите очередь, и таким образом мы ограничиваем её длину)

  • Файлlistener.rb:

    class FishSocket  # Sorting new message module  module Listener    attr_accessor :message, :botdef catch_new_message(message,bot)  self.message = message  self.bot = bot  return false if Security.message_too_far  case self.message  when Telegram::Bot::Types::CallbackQuery    CallbackMessages.process  when Telegram::Bot::Types::Message    StandartMessages.process  endendmodule_function(  :catch_new_message,  :message,  :message=,  :bot,  :bot=)endend
    

    В этом файле мы делим сообщения на две группы, являются ли они ответом на callback функцию, или они обычные.Сейчас проясню что такое callback сообщение в телеграме.Telegram API версии 2.0 предоставляет достаточно обширную поддержку InlineMessages. Это такие сообщение, которые в себе содержает UI элементы взаемодействия с пользователем, я в своем боте использоватInlineKeyboardMarkupэто кнопки, после нажатия на которые сообщение которые прийдет на бота, будет типаCallbackMessage, и текст сообщение будет равен тому, который мы указали в атрибут кнопки, при отправке запроса на Telegram API. Позже мы ешё вернёмся к этому принципу.

  • ФайлDatabase.rb

    # This module assigned to all database operationsmodule Database  attr_accessor :dbrequire 'sqlite3'  # This module assigned to create table action  module Create    def steamaccountlist      Database.db.execute <<-SQL    CREATE TABLE steamaccountlist (    accesses VARCHAR (128),    used INTEGER (1))      SQL      true    rescue SQLite3::SQLException      false    end    modulefunction(        :steamaccount_list    )  enddef setup    # Initializing database file    self.db = SQLite3::Database.open 'autosteam.db'    # Try to get custom table, if table not exists - create this one    unless gettable('steamaccountlist')      Create.steamaccount_list    end  end# Get all from the selected table  # @var tablename  def gettable(tablename)    db.execute <<-SQL    Select * from #{tablename}    SQL  rescue SQLite3::SQLException    false  endmodulefunction(    :gettable,    :setup,    :db,    :db=  )end
    

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

  • Можем попытатся запустить нашего бота, посредством выполнения файлаfishsocket.rbЕсли мы всё сделали правильно, то не должны увидеть никакого сообщения о завершеной работе, так как происходит Active Socket прослушывания ответа от Telegram API.Мы по-сути реестрируем наш локальный сервер прикрепляя его к Webhook от Telegram API, на который будут приходить сообщения о любых изменениях.

  • Попробуем добавить примитивный ответ на какое-то сообщение в боте

    Создадим файлstandartmessages.rb, модуль который будет обрабатывать Стандартные (текстовые) сообщение нашего бота. Как помним сообщение бывают двух типов : Standart и Callback.

    Файлstandartmessages.rb:

    class FishSocket  module Listener    # This module assigned to processing all standart messages    module StandartMessages      def process        case Listener.message.text        when '/getaccount'          Response.stdmessage 'Very sorry, нету аккаунтов на данный момент'        else          Response.stdmessage 'Первый раз такое слышу, попробуй другой текст'        end      end  module_function(      :process  )endendend
    

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

  • Ах да, ответ мы отправляем с помощью модуляResponse, который прямо сейчас и создадим

    Файлresponse.rb

    class FishSocket  module Listener    # This module assigned to responses from bot    module Response      def stdmessage(message, chatid = false )        chat = (defined?Listener.message.chat.id) ? Listener.message.chat.id : Listener.message.message.chat.id        chat = chatid if chatid        Listener.bot.api.sendmessage(          parsemode: 'html',          chatid: chat,          text: message        )      end  module_function(    :std_message  )endendend
    

    В этом файле мы обращаемся к API Telegrama согласно документации, но уже используя gem telegram-ruby, а именно его функциюapi.sendmessage. Все атрибуты можно посмотреть в Telegram API и поигратся с ними, скажу только лишь что этот метод может отправлять только обычные сообщения.

  • Запускаем бота и тестируем две команды :(Бота можно найти по ссылке которую вам вернул BotFather, вместе с API ключем.

    Привет
    
    /getaccount
    

    Как видим всё отработала так как мы и хотели.

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

  • Создадим подпапку assets/ в ней модуль inlinebutton.Файлinlinebutton.rb:

    class FishSocket  # This module assigned to creating InlineKeyboardButton  module InlineButton    GETACCOUNT = Telegram::Bot::Types::InlineKeyboardButton.new(text: 'Получить account', callbackdata: 'getaccount')  endend
    

    Сдесь мы обращаемся всё к тому жеtelegram-ruby-gemчто бы создать обьект типа InlineKeyboardButton.

  • Разширим наш файлReponseновыми методоми :

    def inlinemessage(message, inlinemarkup,editless = false, chatid = false)  chat = (defined?Listener.message.chat.id) ? Listener.message.chat.id : Listener.message.message.chat.id  chat = chatid if chatid  Listener.bot.api.sendmessage(    chatid: chat,    parsemode: 'html',    text: message,    replymarkup: inlinemarkup)enddef generateinlinemarkup(kb, force = false)  Telegram::Bot::Types::InlineKeyboardMarkup.new(    inlinekeyboard: kb  )end
    

    Не стоит забывать выносить новые методы в modulefunction() :

    modulefunction(  :stdmessage,  :generateinlinemarkup,  :inlinemessage)
    
  • Добавим на действия

    /start
    

    , вывод нашей кнопки, для этого разширим сначала модульStandartMessages

    def process  case Listener.message.text  when '/getaccount'    Response.stdmessage 'Very sorry, нету аккаунтов на данный момент'  when '/start'    Response.inlinemessage 'Привет, выбери из доступных действий', Response::generateinlinemarkup(        InlineButton::GETACCOUNT    )  else    Response.stdmessage 'Первый раз такое слышу, попробуй другой текст'  endend
    
  • Создадим файлcallbackmessages.rbдля обработки Callback сообщений :Файлcallbackmessages.rb

    class FishSocket  module Listener    # This module assigned to processing all callback messages    module CallbackMessages      attraccessor :callback_message  def process    self.callback_message = Listener.message.message    case Listener.message.data    when 'get_account'      Listener::Response.std_message('Нету аккаунтов на данный момент')    end  end  module_function(      :process,      :callback_message,      :callback_message=  )endendend
    

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

  • Не забываем обновить список подключаемых модулей, новыми модулями.Файлfishsocket.rb

    require 'telegram/bot'require './library/mac-shake'require './library/database'require './modules/listener'require './modules/security'require './modules/standartmessages'require './modules/response'require './modules/callbackmessages'require './modules/assets/inlinebutton'Entry point classclass FishSocket  include Database  def initialize    super
    
  • Пытаемся запустить бота и посмотреть что будет когда напишем

    /start
    

    Нажимая на кнопку мы видим то - что хотели увидеть.

  • Я бы ещё очень много чем хотел поделится, но тогда это будет бесконечная статья по своей сути - мы же рассмотрим ещё буквально 2 примера на создание ForceReply кнопки, и на использование EditInlineMessage функции


  • ForceReply, создадим соответствующий метод в нашемResponseмодуле

    def forcereplymessage(text, chatid = false)  chat = (defined?Listener.message.chat.id) ? Listener.message.chat.id : Listener.message.message.chat.id  chat = chatid if chatid  Listener.bot.api.sendmessage(    parsemode: 'html',    chatid: chat,    text: text,    replymarkup: Telegram::Bot::Types::ForceReply.new(      forcereply: true,      selective: true    )  )end
    

    Не нужно забывать обновлять modulefunction нашего модуля после изминения кол-ва методов.

    Попробуем сделать банальную реакцию на ввод промокода (хз зачем, для примера)

  • Добавим новую кнопку :

    module InlineButton  GETACCOUNT = Telegram::Bot::Types::InlineKeyboardButton.new(text: 'Получить account', callbackdata: 'getaccount')  HAVEPROMO = Telegram::Bot::Types::InlineKeyboardButton.new(text: 'Есть промокод?', callbackdata: 'forcepromo')end
    
  • Добавить её в вывод по команде

    /start
    

    МодульStandartMessages

    when '/start'  Response.inlinemessage 'Привет, выбери из доступных действий', Response::generateinlinemarkup(    [        InlineButton::GETACCOUNT,        InlineButton::HAVEPROMO    ]  )
    

    Поскольку теперь используется больше одной кнопки, их стоит поместить в массив.

  • Добавим реакцию на нажатие на кнопку, с использованиемForceReply:МодульCallbackMessages

    def process  self.callbackmessage = Listener.message.message  case Listener.message.data  when 'getaccount'    Listener::Response.stdmessage('Нету аккаунтов на данный момент')  when 'forcepromo'    Listener::Response.forcereplymessage('Отправьте промокод')  endend
    
  • Проверим то что мы написали,

    На сообщение от бота сработал ForceReply, что это значит : сообщение выбрано как сообщение для ответа (Reply) так, как если бы мы сами выбрали ответим на сообщение. Оченьюзефулесли речь о пошаговых операциях где нам нужно наверняка знать что именно хочет сказатьюзер.

  • Добавим реакцию на ответ пользователя на сообщение "Отправьте промкод." Поскольку человек отправляет текст, то реагировать мы будем в StandartMessages :МодульStandartMessages

    def process  case Listener.message.text  when '/getaccount'    Response.stdmessage 'Very sorry, нету аккаунтов на данный момент'  when '/start'    Response.inlinemessage 'Привет, выбери из доступных действий', Response::generateinlinemarkup(      [          InlineButton::GETACCOUNT,          InlineButton::HAVEPROMO      ]    )  else    unless Listener.message.replytomessage.nil?      case Listener.message.replytomessage.text      when /Отправьте промокод/        return Listener::Response.std_message 'Промокод существует, вот бесплатный аккаунт :' if Promos::validate Listener.message.text    return Listener::Response.std_message 'Промокод не найден'  endendResponse.std_message 'Первый раз такое слышу, попробуй другой текст'endend
    
  • Создадим файлpromos.rbдля обрабоки промокодовФайлpromos.rb

    class FishSocket  module Listener    # This module assigned to processing all promo-codes    module Promos      def validate(code)        return true if code =~ /^1[a-zA-Z]*0$/        false      end  module_function(      :validate  )endendend
    

    Здесь мы используем регулярное выражение для проверки промокода.НЕ забываем подключить новый модуль в FishSocket модуле :МодульFishSocket

    require 'telegram/bot'require './library/mac-shake'require './library/database'require './modules/listener'require './modules/security'require './modules/standartmessages'require './modules/response'require './modules/callbackmessages'require './modules/assets/inline_button'require './modules/promos'Entry point classclass FishSocket  include Database  def initialize
    
  • Предлагаю протестировать с заведомо не рабочим промокодом, и правильно написаным:

    Функционал работает как и ожидалось, перейдем к последнему пункту: изминения InlineMessages:

  • Вынесем промокоды в отдельное "Меню", для этого добавим новую кнопку на ответ на сообщение

    /start
    

    заменив её кнопку "Есть промкод?"МодульInlineButton

    module InlineButton  GETACCOUNT = Telegram::Bot::Types::InlineKeyboardButton.new(text: 'Получить account', callbackdata: 'getaccount')  HAVEPROMO = Telegram::Bot::Types::InlineKeyboardButton.new(text: 'Есть промокод?', callbackdata: 'forcepromo')  ADDITIONMENU = Telegram::Bot::Types::InlineKeyboardButton.new(text: 'Ништяки', callbackdata: 'advancedmenu')end
    

    Модуль StandartMessages

    when '/start'  Response.inlinemessage 'Привет, выбери из доступных действий', Response::generateinlinemarkup(    [        InlineButton::GETACCOUNT,        InlineButton::ADDITIONMENU    ]  )
    

    Отлично

  • Теперь добавим реакцию на новую кнопку в модуль СallbackMessages:МодульCallbackMessages

    def process  self.callbackmessage = Listener.message.message  case Listener.message.data  when 'getaccount'    Listener::Response.stdmessage('Нету аккаунтов на данный момент')  when 'forcepromo'    Listener::Response.forcereplyC222Cmenu'    Listener::Response.inlineC223CinlineC224CButton::HAVEC225Cmessage
    
  • Предлагаю реализовать обработку этого атрибута в модулеResponse, немного изменив методinlinemessageМодульResponse

    def inlinemessage(message, inlinemarkup, editless = false, chatid = false)  chat = (defined?Listener.message.chat.id) ? Listener.message.chat.id : Listener.message.message.chat.id  chat = chatid if chatid  if editless    return Listener.bot.api.editmessagetext(      chatid: chat,      parsemode: 'html',      messageid: Listener.message.message.messageid,      text: message,      replymarkup: inlinemarkup    )  end  Listener.bot.api.sendmessage(    chatid: chat,    parsemode: 'html',    text: message,    replymarkup: inline_markup  )end
    

    Какова идея? - Мы заменяем уже существующее сообщение на новое, с новым интерфейсом, этот переход позволяет меньше растягивать историю сообщений, и создавать модульные сообщения - такие как меню, оплата, список участников, витрина итд.

  • Что ж, попробуем :

    После того как нажали на кнопку, сообщение измененилось, отобразив другой ReplyKeyboard.
    И если мы клацнем на неё :

    Собственно всё работает как часы.

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

Источник: habr.com
К списку статей
Опубликовано: 30.12.2020 02:06:40
0

Сейчас читают

Комментариев (0)
Имя
Электронная почта

Ruby

Программирование

Telegram

Bot

Telegram-bot

Rubygems

Категории

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

  • Имя: Макс
    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