lib/telegram/bot/updates_controller.rb in telegram-bot-0.4.2 vs lib/telegram/bot/updates_controller.rb in telegram-bot-0.5.0

- old
+ new

@@ -2,16 +2,63 @@ require 'active_support/callbacks' require 'active_support/version' module Telegram module Bot + # Base class to create update processors. With callbacks, session and helpers. + # + # Define public methods for each command and they will be called when + # update has this command. Message is automatically parsed and + # words are passed as method arguments. Be sure to use default values and + # splat arguments in every action method to not get errors, when user + # sends command without necessary args / with extra args. + # + # def start(token = nil, *) + # if token + # # ... + # else + # # ... + # end + # end + # + # def help(*) + # reply_with :message, text: + # end + # + # To process plain text messages (without commands) or other updates just + # define public method with name of payload type. They will receive payload + # as an argument. + # + # def message(message) + # reply_with :message, text: "Echo: #{message['text']}" + # end + # + # def inline_query(query) + # answer_inline_query results_for_query(query), is_personal: true + # end + # + # # To process conflicting commands (`/message args`) just use `on_` prefix: + # def on_message(*args) + # # ... + # end + # + # To process update run: + # + # ControllerClass.dispatch(bot, update) + # + # There is also ability to run action without update: + # + # ControllerClass.new(bot, from: telegram_user, chat: telegram_chat). + # process(:help, *args) + # class UpdatesController < AbstractController::Base abstract! require 'telegram/bot/updates_controller/session' require 'telegram/bot/updates_controller/log_subscriber' require 'telegram/bot/updates_controller/instrumentation' + autoload :MessageContext, 'telegram/bot/updates_controller/message_context' include AbstractController::Callbacks # Redefine callbacks with default terminator. if ActiveSupport.gem_version >= Gem::Version.new('5') define_callbacks :process_action, @@ -35,10 +82,11 @@ ).freeze CMD_REGEX = %r{\A/([a-z\d_]{,31})(@(\S+))?(\s|$)}i CONFLICT_CMD_REGEX = Regexp.new("^(#{PAYLOAD_TYPES.join('|')}|\\d)") class << self + # Initialize controller and process update. def dispatch(*args) new(*args).dispatch end # Overrid it to filter or transform commands. @@ -57,30 +105,51 @@ def command_from_text(text, username = nil) return unless text match = text.match CMD_REGEX return unless match return if match[3] && username != true && match[3] != username - [match[1], text.split(' ').drop(1)] + [match[1], text.split.drop(1)] end end attr_internal_reader :update, :bot, :payload, :payload_type, :is_command alias_method :command?, :is_command delegate :username, to: :bot, prefix: true, allow_nil: true + # Second argument can be either update object with hash access & string + # keys or Hash with `:from` or `:chat` to override this values and assume + # that update is nil. def initialize(bot = nil, update = nil) + if update.is_a?(Hash) && (update.key?(:from) || update.key?(:chat)) + options = update + update = nil + end @_update = update @_bot = bot + @_chat, @_from = options && options.values_at(:chat, :from) + payload_data = nil update && PAYLOAD_TYPES.find do |type| item = update[type] - next unless item - @_payload = item - @_payload_type = type + payload_data = [item, type] if item end + @_payload, @_payload_type = payload_data end + # Accessor to `'chat'` field of payload. Can be overriden with `chat` option + # for #initialize. + def chat + @_chat || payload && payload['chat'] + end + + # Accessor to `'from'` field of payload. Can be overriden with `from` option + # for #initialize. + def from + @_from || payload && payload['from'] + end + + # Processes current update. def dispatch @_is_command, action, args = action_for_payload process(action, *args) end @@ -100,20 +169,33 @@ # Silently ignore unsupported messages. # Params are `action, *args`. def action_missing(*) end - %w(chat from).each do |field| - define_method(field) { payload[field] } - end - + # Helper to call bot's `send_#{type}` method with already set `chat_id` and + # `reply_to_message_id`: + # + # reply_with :message, text: 'Hello!' + # reply_with :message, text: '__Hello!__', parse_mode: :Markdown + # reply_with :photo, photo: File.open(photo_to_send), caption: "It's incredible!" def reply_with(type, params) method = "send_#{type}" + chat = self.chat + payload = self.payload params = params.merge( - chat_id: chat['id'], - reply_to_message: payload['message_id'], + chat_id: (chat && chat['id'] or raise 'Can not reply_with when chat is not present'), + reply_to_message: payload && payload['message_id'], ) bot.public_send(method, params) + end + + # Same as reply_with, but for inline queries. + def answer_inline_query(results, params = {}) + params = params.merge( + inline_query_id: payload['id'], + results: results, + ) + bot.answer_inline_query(params) end ActiveSupport.run_load_hooks('telegram.bot.updates_controller', self) end end