telegram-bot 0.8.0 → 0.9.0.alpha1

Sign up to get free protection for your applications and to get access to all the features.
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