telegram-bot 0.8.0 → 0.9.0.alpha1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: cd93b336f2b1953328663119cc121ff43ff00fee
4
- data.tar.gz: 7be5d289e11d1fb3c45ac1557923265fa23a35bf
3
+ metadata.gz: 590c2a861f53a992a435eb30c21a1425a1929baf
4
+ data.tar.gz: e21c4caa819c6c7328d948c835bc807ed187d864
5
5
  SHA512:
6
- metadata.gz: 8a28a28b2650f95f1360d6d7e015ea959e6f5e79d9d93c809468db1319087dffd7c5c69294f2f06ef429246b5860267d047a16c6f9f779b583b44fcc5ef52b0a
7
- data.tar.gz: 1af5cc258b0b0e453369b3a52bb3fb5231b568de23b0f132aac24a779c6c557dadbd37d6f487922062c740bc1cd1f285fb655b8837c63ddb391622d8e45436d4
6
+ metadata.gz: 358bd9e8f43cf07a6b8a3714570c3dcfd6d59dd5b3066cd4a20b51f754f0315a9fb9db448d5f65fc0098ac45bb18fbb2b538c9480d7ab31b94b21878a093e71f
7
+ data.tar.gz: 93846f47f24480df1288f51566df3d28d3be89230e974457fab2f4b4f8b604ce44cee97423173fadda47818e19fde3e14e4b8af029425f6289b90d26a6279e96
data/CHANGELOG.md CHANGED
@@ -1,3 +1,7 @@
1
+ # Unreleased
2
+
3
+ - One more description for StaleChat error.
4
+
1
5
  # 0.8.0
2
6
 
3
7
  - Fixed `#reply_with`, now it sets `reply_to_message_id` as it's supposed to.
data/README.md CHANGED
@@ -5,7 +5,8 @@
5
5
  [![Build Status](https://travis-ci.org/telegram-bot-rb/telegram-bot.svg)](https://travis-ci.org/telegram-bot-rb/telegram-bot)
6
6
 
7
7
  Tools for developing bot for Telegram. Best used with Rails, but can be be used in
8
- standalone app. Supposed to be used in webhook-mode in production, and poller-mode
8
+ [standalone app](https://github.com/telegram-bot-rb/telegram-bot/wiki/Non-rails-application).
9
+ Supposed to be used in webhook-mode in production, and poller-mode
9
10
  in development, but you can use poller in production if you want.
10
11
 
11
12
  Package contains:
@@ -16,6 +17,7 @@ Package contains:
16
17
  - Middleware and routes helpers for production env.
17
18
  - Poller with automatic source-reloader for development env.
18
19
  - Rake tasks to update webhook urls.
20
+ - Async requests for Telegram and/or Botan API. Let the queue adapter handle errors!
19
21
 
20
22
  Here is sample [telegram_bot_app](https://github.com/telegram-bot-rb/telegram_bot_app)
21
23
  with session, keyboards and inline queries.
@@ -352,11 +354,11 @@ or just add `botan` key in `secrets.yml`:
352
354
  Access to Botan client with `bot.botan`.
353
355
  Use `bot.botan.track(event, uid, payload)` to track events.
354
356
 
355
- There are some helpers for controllers in `Telegram::Bot::UpdatesController::Botan`:
357
+ There are some helpers for controllers in `Telegram::Bot::Botan::ControllerHelpers`:
356
358
 
357
359
  ```ruby
358
360
  class Telegram::WebhookController < Telegram::Bot::UpdatesController
359
- include Telegram::Bot::UpdatesController::Botan
361
+ include Telegram::Bot::Botan::ControllerHelpers
360
362
 
361
363
  # This will track with event: action_name & data: payload
362
364
  before_action :botan_track_action
@@ -373,6 +375,20 @@ end
373
375
 
374
376
  There is no stubbing for botan clients, so don't set botan token in tests.
375
377
 
378
+ ### Async mode
379
+
380
+ There is built in support for async requests using ActiveJob. Without Rails
381
+ you can implement your own worker class to handle such requests. This allows:
382
+
383
+ - Process updates very fast, without waiting for telegram and botan responses.
384
+ - Handle and retry network and other errors with queue adapter.
385
+ - ???
386
+
387
+ To enable this mode add `async: true` to bot's and botan's config.
388
+ For more information and custom configuration check out
389
+ [docs](http://www.rubydoc.info/github/telegram-bot-rb/telegram-bot/master/Telegram/Bot/Async) or
390
+ [source](https://github.com/telegram-bot-rb/telegram-bot/blob/master/lib/telegram/bot/async.rb).
391
+
376
392
  ## Development
377
393
 
378
394
  After checking out the repo, run `bin/setup` to install dependencies.
data/lib/telegram/bot.rb CHANGED
@@ -13,6 +13,7 @@ module Telegram
13
13
  # check response.
14
14
  class StaleChat < Error
15
15
  DESCRIPTIONS = [
16
+ 'Bot was blocked',
16
17
  'bot was kicked',
17
18
  "can't write to",
18
19
  'group chat is deactivated',
@@ -26,9 +27,12 @@ module Telegram
26
27
  end
27
28
  end
28
29
 
30
+ autoload :Async, 'telegram/bot/async'
29
31
  autoload :Botan, 'telegram/bot/botan'
30
32
  autoload :Client, 'telegram/bot/client'
31
33
  autoload :ClientStub, 'telegram/bot/client_stub'
34
+ autoload :DebugClient, 'telegram/bot/debug_client'
35
+ autoload :Initializers, 'telegram/bot/initializers'
32
36
  autoload :Middleware, 'telegram/bot/middleware'
33
37
  autoload :UpdatesController, 'telegram/bot/updates_controller'
34
38
  autoload :UpdatesPoller, 'telegram/bot/updates_poller'
@@ -0,0 +1,138 @@
1
+ module Telegram
2
+ module Bot
3
+ # Telegram & Botan clients can perform requests in async way with
4
+ # any job adapter (ActiveJob by default). Using Rails you don't need any
5
+ # additional configuration. However you may want to enable async requests
6
+ # by default with `async: true` in `secrets.yml`. Botan client doesn't inherit
7
+ # async setting from client and must be configured separately.
8
+ #
9
+ # telegram:
10
+ # bots:
11
+ # chat_async:
12
+ # token: secret
13
+ # async: true # enable async mode for client
14
+ # botan: botan_token # in this way botan will not be async
15
+ # botan: # in this way - it's in async mode
16
+ # token: botan_token
17
+ # async: true
18
+ #
19
+ # Without Rails To start using async requests
20
+ # initialize client with `id` kwarg and make sure the client is
21
+ # accessible via `Teletgram.bots[id]` in job worker. Or just use
22
+ # `Telegram.bots_config=` for configuration.
23
+ #
24
+ # Being in async mode `#request` enqueues job instead to perform
25
+ # http request instead of performing it directly.
26
+ # Async behavior is controlled with `#async=` writer
27
+ # and can be enabled/disabled for the block with `#async`:
28
+ #
29
+ # client = Telegram::Bot::Client.new(**config, async: true)
30
+ # client.send_message(message)
31
+ # client.async(false) { client.send_message(other_one) }
32
+ #
33
+ # It can be set with custom job class or classname. By default it defines
34
+ # job classes for every client class, inherited from ApplicationRecord, which
35
+ # can be accessed via `.default_async_job`. You can integrate it with any
36
+ # other job provider by defining a class with `.perform_later(bot_id, *args)`
37
+ # method. See Async::Job for implemetation.
38
+ module Async
39
+ module Job
40
+ class << self
41
+ def included(base)
42
+ base.singleton_class.send :attr_accessor, :client_class
43
+ end
44
+ end
45
+
46
+ def perform(client_id, *args)
47
+ client = self.class.client_class.wrap(client_id.to_sym)
48
+ client.async(false) { client.request(*args) }
49
+ end
50
+ end
51
+
52
+ module ClassMethods
53
+ def default_async_job
54
+ @default_async_job ||= begin
55
+ begin
56
+ ApplicationJob
57
+ rescue NameError
58
+ raise 'Define ApplicationJob class or setup #async= with custom job class'
59
+ end
60
+ klass = Class.new(ApplicationJob) { include Job }
61
+ klass.client_class = self
62
+ const_set(:AsyncJob, klass)
63
+ end
64
+ end
65
+
66
+ # This is used in specs.
67
+ def default_async_job=(val)
68
+ @default_async_job = val
69
+ remove_const(:AsyncJob) if const_defined?(:AsyncJob, false)
70
+ end
71
+
72
+ # Prepares argments for async job. ActiveJob doesn't support
73
+ # Symbol in argumens. Also we can encode json bodies only once here,
74
+ # so it would not be unnecessarily serialized-deserialized.
75
+ #
76
+ # This is stub method, which returns input. Every client class
77
+ # must prepare args itself.
78
+ def prepare_async_args(*args)
79
+ args
80
+ end
81
+ end
82
+
83
+ class << self
84
+ def prepended(base)
85
+ base.extend(ClassMethods)
86
+ end
87
+
88
+ # Transforms symbols to strings in hash values.
89
+ def prepare_hash(hash)
90
+ return hash unless hash.is_a?(Hash)
91
+ hash = hash.dup
92
+ hash.each { |key, val| hash[key] = val.to_s if val.is_a?(Symbol) }
93
+ end
94
+ end
95
+
96
+ attr_reader :id
97
+
98
+ def initialize(*, id: nil, async: nil, **options)
99
+ @id = id
100
+ self.async = async
101
+ super
102
+ end
103
+
104
+ # Sets `@async` to `self.class.default_async_job` if `true` is given
105
+ # or uses given value.
106
+ # Pass custom job class to perform async calls with.
107
+ def async=(val)
108
+ @async =
109
+ case val
110
+ when true then self.class.default_async_job
111
+ when String then const_get(val)
112
+ else val
113
+ end
114
+ end
115
+
116
+ # Returns value of `@async` if no block is given. Otherwise sets this value
117
+ # for a block.
118
+ def async(val = true)
119
+ return @async unless block_given?
120
+ begin
121
+ old_val = @async
122
+ self.async = val
123
+ yield
124
+ ensure
125
+ @async = old_val
126
+ end
127
+ end
128
+
129
+ # Uses job if #async is set.
130
+ def request(*args)
131
+ job_class = async
132
+ return super unless job_class
133
+ raise 'Can not enqueue job without client id' unless id
134
+ job_class.perform_later(id.to_s, *self.class.prepare_async_args(*args))
135
+ end
136
+ end
137
+ end
138
+ end
@@ -3,24 +3,37 @@ module Telegram
3
3
  class Botan
4
4
  TRACK_URI = 'https://api.botan.io/track'.freeze
5
5
 
6
+ autoload :ClientHelpers, 'telegram/bot/botan/client_helpers'
7
+ autoload :ControllerHelpers, 'telegram/bot/botan/controller_helpers'
6
8
  class Error < Bot::Error; end
7
9
 
10
+ extend Initializers
11
+ prepend Async
8
12
  include DebugClient
9
13
 
14
+ class << self
15
+ def by_id(id)
16
+ Telegram.botans[id]
17
+ end
18
+
19
+ def prepare_async_args(method, uri, query = {}, body = nil)
20
+ [method.to_s, uri.to_s, Async.prepare_hash(query), body]
21
+ end
22
+ end
23
+
10
24
  attr_reader :client, :token
11
25
 
12
- def initialize(token)
26
+ def initialize(token = nil, **options)
13
27
  @client = HTTPClient.new
14
- @token = token
28
+ @token = token || options[:token]
15
29
  end
16
30
 
17
31
  def track(event, uid, payload = {})
18
- res = http_request(
19
- :post,
20
- TRACK_URI,
21
- {token: token, name: event, uid: uid},
22
- payload.to_json,
23
- )
32
+ request(:post, TRACK_URI, {name: event, uid: uid}, payload.to_json)
33
+ end
34
+
35
+ def request(method, uri, query = {}, body = nil)
36
+ res = http_request(method, uri, query.merge(token: token), body)
24
37
  status = res.status
25
38
  return JSON.parse(res.body) if 300 > status
26
39
  result = JSON.parse(res.body) rescue nil # rubocop:disable RescueModifier
@@ -31,6 +44,10 @@ module Telegram
31
44
  def http_request(method, uri, query, body)
32
45
  client.request(method, uri, query, body)
33
46
  end
47
+
48
+ def inspect
49
+ "#<#{self.class.name}##{object_id}(#{@id})>"
50
+ end
34
51
  end
35
52
  end
36
53
  end
@@ -0,0 +1,15 @@
1
+ module Telegram
2
+ module Bot
3
+ class Botan
4
+ # Helpers for botan.io metrics.
5
+ module ClientHelpers
6
+ attr_reader :botan
7
+
8
+ def initialize(*, botan: nil, **)
9
+ super
10
+ @botan = Botan.wrap(botan, id: id) if botan
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -1,8 +1,8 @@
1
1
  module Telegram
2
2
  module Bot
3
- class UpdatesController
3
+ class Botan
4
4
  # Helpers for botan.io metrics.
5
- module Botan
5
+ module ControllerHelpers
6
6
  class MissingFrom < Error; end
7
7
 
8
8
  protected
@@ -2,7 +2,6 @@ require 'json'
2
2
  require 'httpclient'
3
3
  require 'active_support/core_ext/string/inflections'
4
4
  require 'active_support/core_ext/hash/keys'
5
- require 'telegram/bot/debug_client'
6
5
 
7
6
  module Telegram
8
7
  module Bot
@@ -10,23 +9,14 @@ module Telegram
10
9
  URL_TEMPLATE = 'https://api.telegram.org/bot%s/'.freeze
11
10
 
12
11
  autoload :TypedResponse, 'telegram/bot/client/typed_response'
12
+ extend Initializers
13
+ prepend Async
14
+ prepend Botan::ClientHelpers
13
15
  include DebugClient
14
16
 
15
17
  class << self
16
- # Accepts different options to initialize bot.
17
- def wrap(input)
18
- case input
19
- when self then input
20
- when Array then input.map(&method(__callee__))
21
- when Hash then
22
- input = input.stringify_keys
23
- new input['token'], input['username'], botan: input['botan']
24
- when Symbol
25
- Telegram.bots[input] or
26
- raise "Bot #{input} not configured, check Telegram.bots_config."
27
- else
28
- new(input)
29
- end
18
+ def by_id(id)
19
+ Telegram.bots[id]
30
20
  end
31
21
 
32
22
  # Prepend TypedResponse module.
@@ -41,16 +31,19 @@ module Telegram
41
31
  body[k] = val.to_json if val.is_a?(Hash) || val.is_a?(Array)
42
32
  end
43
33
  end
34
+
35
+ def prepare_async_args(action, body = {})
36
+ [action.to_s, Async.prepare_hash(prepare_body(body))]
37
+ end
44
38
  end
45
39
 
46
- attr_reader :client, :token, :username, :base_uri, :botan
40
+ attr_reader :client, :token, :username, :base_uri
47
41
 
48
- def initialize(token, username = nil, botan: nil)
42
+ def initialize(token = nil, username = nil, **options)
49
43
  @client = HTTPClient.new
50
- @token = token
51
- @username = username
52
- @base_uri = format URL_TEMPLATE, token
53
- @botan = Botan.new(botan) if botan
44
+ @token = token || options[:token]
45
+ @username = username || options[:username]
46
+ @base_uri = format URL_TEMPLATE, self.token
54
47
  end
55
48
 
56
49
  def request(action, body = {}) # rubocop:disable PerceivedComplexity
@@ -9,7 +9,7 @@ module Telegram
9
9
  if self == ClientStub || !ClientStub.stub_all?
10
10
  super
11
11
  else
12
- ClientStub.new(args[1])
12
+ ClientStub.new(*args)
13
13
  end
14
14
  end
15
15
  end
@@ -34,8 +34,8 @@ module Telegram
34
34
  end
35
35
  end
36
36
 
37
- def initialize(username = nil)
38
- @username = username
37
+ def initialize(token = nil, username = nil, **options)
38
+ @username = username || options[:username] || token
39
39
  reset
40
40
  end
41
41
 
@@ -26,7 +26,9 @@ module Telegram
26
26
 
27
27
  # Hash of bots made with bots_config.
28
28
  def bots
29
- @bots ||= bots_config.transform_values(&Client.method(:wrap))
29
+ @bots ||= bots_config.each_with_object({}) do |(id, config), h|
30
+ h[id] = Client.wrap(config, id: id)
31
+ end
30
32
  end
31
33
 
32
34
  # Default bot.
@@ -34,17 +36,26 @@ module Telegram
34
36
  @bot ||= bots[:default]
35
37
  end
36
38
 
39
+ # Hash of botan clients made from #bots.
40
+ def botans
41
+ @botans ||= bots.transform_values(&:botan)
42
+ end
43
+
37
44
  # Returns config for .bots method. By default uses `telegram['bots']` section
38
45
  # from `secrets.yml` merging `telegram['bot']` at `:default` key.
39
46
  #
40
47
  # Can be overwritten with .bots_config=
41
48
  def bots_config
42
- return @bots_config if @bots_config
43
- telegram_config = Rails.application.secrets[:telegram]
44
- (telegram_config['bots'] || {}).symbolize_keys.tap do |config|
45
- default = telegram_config['bot']
46
- config[:default] = default if default
47
- end
49
+ @bots_config ||=
50
+ if defined?(Rails)
51
+ telegram_config = Rails.application.secrets[:telegram] || {}
52
+ (telegram_config['bots'] || {}).symbolize_keys.tap do |config|
53
+ default = telegram_config['bot']
54
+ config[:default] = default if default
55
+ end
56
+ else
57
+ {}
58
+ end
48
59
  end
49
60
 
50
61
  # Resets all cached bots and their configs.
@@ -52,6 +63,7 @@ module Telegram
52
63
  @bots = nil
53
64
  @bot = nil
54
65
  @bots_config = nil
66
+ @botans = nil
55
67
  end
56
68
  end
57
69
  end
@@ -0,0 +1,19 @@
1
+ module Telegram
2
+ module Bot
3
+ module Initializers
4
+ # Accepts different options to initialize bot.
5
+ def wrap(input, **options)
6
+ case input
7
+ when Symbol then by_id(input) or raise "#{name} #{input.inspect} not configured"
8
+ when self then input
9
+ when Hash then new(**input.symbolize_keys, **options)
10
+ else new(input, **options)
11
+ end
12
+ end
13
+
14
+ def by_id(_id)
15
+ raise 'Not implemented'
16
+ end
17
+ end
18
+ end
19
+ end
@@ -20,7 +20,7 @@ module Telegram
20
20
  # Replaces colon with underscore so rails don't treat it as
21
21
  # route parameter.
22
22
  def escape_token(token)
23
- token.tr(':', '_')
23
+ token && token.tr(':', '_')
24
24
  end
25
25
  end
26
26
 
@@ -59,7 +59,6 @@ module Telegram
59
59
  require 'telegram/bot/updates_controller/reply_helpers'
60
60
  autoload :CallbackQueyContext, 'telegram/bot/updates_controller/callback_query_context'
61
61
  autoload :MessageContext, 'telegram/bot/updates_controller/message_context'
62
- autoload :Botan, 'telegram/bot/updates_controller/botan'
63
62
 
64
63
  include AbstractController::Callbacks
65
64
  # Redefine callbacks with default terminator.
@@ -64,7 +64,7 @@ module Telegram
64
64
  end
65
65
 
66
66
  def fetch_updates
67
- response = bot.get_updates(offset: offset, timeout: timeout)
67
+ response = bot.async(false) { bot.get_updates(offset: offset, timeout: timeout) }
68
68
  return unless response['ok'] && response['result'].any?
69
69
  reload! do
70
70
  response['result'].each do |update|
@@ -1,6 +1,6 @@
1
1
  module Telegram
2
2
  module Bot
3
- VERSION = '0.8.0'.freeze
3
+ VERSION = '0.9.0.alpha1'.freeze
4
4
 
5
5
  def self.gem_version
6
6
  Gem::Version.new VERSION
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: telegram-bot
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.0
4
+ version: 0.9.0.alpha1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Max Melentiev
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2016-06-02 00:00:00.000000000 Z
11
+ date: 2016-08-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -114,17 +114,20 @@ files:
114
114
  - bin/setup
115
115
  - lib/tasks/telegram-bot.rake
116
116
  - lib/telegram/bot.rb
117
+ - lib/telegram/bot/async.rb
117
118
  - lib/telegram/bot/botan.rb
119
+ - lib/telegram/bot/botan/client_helpers.rb
120
+ - lib/telegram/bot/botan/controller_helpers.rb
118
121
  - lib/telegram/bot/client.rb
119
122
  - lib/telegram/bot/client/typed_response.rb
120
123
  - lib/telegram/bot/client_stub.rb
121
124
  - lib/telegram/bot/config_methods.rb
122
125
  - lib/telegram/bot/debug_client.rb
126
+ - lib/telegram/bot/initializers.rb
123
127
  - lib/telegram/bot/middleware.rb
124
128
  - lib/telegram/bot/railtie.rb
125
129
  - lib/telegram/bot/routes_helper.rb
126
130
  - lib/telegram/bot/updates_controller.rb
127
- - lib/telegram/bot/updates_controller/botan.rb
128
131
  - lib/telegram/bot/updates_controller/callback_query_context.rb
129
132
  - lib/telegram/bot/updates_controller/instrumentation.rb
130
133
  - lib/telegram/bot/updates_controller/log_subscriber.rb
@@ -152,9 +155,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
152
155
  version: '2.0'
153
156
  required_rubygems_version: !ruby/object:Gem::Requirement
154
157
  requirements:
155
- - - ">="
158
+ - - ">"
156
159
  - !ruby/object:Gem::Version
157
- version: '0'
160
+ version: 1.3.1
158
161
  requirements: []
159
162
  rubyforge_project:
160
163
  rubygems_version: 2.5.1