1
0
зеркало из https://github.com/glebtv/telegram-bot.git synced 2026-09-03 17:55:52 +03:00

Сравнить коммиты

..

7 Коммитов

Автор SHA1 Сообщение Дата
Max Melentiev
da93abaaf7 v0.12.1 2017-10-06 11:23:56 +03:00
printercu
0ae17fb34e Merge pull request #45 from telegram-bot-rb/update_sessions
Update sessions
2017-10-06 11:19:01 +03:00
Max Melentiev
7e41bd4f9b Allow use different sessions for MessageContext 2017-10-06 11:10:05 +03:00
Max Melentiev
e529a79339 Don't use Rails.cache as fallback for session_store
It's NullStore in development by default so session does not work,
and indeed should not be dependent.
2017-10-06 11:07:11 +03:00
Max Melentiev
ef4f3caa5f Make #session raise error when store is not configured
Add more explanation on sessions in readme
2017-10-06 11:05:25 +03:00
Max Melentiev
df7039326d Run set_webhook task always in sync way
Fixes telegram-bot-rb/telegram_bot_app#7
2017-10-05 22:11:22 +03:00
Max Melentiev
4f47506e22 Fix typo in readme 2017-08-11 15:10:05 +03:00
11 изменённых файлов: 134 добавлений и 25 удалений

Просмотреть файл

@@ -1,5 +1,12 @@
# Unreleased # Unreleased
# 0.12.1
- Fix `set_webhook` rake task for async bots with self-issued certificates.
- Make `#session` raise error when store is not configured,
don't use Rails.cache as fallback for session_store.
- Allow use different sessions for MessageContext.
# 0.12.0 # 0.12.0
- New API methods and payload types (up to Bot API 3.2). - New API methods and payload types (up to Bot API 3.2).

Просмотреть файл

@@ -24,7 +24,7 @@ Here is sample [telegram_bot_app](https://github.com/telegram-bot-rb/telegram_bo
with session, keyboards and inline queries. with session, keyboards and inline queries.
Run it on your local machine in 1 minute! Run it on your local machine in 1 minute!
And here is [app teamplate](https://github.com/telegram-bot-rb/rails_template) And here is [app template](https://github.com/telegram-bot-rb/rails_template)
to generate clean app in seconds. to generate clean app in seconds.
Examples and cookbook in [wiki](https://github.com/telegram-bot-rb/telegram-bot/wiki). Examples and cookbook in [wiki](https://github.com/telegram-bot-rb/telegram-bot/wiki).
@@ -199,12 +199,33 @@ end
#### Session #### Session
There is support for sessions using `ActiveSupport::Cache` stores. This API is very close to ActiveController's session API, but works different
under the hood. Cookies can not be used to store session id or
whole session (like CookieStore does). So it uses key-value store and `session_key`
method to build identifier from update.
Store can be one of numerous `ActiveSupport::Cache` stores.
While `:file_store` is suitable for development and single-server deployments
without heavy load, it doesn't scale well. Key-value databases with persistance
like Redis are more appropriate for production use.
```ruby ```ruby
# configure store in env files: # In rails app store can be configured in env files:
config.telegram_updates_controller.session_store = :redis_store, {expires_in: 1.month} config.telegram_updates_controller.session_store = :redis_store, {expires_in: 1.month}
# In other app it can be done for all controllers with:
Telegram::Bot::UpdatesController.session_store = :redis_store, {expires_in: 1.month}
# or for specific one:
OneOfUpdatesController.session_store = :redis_store, {expires_in: 1.month}
```
Default session id is made from bot's username and `(from || chat)['id']`.
It means that session will be the same for updates from user in every chat,
and different for every user in the same group chat.
To change this behavior you can override `session_key` method, or even
define multiple sessions in single controller. For details see `Session` module.
```ruby
class Telegram::WebhookController < Telegram::Bot::UpdatesController class Telegram::WebhookController < Telegram::Bot::UpdatesController
include Telegram::Bot::UpdatesController::Session include Telegram::Bot::UpdatesController::Session
# or just shortcut: # or just shortcut:
@@ -222,12 +243,16 @@ class Telegram::WebhookController < Telegram::Bot::UpdatesController
end end
private private
# By default it uses bot's username and user's id as a session key.
# Chat's id is used only when `from` field is empty. # In this case session will persist for user only in specific chat.
# Override `session_key` method to change this behavior. # Same user in other chat will have different session.
def session_key def session_key
# In this case session will persist for user only in specific chat: "#{bot.username}:#{chat['id']}:#{from['id']}" if chat && from
"#{bot.username}:#{chat['id']}:#{from['id']}" end
# This session will be the same for all updates in chat.
def chat_session
@_chat_session ||= self.class.build_session(chat && "#{bot.username}:#{chat['id']}")
end end
end end
``` ```

Просмотреть файл

@@ -21,7 +21,7 @@ namespace :telegram do
route_name = Telegram::Bot::RoutesHelper.route_name_for_bot(bot) route_name = Telegram::Bot::RoutesHelper.route_name_for_bot(bot)
url = routes.send("#{route_name}_url") url = routes.send("#{route_name}_url")
puts "Setting webhook for #{key}..." puts "Setting webhook for #{key}..."
bot.set_webhook(url: url, certificate: cert) bot.async(false) { bot.set_webhook(url: url, certificate: cert) }
end end
end end
end end

Просмотреть файл

@@ -18,7 +18,7 @@ module Telegram
ActiveSupport.on_load('telegram.bot.updates_controller') do ActiveSupport.on_load('telegram.bot.updates_controller') do
self.logger = options.logger || Rails.logger self.logger = options.logger || Rails.logger
self.session_store = options.session_store || Rails.cache self.session_store = options.session_store if options.session_store
end end
end end

Просмотреть файл

@@ -7,14 +7,11 @@ module Telegram
include Session include Session
included do
# As we use before_action context is cleared anyway,
# no matter we used it or not.
singleton_class.send :attr_reader, :context_handlers, :context_to_action
@context_handlers = {}
end
module ClassMethods module ClassMethods
def context_handlers
@_context_handlers ||= {}
end
# Registers handler for context. # Registers handler for context.
# #
# context_handler :rename do |*| # context_handler :rename do |*|
@@ -39,6 +36,8 @@ module Telegram
context_handlers[context] = action || context context_handlers[context] = action || context
end end
attr_reader :context_to_action
# Use it to use context value as action name for all contexts # Use it to use context value as action name for all contexts
# which miss handlers. # which miss handlers.
# For security reasons it supports only action methods and will # For security reasons it supports only action methods and will
@@ -59,10 +58,16 @@ module Telegram
# according to previous request. # according to previous request.
attr_reader :context attr_reader :context
# Controller may have multiple sessions, let it be possible
# to select session for message context.
def message_context_session
session
end
# Fetches context and finds handler for it. If message has new command, # Fetches context and finds handler for it. If message has new command,
# it has higher priority than contextual action. # it has higher priority than contextual action.
def action_for_message def action_for_message
val = session.delete(:context) val = message_context_session.delete(:context)
@context = val && val.to_sym @context = val && val.to_sym
super || context && begin super || context && begin
handler = handler_for_context handler = handler_for_context
@@ -72,7 +77,7 @@ module Telegram
# Save context for the next request. # Save context for the next request.
def save_context(context) def save_context(context)
session[:context] = context message_context_session[:context] = context
end end
def handler_for_context def handler_for_context

Просмотреть файл

@@ -8,19 +8,26 @@ module Telegram
module Session module Session
extend ActiveSupport::Concern extend ActiveSupport::Concern
module ClassMethods
# Builds session with given key and optional store (default to session_store).
# This way it's easier to define multiple custom sessions,
# ex. one for group chat and one for user.
def build_session(key, store = session_store)
raise 'session_store is not configured' unless store
key ? SessionHash.new(store, key) : NullSessionHash.new
end
end
def process_action(*) def process_action(*)
super super
ensure ensure
session.commit session.commit if @_session
end end
protected protected
def session def session
@_session ||= begin @_session ||= self.class.build_session(session_key)
key = session_key
key ? SessionHash.new(self.class.session_store, key) : NullSessionHash.new
end
end end
def session_key def session_key

Просмотреть файл

@@ -1,6 +1,6 @@
module Telegram module Telegram
module Bot module Bot
VERSION = '0.12.0'.freeze VERSION = '0.12.1'.freeze
def self.gem_version def self.gem_version
Gem::Version.new VERSION Gem::Version.new VERSION

Просмотреть файл

@@ -5,4 +5,9 @@ RSpec.describe DefaultBotController, :telegram_bot, type: :request do
subject { -> { dispatch_command :start } } subject { -> { dispatch_command :start } }
it { should respond_with_message 'from default' } it { should respond_with_message 'from default' }
end end
describe '#load_session' do
subject { -> { dispatch_command :load_session } }
it { should_not raise_error }
end
end end

Просмотреть файл

@@ -6,4 +6,9 @@ RSpec.describe OtherBotController, :telegram_bot, type: :request do
subject { -> { dispatch_command :start } } subject { -> { dispatch_command :start } }
it { should respond_with_message 'from other' } it { should respond_with_message 'from other' }
end end
describe '#load_session' do
subject { -> { dispatch_command :load_session } }
it { should raise_error(/session_store is not configured/) }
end
end end

Просмотреть файл

@@ -4,6 +4,7 @@ require 'action_dispatch'
require 'action_dispatch/testing/integration' require 'action_dispatch/testing/integration'
require 'rails' require 'rails'
require 'telegram/bot/railtie'
require 'rspec/rails/adapters' require 'rspec/rails/adapters'
require 'rspec/rails/fixture_support' require 'rspec/rails/fixture_support'
require 'rspec/rails/example/rails_example_group' require 'rspec/rails/example/rails_example_group'
@@ -13,6 +14,7 @@ ENV['RAILS_ENV'] = 'test'
class TestApplication < Rails::Application class TestApplication < Rails::Application
config.eager_load = false config.eager_load = false
config.log_level = :debug config.log_level = :debug
config.action_dispatch.show_exceptions = false
secrets[:secret_key_base] = 'test' secrets[:secret_key_base] = 'test'
secrets[:telegram] = { secrets[:telegram] = {
bot: 'default_token', bot: 'default_token',
@@ -34,6 +36,18 @@ Rails.application.initialize!
Object.const_set("#{bot_name}_bot_controller".camelize, controller) Object.const_set("#{bot_name}_bot_controller".camelize, controller)
end end
[DefaultBotController, OtherBotController].each do |klass|
klass.class_eval do
use_session!
define_method :load_session do |*|
session[:test]
end
end
end
DefaultBotController.session_store = :memory_store
RSpec.configure do |config| RSpec.configure do |config|
config.include RSpec::Rails::RequestExampleGroup, type: :request config.include RSpec::Rails::RequestExampleGroup, type: :request

Просмотреть файл

@@ -54,4 +54,45 @@ RSpec.describe Telegram::Bot::UpdatesController::Session do
end end
end end
end end
describe '.build_session' do
subject { controller_class.build_session(key, *args) }
let(:key) {}
let(:args) { [] }
it { expect { subject }.to raise_error(/session_store is not configured/) }
shared_examples 'NullSessionHash when key is not present' do |store_proc|
it { should be_instance_of(described_class::NullSessionHash) }
context 'and key is present' do
let(:key) { :test_key }
it 'is valid SessionHash' do
expect(subject).to be_instance_of(described_class::SessionHash)
expect(subject.id).to eq key
expect(subject.instance_variable_get(:@store)).to be(instance_exec(&store_proc))
end
end
end
context 'when store configured' do
before { controller_class.session_store = nil }
include_examples 'NullSessionHash when key is not present',
-> { controller_class.session_store }
end
context 'when store is given' do
let(:args) { [double(:store)] }
include_examples 'NullSessionHash when key is not present', -> { args[0] }
end
end
describe '.session_store=' do
subject { ->(val) { controller_class.session_store = val } }
it 'casts to AS::Cache' do
expect { subject[:null_store] }.to change(controller_class, :session_store).
to(instance_of(ActiveSupport::Cache::NullStore))
expect { subject[nil] }.to change(controller_class, :session_store).
to(instance_of(ActiveSupport::Cache::MemoryStore))
end
end
end end