telegram-bot 0.9.0.alpha2 → 0.9.0

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: 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