зеркало из
https://github.com/glebtv/telegram-bot.git
synced 2026-09-07 03:25:49 +03:00
MessageContext
Этот коммит содержится в:
39
README.md
39
README.md
@@ -180,6 +180,45 @@ class Telegram::WebhookController < Telegram::Bot::UpdatesController
|
|||||||
end
|
end
|
||||||
```
|
```
|
||||||
|
|
||||||
|
It's usual to support chain of messages like BotFather: after receiving command
|
||||||
|
it asks you for additional argument. There is `MessageContext` for this:
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
class Telegram::WebhookController < Telegram::Bot::UpdatesController
|
||||||
|
include Telegram::Bot::UpdatesController::MessageContext
|
||||||
|
|
||||||
|
def rename(*)
|
||||||
|
# set context for the next message
|
||||||
|
save_context :rename
|
||||||
|
reply_with :message, text: 'What name do you like?'
|
||||||
|
end
|
||||||
|
|
||||||
|
# register context handlers to handle this context
|
||||||
|
context_handler :rename do |message|
|
||||||
|
update_name message[:text]
|
||||||
|
reply_with :message, text: 'Renamed!'
|
||||||
|
end
|
||||||
|
|
||||||
|
# You can do it in other way:
|
||||||
|
def rename(name = nil, *)
|
||||||
|
if name
|
||||||
|
update_name message[:text]
|
||||||
|
reply_with :message, text: 'Renamed!'
|
||||||
|
else
|
||||||
|
save_context :rename
|
||||||
|
reply_with :message, text: 'What name do you like?'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# This will call #rename like if it is called with message '/rename %text%'
|
||||||
|
context_handler :rename
|
||||||
|
|
||||||
|
# If you have a lot of such methods you can use
|
||||||
|
context_to_action!
|
||||||
|
# It'll use context value as action name for all contexts which miss handlers.
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
### Routes
|
### Routes
|
||||||
|
|
||||||
Use `telegram_webhooks` helper to add routes. It will create routes for bots
|
Use `telegram_webhooks` helper to add routes. It will create routes for bots
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ module Telegram
|
|||||||
require 'telegram/bot/updates_controller/session'
|
require 'telegram/bot/updates_controller/session'
|
||||||
require 'telegram/bot/updates_controller/log_subscriber'
|
require 'telegram/bot/updates_controller/log_subscriber'
|
||||||
require 'telegram/bot/updates_controller/instrumentation'
|
require 'telegram/bot/updates_controller/instrumentation'
|
||||||
|
autoload :MessageContext, 'telegram/bot/updates_controller/message_context'
|
||||||
|
|
||||||
include AbstractController::Callbacks
|
include AbstractController::Callbacks
|
||||||
# Redefine callbacks with default terminator.
|
# Redefine callbacks with default terminator.
|
||||||
@@ -59,7 +60,7 @@ module Telegram
|
|||||||
match = text.match CMD_REGEX
|
match = text.match CMD_REGEX
|
||||||
return unless match
|
return unless match
|
||||||
return if match[3] && username != true && match[3] != username
|
return if match[3] && username != true && match[3] != username
|
||||||
[match[1], text.split(' ').drop(1)]
|
[match[1], text.split.drop(1)]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
102
lib/telegram/bot/updates_controller/message_context.rb
Обычный файл
102
lib/telegram/bot/updates_controller/message_context.rb
Обычный файл
@@ -0,0 +1,102 @@
|
|||||||
|
module Telegram
|
||||||
|
module Bot
|
||||||
|
class UpdatesController
|
||||||
|
# Allows to store context in session and treat next message according to this context.
|
||||||
|
module MessageContext
|
||||||
|
extend ActiveSupport::Concern
|
||||||
|
|
||||||
|
include Session
|
||||||
|
|
||||||
|
included do
|
||||||
|
# As we use before_action context is cleared anyway,
|
||||||
|
# no matter we used it or not.
|
||||||
|
before_action :fetch_context
|
||||||
|
singleton_class.send :attr_reader, :context_handlers, :context_to_action
|
||||||
|
@context_handlers = {}
|
||||||
|
end
|
||||||
|
|
||||||
|
module ClassMethods
|
||||||
|
# Registers handler for context.
|
||||||
|
#
|
||||||
|
# context_handler :rename do |message|
|
||||||
|
# resource.update!(name: message['text'])
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
# # To run other action with all the callbacks:
|
||||||
|
# context_handler :rename do |message|
|
||||||
|
# process(:rename, *m['text'].split)
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
# # Or just
|
||||||
|
# context_handler :rename, :your_action_to_call
|
||||||
|
# context_handler :rename # to call :rename
|
||||||
|
#
|
||||||
|
# # For messages without context use this instead of `message` method:
|
||||||
|
# context_handle do |message|
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
def context_handler(context = nil, action = nil, &block)
|
||||||
|
context &&= context.to_sym
|
||||||
|
context_handlers[context] = block || action || context
|
||||||
|
end
|
||||||
|
|
||||||
|
# Use it to use context value as action name for all contexts
|
||||||
|
# which miss handlers.
|
||||||
|
# For security reasons it supports only action methods and will
|
||||||
|
# raise AbstractController::ActionNotFound if context is invalid.
|
||||||
|
def context_to_action!
|
||||||
|
@context_to_action = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Finds handler for current context and processes message with it.
|
||||||
|
def message(message)
|
||||||
|
handler = handler_for_context
|
||||||
|
return unless handler
|
||||||
|
if handler.respond_to?(:call)
|
||||||
|
instance_exec(message, &handler)
|
||||||
|
else
|
||||||
|
process(handler, *message['text'].split)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Action to clear context.
|
||||||
|
def cancel
|
||||||
|
# Context is already cleared in before_action
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
# Context is read from the session to treat messages
|
||||||
|
# according to previous request.
|
||||||
|
attr_reader :context
|
||||||
|
|
||||||
|
# Fetches and removes context from session.
|
||||||
|
def fetch_context
|
||||||
|
val = session.delete(:context)
|
||||||
|
@context = val && val.to_sym
|
||||||
|
true # TODO: remove in Rails 5.0
|
||||||
|
end
|
||||||
|
|
||||||
|
# Save context for the next request.
|
||||||
|
def save_context(context)
|
||||||
|
session[:context] = context
|
||||||
|
end
|
||||||
|
|
||||||
|
def handler_for_context
|
||||||
|
self.class.context_handlers[context] || self.class.context_to_action && begin
|
||||||
|
action_name = context.to_s
|
||||||
|
unless action_method?(action_name)
|
||||||
|
raise AbstractController::ActionNotFound,
|
||||||
|
"The action '#{action_name}' could not be set from context " \
|
||||||
|
"for #{self.class.name}. " \
|
||||||
|
'context_to_action! supports only action methods for security reasons. ' \
|
||||||
|
'If you need to call this action use context_handler for it.'
|
||||||
|
end
|
||||||
|
action_name
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
109
spec/telegram/bot/updates_controller/message_context_spec.rb
Обычный файл
109
spec/telegram/bot/updates_controller/message_context_spec.rb
Обычный файл
@@ -0,0 +1,109 @@
|
|||||||
|
RSpec.describe Telegram::Bot::UpdatesController::MessageContext 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: :redirect do
|
||||||
|
self.filter_done = true
|
||||||
|
end
|
||||||
|
|
||||||
|
context_handler do |*args|
|
||||||
|
[:no_context, *args]
|
||||||
|
end
|
||||||
|
|
||||||
|
context_handler :block do |*args|
|
||||||
|
[:block_result, *args]
|
||||||
|
end
|
||||||
|
|
||||||
|
context_handler :redirect
|
||||||
|
context_handler :other_redirect, :redirect
|
||||||
|
|
||||||
|
def redirect(*args)
|
||||||
|
[:method_result, *args]
|
||||||
|
end
|
||||||
|
|
||||||
|
def action(*args)
|
||||||
|
[:action_result, *args]
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def not_action
|
||||||
|
raise 'Should not be called!'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#message' do
|
||||||
|
subject { -> { dispatch } }
|
||||||
|
let(:payload_type) { 'message' }
|
||||||
|
let(:payload) { {'text' => text} }
|
||||||
|
let(:text) { 'asd qwe zxc' }
|
||||||
|
|
||||||
|
context 'when context is not set' do
|
||||||
|
its(:call) { should eq [:no_context, payload] }
|
||||||
|
it { should_not change(controller, :filter_done) }
|
||||||
|
it { should_not change { session[:context] } }
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when context is handled by block' do
|
||||||
|
before { session[:context] = :block }
|
||||||
|
its(:call) { should eq [:block_result, payload] }
|
||||||
|
it { should_not change(controller, :filter_done) }
|
||||||
|
it { should change { session[:context] }.to nil }
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when context is handled by short redirect' do
|
||||||
|
before { session[:context] = :redirect }
|
||||||
|
its(:call) { should eq [:method_result, *text.split] }
|
||||||
|
it { should change(controller, :filter_done).to true }
|
||||||
|
it { should change { session[:context] }.to nil }
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when context is handled by custom redirect' do
|
||||||
|
before { session[:context] = :other_redirect }
|
||||||
|
its(:call) { should eq [:method_result, *text.split] }
|
||||||
|
it { should change(controller, :filter_done).to true }
|
||||||
|
it { should change { session[:context] }.to nil }
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when context is action`s name but not mapped' do
|
||||||
|
before { session[:context] = :action }
|
||||||
|
its(:call) { should eq nil }
|
||||||
|
it { should_not change(controller, :filter_done) }
|
||||||
|
it { should change { session[:context] }.to nil }
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when context_to_action is true' do
|
||||||
|
before { controller_class.context_to_action! }
|
||||||
|
|
||||||
|
context 'when context is action`s name but not mapped' do
|
||||||
|
before { session[:context] = :action }
|
||||||
|
its(:call) { should eq [:action_result, *text.split] }
|
||||||
|
it { should_not change(controller, :filter_done) }
|
||||||
|
it { should change { session[:context] }.to nil }
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when context is invalid' do
|
||||||
|
before { session[:context] = :invalid }
|
||||||
|
it 'raises error and clears context' do
|
||||||
|
expect do
|
||||||
|
should raise_error AbstractController::ActionNotFound
|
||||||
|
end.to change { session[:context] }.to nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when context is private method`s name' do
|
||||||
|
before { session[:context] = :not_action }
|
||||||
|
it 'raises error and clears context' do
|
||||||
|
expect do
|
||||||
|
should raise_error AbstractController::ActionNotFound
|
||||||
|
end.to change { session[:context] }.to nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
Ссылка в новой задаче
Block a user