telegram-bot 0.9.0.alpha2 → 0.9.0

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: 36cad9c75339c3e1119aec4f14645a7cdcb1c868
4
- data.tar.gz: 0817596572b5667c47642cb2ad30dd5e51690a32
3
+ metadata.gz: aac15a5abf98702f6b99efe128d52133aca33901
4
+ data.tar.gz: 18c94d0ee3d719931ad7fe5c848c53afc7c55665
5
5
  SHA512:
6
- metadata.gz: d488af6c423477bc93fe182d73f9b104c45886097896dbd400bd29c2d5eb1455c6237a1eabd206397f9ba5799798b6d1b83936662e655b2606b0998136c5f228
7
- data.tar.gz: 21468fbab556f7c31594e0b68be13f75b51465b6418b075e4441613435bb7dc5fe02a77ef6a3c6241475fd026792d02e55a1b106b0ea89e540a522157a47281c
6
+ metadata.gz: efb85157e8fdaceab87411e9e582f6f4ff22d480ec0a7ac134b4e89a9e43da4e8fd022af541a5280991c0d6b6c998722d424b224ab6fc3c0d68a02da8d9f4c5a
7
+ data.tar.gz: 31218771276a4a3d7920b68acfc6f3676d2778a96f4766a6d78db4a6d4c87a78e63e7fc7da8b117bd7f68042dae4377c28abe98d118e2c5dbbc92a71b41a327a
data/CHANGELOG.md CHANGED
@@ -1,6 +1,12 @@
1
- # Unreleased
1
+ # 0.9.0
2
2
 
3
+ - Async API requests.
3
4
  - One more description for StaleChat error.
5
+ - edit_message_* methods.
6
+ - API methods from 2016-10-03 update
7
+ - Fix typo in module name: CallbackQueyContext -> CallbackQueryContext.
8
+ - Take `chat` from `message` for callback queries
9
+ - RSpec matchers.
4
10
 
5
11
  # 0.8.0
6
12
 
data/README.md CHANGED
@@ -17,7 +17,8 @@ Package contains:
17
17
  - Middleware and routes helpers for production env.
18
18
  - Poller with automatic source-reloader for development env.
19
19
  - Rake tasks to update webhook urls.
20
- - Async requests for Telegram and/or Botan API. Let the queue adapter handle errors!
20
+ - __[Async mode](#async-mode)__ for Telegram and/or Botan API.
21
+ Let the queue adapter handle network errors!
21
22
 
22
23
  Here is sample [telegram_bot_app](https://github.com/telegram-bot-rb/telegram_bot_app)
23
24
  with session, keyboards and inline queries.
@@ -132,6 +133,7 @@ class Telegram::WebhookController < Telegram::Bot::UpdatesController
132
133
  # do_smth_with(data)
133
134
 
134
135
  # There are `chat` & `from` shortcut methods.
136
+ # For callback queries `chat` if taken from `message` when it's available.
135
137
  response = from ? "Hello #{from['username']}!" : 'Hi there!'
136
138
  # There is `respond_with` helper to set `chat_id` from received message:
137
139
  respond_with :message, text: response
@@ -246,7 +248,7 @@ class Telegram::WebhookController < Telegram::Bot::UpdatesController
246
248
  end
247
249
  ```
248
250
 
249
- You can use `CallbackQueyContext` in the similar way to split `#callback_query` into
251
+ You can use `CallbackQueryContext` in the similar way to split `#callback_query` into
250
252
  several specific methods. It doesn't require session support, and takes context from
251
253
  data. If data has a prefix with colon like this `my_ctx:smth...` it'll call
252
254
  `my_ctx_callback_query('smth...')` when there is such action method. Otherwise
@@ -334,6 +336,23 @@ There are also some helpers for controller tests.
334
336
  Check out `telegram/bot/updates_controller/rspec_helpers` and
335
337
  `telegram/bot/updates_controller/testing`.
336
338
 
339
+ Built-in RSpec matchers will help you to write tests fast:
340
+
341
+ ```ruby
342
+ include Telegram::Bot::RSpec::ClientMatchers # no need if you already use controller herlpers
343
+
344
+ expect(&process_update).to send_telegram_message(bot, /msg regexp/, some: :option)
345
+ expect(&process_update).
346
+ to make_telegram_request(bot, :sendMessage, hash_including(text: 'msg text'))
347
+
348
+ # controller specs are even simplier:
349
+ describe '#start' do
350
+ subject { -> { dispatch_message '/start' } }
351
+ it { should respond_with_message(/Hello/) }
352
+ end
353
+ # See sample app for more examples.
354
+ ```
355
+
337
356
  ### Deploying
338
357
 
339
358
  Use `rake telegram:bot:set_webhook` to update webhook url for all configured bots.
@@ -384,11 +403,28 @@ you can implement your own worker class to handle such requests. This allows:
384
403
  - Handle and retry network and other errors with queue adapter.
385
404
  - ???
386
405
 
406
+ Instead of performing request instantly client serializes it, pushes to queue,
407
+ and immediately return control back. The job is then fetched with a worker
408
+ and real API request is performed. And this all is absolutely transparent for the app.
409
+
387
410
  To enable this mode add `async: true` to bot's and botan's config.
388
411
  For more information and custom configuration check out
389
412
  [docs](http://www.rubydoc.info/github/telegram-bot-rb/telegram-bot/master/Telegram/Bot/Async) or
390
413
  [source](https://github.com/telegram-bot-rb/telegram-bot/blob/master/lib/telegram/bot/async.rb).
391
414
 
415
+ If you want async mode, but don't want to setup queue, know that Rails 5 are shipped
416
+ with Async adapter by default, and there is
417
+ [Sucker Punch](https://github.com/brandonhilkert/sucker_punch) for Rails 4.
418
+
419
+ Be aware of some limitations:
420
+
421
+ - Client will not return API response.
422
+ - Sending files is not available in async mode [now],
423
+ because them can not be serialized.
424
+
425
+ To disable async mode for the block of code use `bot.async(false) { bot.send_photo }`.
426
+ Yes, it's threadsafe too.
427
+
392
428
  ## Development
393
429
 
394
430
  After checking out the repo, run `bin/setup` to install dependencies.
data/lib/telegram/bot.rb CHANGED
@@ -34,6 +34,7 @@ module Telegram
34
34
  autoload :DebugClient, 'telegram/bot/debug_client'
35
35
  autoload :Initializers, 'telegram/bot/initializers'
36
36
  autoload :Middleware, 'telegram/bot/middleware'
37
+ autoload :RSpec, 'telegram/bot/rspec'
37
38
  autoload :UpdatesController, 'telegram/bot/updates_controller'
38
39
  autoload :UpdatesPoller, 'telegram/bot/updates_poller'
39
40
  end
@@ -63,21 +63,27 @@ module Telegram
63
63
  %w(
64
64
  answerCallbackQuery
65
65
  answerInlineQuery
66
+ editMessageCaption
67
+ editMessageReplyMarkup
68
+ editMessageText
66
69
  forwardMessage
67
70
  getChat
68
71
  getChatAdministrators
69
72
  getChatMember
70
73
  getChatMembersCount
71
74
  getFile
75
+ getGameHighScores
72
76
  getMe
73
77
  getUpdates
74
78
  getUserProfilePhotos
79
+ getWebhookInfo
75
80
  kickChatMember
76
81
  leaveChat
77
82
  sendAudio
78
83
  sendChatAction
79
84
  sendContact
80
85
  sendDocument
86
+ sendGame
81
87
  sendLocation
82
88
  sendMessage
83
89
  sendPhoto
@@ -85,6 +91,7 @@ module Telegram
85
91
  sendVenue
86
92
  sendVideo
87
93
  sendVoice
94
+ setGameScore
88
95
  setWebhook
89
96
  unbanChatMember
90
97
  ).each do |method|
@@ -43,7 +43,7 @@ module Telegram
43
43
  @requests = Hash.new { |h, k| h[k] = [] }
44
44
  end
45
45
 
46
- def request(action, body)
46
+ def request(action, body = {})
47
47
  requests[action.to_sym] << body
48
48
  end
49
49
  end
@@ -0,0 +1,7 @@
1
+ module Telegram
2
+ module Bot
3
+ module RSpec
4
+ autoload :ClientMatchers, 'telegram/bot/rspec/client_matchers'
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,151 @@
1
+ module Telegram
2
+ module Bot
3
+ module RSpec
4
+ # Proxy that uses RSpec::Mocks::ArgListMatcher when it's available.
5
+ # Otherwise just performs `#==` match.
6
+ #
7
+ # Also allows to check argumets with custom block.
8
+ class ArgListMatcher
9
+ attr_reader :expected, :expected_proc
10
+
11
+ def initialize(*args, &block)
12
+ @expected_proc = block if block_given?
13
+ @expected =
14
+ if mocks_matcher?
15
+ ::RSpec::Mocks::ArgumentListMatcher.new(*args)
16
+ else
17
+ args
18
+ end
19
+ end
20
+
21
+ def args_match?(*actual)
22
+ if expected_proc
23
+ expected_proc[*actual]
24
+ true
25
+ elsif mocks_matcher?
26
+ expected.args_match?(*actual)
27
+ else
28
+ expected == actual
29
+ end
30
+ end
31
+
32
+ def args
33
+ mocks_matcher? ? expected.args : expected
34
+ end
35
+
36
+ def mocks_matcher?
37
+ defined?(::RSpec::Mocks::ArgumentListMatcher)
38
+ end
39
+
40
+ def to_s
41
+ if mocks_matcher?
42
+ expected.expected_args.inspect
43
+ elsif expected_proc
44
+ '(proc matcher)'
45
+ else
46
+ expected.inspect
47
+ end
48
+ end
49
+ end
50
+
51
+ # Matchers to test requests to Telegram API.
52
+ #
53
+ # Complex matchers requires `rspec-mocks` to be installed.
54
+ module ClientMatchers
55
+ class MakeTelegramRequest < ::RSpec::Matchers::BuiltIn::BaseMatcher
56
+ EXPECTATION_TYPES = {
57
+ exactly: :==,
58
+ at_most: :>=,
59
+ at_least: :<=,
60
+ }.freeze
61
+
62
+ attr_reader :performed_requests, :description
63
+
64
+ def initialize(bot, action, description: nil)
65
+ @bot = bot
66
+ @action = action
67
+ @description = description || "make #{action} telegram request"
68
+ exactly(1)
69
+ end
70
+
71
+ def matches?(proc) # rubocop:disable AbcSize
72
+ raise ArgumentError, 'matcher only supports block expectations' unless proc.is_a?(Proc)
73
+ original_requests_count = bot.requests[action].count
74
+ proc.call
75
+ @performed_requests = bot.requests[action].drop(original_requests_count)
76
+ @matching_requests_count = performed_requests.count do |request|
77
+ !arg_list_matcher || arg_list_matcher.args_match?(request)
78
+ end
79
+ expectation_method = EXPECTATION_TYPES[expectation_type]
80
+ expected_number.public_send(expectation_method, matching_requests_count)
81
+ end
82
+
83
+ def with(*args, &block)
84
+ @arg_list_matcher = ArgListMatcher.new(*args, &block)
85
+ self
86
+ end
87
+
88
+ EXPECTATION_TYPES.each_key do |type|
89
+ define_method type do |count|
90
+ @expectation_type = type
91
+ @expected_number = Integer(count)
92
+ self
93
+ end
94
+ end
95
+
96
+ def times
97
+ self
98
+ end
99
+
100
+ def failure_message
101
+ "expected to #{base_message}"
102
+ end
103
+
104
+ def failure_message_when_negated
105
+ "expected not to #{base_message}"
106
+ end
107
+
108
+ def supports_block_expectations?
109
+ true
110
+ end
111
+
112
+ private
113
+
114
+ attr_reader :bot, :action, :expectation_type, :expected_number,
115
+ :arg_list_matcher, :matching_requests_count
116
+
117
+ def base_message
118
+ "make #{expectation_type.to_s.tr('_', ' ')} #{expected_number} " \
119
+ "#{bot.inspect}.#{action} requests,".tap do |msg|
120
+ msg << " with #{arg_list_matcher}," if arg_list_matcher
121
+ msg << " but made #{matching_requests_count}"
122
+ if performed_requests
123
+ actual_args = performed_requests.map(&:inspect).join(', ')
124
+ msg << ", and #{performed_requests.count} with #{actual_args}"
125
+ end
126
+ end
127
+ end
128
+ end
129
+
130
+ # Check that bot performed request to telegram API:
131
+ #
132
+ # expect { dispatch_message('Hi!') }.
133
+ # to make_telegram_request(bot, :sendMessage).
134
+ # with(text: 'Hello!', chat_id: chat_id)
135
+ def make_telegram_request(bot, action)
136
+ MakeTelegramRequest.new(bot, action)
137
+ end
138
+
139
+ # Helper for asserting message is sent. Note that options are checked
140
+ # with `hash_including`. For strict checks use #make_telegram_request.
141
+ def send_telegram_message(bot, text = nil, options = {})
142
+ text = a_string_matching(text) if text.is_a?(Regexp)
143
+ options = options.merge(text: text) if text
144
+ description = "send telegram message #{text.inspect}"
145
+ MakeTelegramRequest.new(bot, :sendMessage, description: description).
146
+ with(hash_including(options))
147
+ end
148
+ end
149
+ end
150
+ end
151
+ end
@@ -50,14 +50,14 @@ module Telegram
50
50
  # ControllerClass.new(bot, from: telegram_user, chat: telegram_chat).
51
51
  # process(:help, *args)
52
52
  #
53
- class UpdatesController < AbstractController::Base # rubocop:disable ClassLength
53
+ class UpdatesController < AbstractController::Base
54
54
  abstract!
55
55
 
56
56
  require 'telegram/bot/updates_controller/session'
57
57
  require 'telegram/bot/updates_controller/log_subscriber'
58
58
  require 'telegram/bot/updates_controller/instrumentation'
59
59
  require 'telegram/bot/updates_controller/reply_helpers'
60
- autoload :CallbackQueyContext, 'telegram/bot/updates_controller/callback_query_context'
60
+ autoload :CallbackQueryContext, 'telegram/bot/updates_controller/callback_query_context'
61
61
  autoload :MessageContext, 'telegram/bot/updates_controller/message_context'
62
62
 
63
63
  include AbstractController::Callbacks
@@ -140,10 +140,12 @@ module Telegram
140
140
  @_payload, @_payload_type = payload_data
141
141
  end
142
142
 
143
- # Accessor to `'chat'` field of payload. Can be overriden with `chat` option
144
- # for #initialize.
143
+ # Accessor to `'chat'` field of payload. Also tries `'chat'` in `'message'`
144
+ # when there is no such field in payload.
145
+ #
146
+ # Can be overriden with `chat` option for #initialize.
145
147
  def chat
146
- @_chat ||= payload && payload['chat']
148
+ @_chat ||= payload.try! { |x| x['chat'] || x['message'] && x['message']['chat'] }
147
149
  end
148
150
 
149
151
  # Accessor to `'from'` field of payload. Can be overriden with `from` option
@@ -3,7 +3,7 @@ module Telegram
3
3
  class UpdatesController
4
4
  # Use separate actions for different callback queries.
5
5
  # It doesn't require session support. Simply add `%{context}:` prefix to data.
6
- module CallbackQueyContext
6
+ module CallbackQueryContext
7
7
  protected
8
8
 
9
9
  # Uses #context_from_callback_query as context name.
@@ -14,11 +14,14 @@ RSpec.shared_context 'telegram/bot/updates_controller' do
14
14
  let(:bot_name) { 'bot' }
15
15
  let(:session) { controller.send(:session) }
16
16
 
17
+ include Telegram::Bot::RSpec::ClientMatchers
18
+
17
19
  def dispatch(bot = self.bot, update = self.update)
18
20
  controller.dispatch_again(bot, update)
19
21
  end
20
22
 
21
- def dispatch_message(text, options = {})
23
+ def dispatch_message(text, options = nil)
24
+ options ||= respond_to?(:default_message_options) ? default_message_options : {}
22
25
  update = build_update :message, options.merge(text: text)
23
26
  dispatch bot, update
24
27
  end
@@ -34,4 +37,9 @@ RSpec.shared_context 'telegram/bot/updates_controller' do
34
37
  else input
35
38
  end
36
39
  end
40
+
41
+ # Matcher to check response. Make sure to define `let(:chat_id)`.
42
+ def respond_with_message(expected)
43
+ send_telegram_message(bot, expected, chat_id: chat_id)
44
+ end
37
45
  end
@@ -1,6 +1,6 @@
1
1
  module Telegram
2
2
  module Bot
3
- VERSION = '0.9.0.alpha2'.freeze
3
+ VERSION = '0.9.0'.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.9.0.alpha2
4
+ version: 0.9.0
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-08-15 00:00:00.000000000 Z
11
+ date: 2016-11-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -127,6 +127,8 @@ files:
127
127
  - lib/telegram/bot/middleware.rb
128
128
  - lib/telegram/bot/railtie.rb
129
129
  - lib/telegram/bot/routes_helper.rb
130
+ - lib/telegram/bot/rspec.rb
131
+ - lib/telegram/bot/rspec/client_matchers.rb
130
132
  - lib/telegram/bot/updates_controller.rb
131
133
  - lib/telegram/bot/updates_controller/callback_query_context.rb
132
134
  - lib/telegram/bot/updates_controller/instrumentation.rb
@@ -155,12 +157,12 @@ required_ruby_version: !ruby/object:Gem::Requirement
155
157
  version: '2.0'
156
158
  required_rubygems_version: !ruby/object:Gem::Requirement
157
159
  requirements:
158
- - - ">"
160
+ - - ">="
159
161
  - !ruby/object:Gem::Version
160
- version: 1.3.1
162
+ version: '0'
161
163
  requirements: []
162
164
  rubyforge_project:
163
- rubygems_version: 2.4.6
165
+ rubygems_version: 2.5.1
164
166
  signing_key:
165
167
  specification_version: 4
166
168
  summary: Library for building Telegram Bots with Rails integration