lib/telegram/bot/updates_controller.rb in telegram-bot-0.13.1 vs lib/telegram/bot/updates_controller.rb in telegram-bot-0.14.0

- old
+ new

@@ -1,6 +1,7 @@ require 'abstract_controller' +require 'active_support/core_ext/string/inflections' require 'active_support/callbacks' require 'active_support/version' module Telegram module Bot @@ -52,16 +53,18 @@ # class UpdatesController < AbstractController::Base # rubocop:disable ClassLength abstract! %w[ - instrumentation - log_subscriber - reply_helpers - rescue - session - ].each { |file| require "telegram/bot/updates_controller/#{file}" } + Commands + Instrumentation + LogSubscriber + ReplyHelpers + Rescue + Session + Translation + ].each { |name| require "telegram/bot/updates_controller/#{name.underscore}" } %w[ CallbackQueryContext MessageContext TypedUpdate @@ -76,13 +79,16 @@ define_callbacks :process_action, terminator: ->(_, result) { result == false }, skip_after_callbacks_if_terminated: true end - include AbstractController::Translation + include Commands include Rescue include ReplyHelpers + include Translation + # Add instrumentations hooks at the bottom, to ensure they instrument + # all the methods properly. include Instrumentation extend Session::ConfigMethods PAYLOAD_TYPES = %w[ @@ -94,50 +100,26 @@ chosen_inline_result callback_query shipping_query pre_checkout_query ].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. - # Default implementation is to convert to downcase and add `on_` prefix - # for conflicting commands. - def action_for_command(cmd) - cmd.downcase! - cmd.match(CONFLICT_CMD_REGEX) ? "on_#{cmd}" : cmd - end - - # Fetches command from text message. All subsequent words are returned - # as arguments. - # If command has mention (eg. `/test@SomeBot`), it returns commands only - # for specified username. Set `username` to `true` to accept - # any commands. - def command_from_text(text, username = nil) - return unless text - match = text.match(CMD_REGEX) - return unless match - mention = match[3] - [match[1], text.split.drop(1)] if username == true || !mention || mention == username - end - def payload_from_update(update) update && PAYLOAD_TYPES.find do |type| item = update[type] return [item, type] if item end end end - attr_internal_reader :update, :bot, :payload, :payload_type, :is_command - alias_method :command?, :is_command + attr_internal_reader :update, :bot, :payload, :payload_type 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. @@ -173,57 +155,67 @@ @_from ||= payload && payload['from'] end # Processes current update. def dispatch - @_is_command, action, args = action_for_payload + action, args = action_for_payload process(action, *args) end + attr_internal_reader :action_options + + # It provides support for passing array as action, where first vaule + # is action name and second is action metadata. + # This metadata is stored inside action_options + def process(action, *args) + action, options = action if action.is_a?(Array) + @_action_options = options || {} + super + end + + # There are multiple ways how action name is calculated for update + # (see Commands, MessageContext, etc.). This method represents the + # way how action was calculated for current udpate. + # + # Some of possible values are `:payload, :command, :message_context`. + def action_type + action_options[:type] || :payload + end + # Calculates action name and args for payload. # Uses `action_for_#{payload_type}` methods. # If this method doesn't return anything # it uses fallback with action same as payload type. - # Returns array `[is_command?, action, args]`. + # Returns array `[action, args]`. def action_for_payload if payload_type send("action_for_#{payload_type}") || action_for_default_payload else - [false, :unsupported_payload_type, []] + [:unsupported_payload_type, []] end end def action_for_default_payload - [false, payload_type, [payload]] + [payload_type, [payload]] end - # If payload is a message with command, then returned action is an - # action for this command. - # Separate method, so it can be easily overriden (ex. MessageContext). - # - # This is not used for edited messages/posts. It process them as basic updates. - def action_for_message - cmd, args = self.class.command_from_text(payload['text'], bot_username) - cmd &&= self.class.action_for_command(cmd) - [true, cmd, args] if cmd - end - alias_method :action_for_channel_post, :action_for_message - def action_for_inline_query - [false, payload_type, [payload['query'], payload['offset']]] + [payload_type, [payload['query'], payload['offset']]] end def action_for_chosen_inline_result - [false, payload_type, [payload['result_id'], payload['query']]] + [payload_type, [payload['result_id'], payload['query']]] end def action_for_callback_query - [false, payload_type, [payload['data']]] + [payload_type, [payload['data']]] end - # Silently ignore unsupported messages. - # Params are `action, *args`. - def action_missing(*) + # Silently ignore unsupported messages to not fail when user crafts + # an update with usupported command, callback query context, etc. + def action_missing(action, *_args) + logger.debug { "The action '#{action}' is not defined in #{self.class.name}" } if logger + nil end PAYLOAD_TYPES.each do |type| method = :"action_for_#{type}" alias_method method, :action_for_default_payload unless instance_methods.include?(method)