зеркало из
https://github.com/glebtv/telegram-bot.git
synced 2026-09-04 02:05:52 +03:00
Async requests with ActiveJob
Этот коммит содержится в:
138
lib/telegram/bot/async.rb
Обычный файл
138
lib/telegram/bot/async.rb
Обычный файл
@@ -0,0 +1,138 @@
|
||||
module Telegram
|
||||
module Bot
|
||||
# Telegram & Botan clients can perform requests in async way with
|
||||
# any job adapter (ActiveJob by default). Using Rails you don't need any
|
||||
# additional configuration. However you may want to enable async requests
|
||||
# by default with `async: true` in `secrets.yml`. Botan client doesn't inherit
|
||||
# async setting from client and must be configured separately.
|
||||
#
|
||||
# telegram:
|
||||
# bots:
|
||||
# chat_async:
|
||||
# token: secret
|
||||
# async: true # enable async mode for client
|
||||
# botan: botan_token # in this way botan will not be async
|
||||
# botan: # in this way - it's in async mode
|
||||
# token: botan_token
|
||||
# async: true
|
||||
#
|
||||
# Without Rails To start using async requests
|
||||
# initialize client with `id` kwarg and make sure the client is
|
||||
# accessible via `Teletgram.bots[id]` in job worker. Or just use
|
||||
# `Telegram.bots_config=` for configuration.
|
||||
#
|
||||
# Being in async mode `#request` enqueues job instead to perform
|
||||
# http request instead of performing it directly.
|
||||
# Async behavior is controlled with `#async=` writer
|
||||
# and can be enabled/disabled for the block with `#async`:
|
||||
#
|
||||
# client = Telegram::Bot::Client.new(**config, async: true)
|
||||
# client.send_message(message)
|
||||
# client.async(false) { client.send_message(other_one) }
|
||||
#
|
||||
# It can be set with custom job class or classname. By default it defines
|
||||
# job classes for every client class, inherited from ApplicationRecord, which
|
||||
# can be accessed via `.default_async_job`. You can integrate it with any
|
||||
# other job provider by defining a class with `.perform_later(bot_id, *args)`
|
||||
# method. See Async::Job for implemetation.
|
||||
module Async
|
||||
module Job
|
||||
class << self
|
||||
def included(base)
|
||||
base.singleton_class.send :attr_accessor, :client_class
|
||||
end
|
||||
end
|
||||
|
||||
def perform(client_id, *args)
|
||||
client = self.class.client_class.wrap(client_id.to_sym)
|
||||
client.async(false) { client.request(*args) }
|
||||
end
|
||||
end
|
||||
|
||||
module ClassMethods
|
||||
def default_async_job
|
||||
@default_async_job ||= begin
|
||||
begin
|
||||
ApplicationJob
|
||||
rescue NameError
|
||||
raise 'Define ApplicationJob class or setup #async= with custom job class'
|
||||
end
|
||||
klass = Class.new(ApplicationJob) { include Job }
|
||||
klass.client_class = self
|
||||
const_set(:AsyncJob, klass)
|
||||
end
|
||||
end
|
||||
|
||||
# This is used in specs.
|
||||
def default_async_job=(val)
|
||||
@default_async_job = val
|
||||
remove_const(:AsyncJob) if const_defined?(:AsyncJob, false)
|
||||
end
|
||||
|
||||
# Prepares argments for async job. ActiveJob doesn't support
|
||||
# Symbol in argumens. Also we can encode json bodies only once here,
|
||||
# so it would not be unnecessarily serialized-deserialized.
|
||||
#
|
||||
# This is stub method, which returns input. Every client class
|
||||
# must prepare args itself.
|
||||
def prepare_async_args(*args)
|
||||
args
|
||||
end
|
||||
end
|
||||
|
||||
class << self
|
||||
def prepended(base)
|
||||
base.extend(ClassMethods)
|
||||
end
|
||||
|
||||
# Transforms symbols to strings in hash values.
|
||||
def prepare_hash(hash)
|
||||
return hash unless hash.is_a?(Hash)
|
||||
hash = hash.dup
|
||||
hash.each { |key, val| hash[key] = val.to_s if val.is_a?(Symbol) }
|
||||
end
|
||||
end
|
||||
|
||||
attr_reader :id
|
||||
|
||||
def initialize(*, id: nil, async: nil, **options)
|
||||
@id = id
|
||||
self.async = async
|
||||
super
|
||||
end
|
||||
|
||||
# Sets `@async` to `self.class.default_async_job` if `true` is given
|
||||
# or uses given value.
|
||||
# Pass custom job class to perform async calls with.
|
||||
def async=(val)
|
||||
@async =
|
||||
case val
|
||||
when true then self.class.default_async_job
|
||||
when String then const_get(val)
|
||||
else val
|
||||
end
|
||||
end
|
||||
|
||||
# Returns value of `@async` if no block is given. Otherwise sets this value
|
||||
# for a block.
|
||||
def async(val = true)
|
||||
return @async unless block_given?
|
||||
begin
|
||||
old_val = @async
|
||||
self.async = val
|
||||
yield
|
||||
ensure
|
||||
@async = old_val
|
||||
end
|
||||
end
|
||||
|
||||
# Uses job if #async is set.
|
||||
def request(*args)
|
||||
job_class = async
|
||||
return super unless job_class
|
||||
raise 'Can not enqueue job without client id' unless id
|
||||
job_class.perform_later(id.to_s, *self.class.prepare_async_args(*args))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -8,12 +8,17 @@ module Telegram
|
||||
class Error < Bot::Error; end
|
||||
|
||||
extend Initializers
|
||||
prepend Async
|
||||
include DebugClient
|
||||
|
||||
class << self
|
||||
def by_id(id)
|
||||
Telegram.botans[id]
|
||||
end
|
||||
|
||||
def prepare_async_args(method, uri, query = {}, body = nil)
|
||||
[method.to_s, uri.to_s, Async.prepare_hash(query), body]
|
||||
end
|
||||
end
|
||||
|
||||
attr_reader :client, :token
|
||||
@@ -24,12 +29,11 @@ module Telegram
|
||||
end
|
||||
|
||||
def track(event, uid, payload = {})
|
||||
res = http_request(
|
||||
:post,
|
||||
TRACK_URI,
|
||||
{token: token, name: event, uid: uid},
|
||||
payload.to_json,
|
||||
)
|
||||
request(:post, TRACK_URI, {name: event, uid: uid}, payload.to_json)
|
||||
end
|
||||
|
||||
def request(method, uri, query = {}, body = nil)
|
||||
res = http_request(method, uri, query.merge(token: token), body)
|
||||
status = res.status
|
||||
return JSON.parse(res.body) if 300 > status
|
||||
result = JSON.parse(res.body) rescue nil # rubocop:disable RescueModifier
|
||||
@@ -40,6 +44,10 @@ module Telegram
|
||||
def http_request(method, uri, query, body)
|
||||
client.request(method, uri, query, body)
|
||||
end
|
||||
|
||||
def inspect
|
||||
"#<#{self.class.name}##{object_id}(#{@id})>"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -7,7 +7,7 @@ module Telegram
|
||||
|
||||
def initialize(*, botan: nil, **)
|
||||
super
|
||||
@botan = Botan.wrap(botan) if botan
|
||||
@botan = Botan.wrap(botan, id: id) if botan
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -10,6 +10,7 @@ module Telegram
|
||||
|
||||
autoload :TypedResponse, 'telegram/bot/client/typed_response'
|
||||
extend Initializers
|
||||
prepend Async
|
||||
prepend Botan::ClientHelpers
|
||||
include DebugClient
|
||||
|
||||
@@ -30,6 +31,10 @@ module Telegram
|
||||
body[k] = val.to_json if val.is_a?(Hash) || val.is_a?(Array)
|
||||
end
|
||||
end
|
||||
|
||||
def prepare_async_args(action, body = {})
|
||||
[action.to_s, Async.prepare_hash(prepare_body(body))]
|
||||
end
|
||||
end
|
||||
|
||||
attr_reader :client, :token, :username, :base_uri
|
||||
|
||||
@@ -26,7 +26,9 @@ module Telegram
|
||||
|
||||
# Hash of bots made with bots_config.
|
||||
def bots
|
||||
@bots ||= bots_config.transform_values(&Client.method(:wrap))
|
||||
@bots ||= bots_config.each_with_object({}) do |(id, config), h|
|
||||
h[id] = Client.wrap(config, id: id)
|
||||
end
|
||||
end
|
||||
|
||||
# Default bot.
|
||||
@@ -44,12 +46,16 @@ module Telegram
|
||||
#
|
||||
# Can be overwritten with .bots_config=
|
||||
def bots_config
|
||||
return @bots_config if @bots_config
|
||||
telegram_config = Rails.application.secrets[:telegram]
|
||||
(telegram_config['bots'] || {}).symbolize_keys.tap do |config|
|
||||
default = telegram_config['bot']
|
||||
config[:default] = default if default
|
||||
end
|
||||
@bots_config ||=
|
||||
if defined?(Rails)
|
||||
telegram_config = Rails.application.secrets[:telegram] || {}
|
||||
(telegram_config['bots'] || {}).symbolize_keys.tap do |config|
|
||||
default = telegram_config['bot']
|
||||
config[:default] = default if default
|
||||
end
|
||||
else
|
||||
{}
|
||||
end
|
||||
end
|
||||
|
||||
# Resets all cached bots and their configs.
|
||||
|
||||
@@ -64,7 +64,7 @@ module Telegram
|
||||
end
|
||||
|
||||
def fetch_updates
|
||||
response = bot.get_updates(offset: offset, timeout: timeout)
|
||||
response = bot.async(false) { bot.get_updates(offset: offset, timeout: timeout) }
|
||||
return unless response['ok'] && response['result'].any?
|
||||
reload! do
|
||||
response['result'].each do |update|
|
||||
|
||||
Ссылка в новой задаче
Block a user