diff --git a/CHANGELOG.md b/CHANGELOG.md index 936a6fa..509edb8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,7 @@ # 0.7.0 Unreleased - New Bot API methods. -- Helpers for inline keyboards. +- Helpers for inline keyboards, support for callback_query (with contextual actions). - Changed action methods signature - `#inline_query(payload) -> #inline_query(query, offset)` - `#chosen_inline_result(payload)` -> `#chosen_inline_result(result_id, query)` diff --git a/README.md b/README.md index b90138d..f4e5d4f 100644 --- a/README.md +++ b/README.md @@ -236,6 +236,12 @@ class Telegram::WebhookController < Telegram::Bot::UpdatesController end ``` +You can use `CallbackQueyContext` in the similar way to split `#callback_query` into +several specific methods. It doesn't require session support, and takes context from +data. If data has a prefix with colon like this `my_ctx:smth...` it'll call +`my_ctx_callback_query('smth...')` when there is such action method. Otherwise +it'll call `callback_query('my_ctx:smth...')` as usual. + To process update run: ```ruby diff --git a/lib/telegram/bot/updates_controller.rb b/lib/telegram/bot/updates_controller.rb index 8bf5a4c..2f34889 100644 --- a/lib/telegram/bot/updates_controller.rb +++ b/lib/telegram/bot/updates_controller.rb @@ -57,6 +57,7 @@ module Telegram require 'telegram/bot/updates_controller/log_subscriber' require 'telegram/bot/updates_controller/instrumentation' require 'telegram/bot/updates_controller/reply_helpers' + autoload :CallbackQueyContext, 'telegram/bot/updates_controller/callback_query_context' autoload :MessageContext, 'telegram/bot/updates_controller/message_context' autoload :Botan, 'telegram/bot/updates_controller/botan' diff --git a/lib/telegram/bot/updates_controller/callback_query_context.rb b/lib/telegram/bot/updates_controller/callback_query_context.rb new file mode 100644 index 0000000..2d4303c --- /dev/null +++ b/lib/telegram/bot/updates_controller/callback_query_context.rb @@ -0,0 +1,34 @@ +module Telegram + module Bot + class UpdatesController + # Use separate actions for different callback queries. + # It doesn't require session support. Simply add `%{context}:` prefix to data. + module CallbackQueyContext + protected + + # Uses #context_from_callback_query as context name. + # If context is present checks if `%context%_callback_query` is valid + # action method and returns it if so. Context is stripped from data + # in this case. Otherwise returns `super`. + # + # It wont raise ActionNotFound as MessageContext does, + # because `data` param is controlled by client. + def action_for_callback_query + context, new_data = context_from_callback_query + # binding.pry + if context + action_name = "#{context}_callback_query" + [false, action_name, [new_data]] if action_method?(action_name) + end || super + end + + def context_from_callback_query + data = payload['data'] + return unless data + parts = data.split(':', 2) + parts if parts.size > 1 + end + end + end + end +end diff --git a/spec/telegram/bot/updates_controller/callback_query_context_spec.rb b/spec/telegram/bot/updates_controller/callback_query_context_spec.rb new file mode 100644 index 0000000..c94433d --- /dev/null +++ b/spec/telegram/bot/updates_controller/callback_query_context_spec.rb @@ -0,0 +1,65 @@ +RSpec.describe Telegram::Bot::UpdatesController::CallbackQueyContext do + include_context 'telegram/bot/updates_controller' + let(:controller_class) do + described_class = self.described_class + Class.new(Telegram::Bot::UpdatesController) do + include described_class + + attr_accessor :filter_done + before_action only: :ctx2_callback_query do + self.filter_done = true + end + + def callback_query(data) + [:no_context, data] + end + + def ctx1_callback_query(data) + [:ctx1, data] + end + + def ctx2_callback_query(data) + [:ctx2, data] + end + end + end + + describe '#dispatch' do + subject { -> { dispatch } } + let(:payload_type) { :callback_query } + let(:payload) { {'data' => data} } + let(:data) { text } + let(:text) { 'asd qwe zxc' } + + context 'when context is not set' do + its(:call) { should eq [:no_context, text] } + it { should_not change(controller, :filter_done) } + end + + context 'when context is set' do + let(:data) { "#{ctx}:#{text}" } + + context 'and valid' do + let(:ctx) { 'ctx1' } + its(:call) { should eq [:ctx1, text] } + it { should_not change(controller, :filter_done) } + + context 'and context has callback' do + let(:ctx) { 'ctx2' } + its(:call) { should eq [:ctx2, text] } + it { should change(controller, :filter_done) } + end + + context 'and data has multiple colons' do + let(:text) { super().tr(' ', ':') } + its(:call) { should eq [:ctx1, text] } + end + end + + context 'and invalid' do + let(:ctx) { 'missing' } + its(:call) { should eq [:no_context, data] } + end + end + end +end