telegram-bot 0.6.0 → 0.7.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +16 -0
- data/README.md +67 -4
- data/lib/telegram/bot.rb +1 -0
- data/lib/telegram/bot/botan.rb +36 -0
- data/lib/telegram/bot/client.rb +18 -23
- data/lib/telegram/bot/client_stub.rb +2 -1
- data/lib/telegram/bot/debug_client.rb +23 -0
- data/lib/telegram/bot/updates_controller.rb +42 -36
- data/lib/telegram/bot/updates_controller/botan.rb +33 -0
- data/lib/telegram/bot/updates_controller/callback_query_context.rb +34 -0
- data/lib/telegram/bot/updates_controller/instrumentation.rb +6 -0
- data/lib/telegram/bot/updates_controller/message_context.rb +17 -25
- data/lib/telegram/bot/updates_controller/reply_helpers.rb +42 -0
- data/lib/telegram/bot/updates_controller/session.rb +18 -2
- data/lib/telegram/bot/updates_controller/testing.rb +1 -13
- data/lib/telegram/bot/version.rb +1 -1
- metadata +8 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5f435d8f9b79fbaf3bd5f5b2fce82c96c64be1d6
|
4
|
+
data.tar.gz: 29fbc530b43dca4b39d27ad69fd2ef4cade4ce15
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 50b1cf3174f36b9c0dbfa2eb6b33cdc5a9b9352b18c6b24c7e7acca0e5fb29bddfd50c0b4199f7f33fb71f91c771ffb0b0894c99d287664ba48d9dc39f7fa39a
|
7
|
+
data.tar.gz: 06ebe8d059e5ddf23e2648d5cdbdae3f2d85096789a7b93b893a9e96b19debcd6e338ac531a1a67282a7611b443487b33720caf52688938656dcedb793c0200a
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,19 @@
|
|
1
|
+
# 0.7.2
|
2
|
+
|
3
|
+
- Bot API 2.1
|
4
|
+
- Fixed possible crashes when payload type is not supported.
|
5
|
+
Provides empty session when neither `from` nor `chat` is defined.
|
6
|
+
|
7
|
+
# 0.7.0
|
8
|
+
|
9
|
+
- New Bot API methods.
|
10
|
+
- Helpers for inline keyboards, support for callback_query (with contextual actions).
|
11
|
+
- Changed action methods signature
|
12
|
+
- `#inline_query(payload) -> #inline_query(query, offset)`
|
13
|
+
- `#chosen_inline_result(payload)` -> `#chosen_inline_result(result_id, query)`
|
14
|
+
- MessageContext doesn't use second #process call to run contextual action.
|
15
|
+
- Botan.io metrics.
|
16
|
+
|
1
17
|
# 0.6.0
|
2
18
|
|
3
19
|
- StaleChat error.
|
data/README.md
CHANGED
@@ -17,6 +17,10 @@ Package contains:
|
|
17
17
|
- Poller with automatic source-reloader for development env.
|
18
18
|
- Rake tasks to update webhook urls.
|
19
19
|
|
20
|
+
Here is sample [telegram_bot_app](https://github.com/telegram-bot-rb/telegram_bot_app)
|
21
|
+
with session, keyboards and inline queries.
|
22
|
+
Run it on your local machine in 1 minute!
|
23
|
+
|
20
24
|
## Installation
|
21
25
|
|
22
26
|
Add this line to your application's Gemfile:
|
@@ -99,7 +103,8 @@ class Telegram::WebhookController < Telegram::Bot::UpdatesController
|
|
99
103
|
# use callbacks like in any other controllers
|
100
104
|
around_action :with_locale
|
101
105
|
|
102
|
-
# Every update can have one of: message, inline_query
|
106
|
+
# Every update can have one of: message, inline_query, chosen_inline_result,
|
107
|
+
# callback_query.
|
103
108
|
# Define method with same name to respond to this updates.
|
104
109
|
def message(message)
|
105
110
|
# message can be also accessed via instance method
|
@@ -107,6 +112,13 @@ class Telegram::WebhookController < Telegram::Bot::UpdatesController
|
|
107
112
|
# store_message(message['text'])
|
108
113
|
end
|
109
114
|
|
115
|
+
# This basic methods receives commonly used params:
|
116
|
+
#
|
117
|
+
# message(payload)
|
118
|
+
# inline_query(query, offset)
|
119
|
+
# chosen_inline_result(result_id, query)
|
120
|
+
# callback_query(data)
|
121
|
+
|
110
122
|
# Define public methods to respond to commands.
|
111
123
|
# Command arguments will be parsed and passed to the method.
|
112
124
|
# Be sure to use splat args and default values to not get errors when
|
@@ -140,6 +152,8 @@ class Telegram::WebhookController < Telegram::Bot::UpdatesController
|
|
140
152
|
end
|
141
153
|
```
|
142
154
|
|
155
|
+
#### Optional typecasting
|
156
|
+
|
143
157
|
You can enable typecasting of `update` with `telegram-bot-types` by including
|
144
158
|
`Telegram::Bot::UpdatesPoller::TypedUpdate`:
|
145
159
|
|
@@ -153,6 +167,8 @@ class Telegram::WebhookController < Telegram::Bot::UpdatesController
|
|
153
167
|
end
|
154
168
|
```
|
155
169
|
|
170
|
+
#### Session
|
171
|
+
|
156
172
|
There is support for sessions using `ActiveSupport::Cache` stores.
|
157
173
|
|
158
174
|
```ruby
|
@@ -186,6 +202,8 @@ class Telegram::WebhookController < Telegram::Bot::UpdatesController
|
|
186
202
|
end
|
187
203
|
```
|
188
204
|
|
205
|
+
#### Message context
|
206
|
+
|
189
207
|
It's usual to support chain of messages like BotFather: after receiving command
|
190
208
|
it asks you for additional argument. There is `MessageContext` for this:
|
191
209
|
|
@@ -200,8 +218,8 @@ class Telegram::WebhookController < Telegram::Bot::UpdatesController
|
|
200
218
|
end
|
201
219
|
|
202
220
|
# register context handlers to handle this context
|
203
|
-
context_handler :rename do |
|
204
|
-
update_name
|
221
|
+
context_handler :rename do |*words|
|
222
|
+
update_name words[0]
|
205
223
|
reply_with :message, text: 'Renamed!'
|
206
224
|
end
|
207
225
|
|
@@ -225,6 +243,14 @@ class Telegram::WebhookController < Telegram::Bot::UpdatesController
|
|
225
243
|
end
|
226
244
|
```
|
227
245
|
|
246
|
+
You can use `CallbackQueyContext` in the similar way to split `#callback_query` into
|
247
|
+
several specific methods. It doesn't require session support, and takes context from
|
248
|
+
data. If data has a prefix with colon like this `my_ctx:smth...` it'll call
|
249
|
+
`my_ctx_callback_query('smth...')` when there is such action method. Otherwise
|
250
|
+
it'll call `callback_query('my_ctx:smth...')` as usual.
|
251
|
+
|
252
|
+
#### Processesing updates
|
253
|
+
|
228
254
|
To process update run:
|
229
255
|
|
230
256
|
```ruby
|
@@ -266,7 +292,8 @@ telegram_webhooks bot => TelegramChatController,
|
|
266
292
|
# You can override this options or specify others:
|
267
293
|
telegram_webhooks TelegramController, as: :my_webhook
|
268
294
|
telegram_webhooks bot => [TelegramChatController, as: :chat_webhook],
|
269
|
-
other_bot =>
|
295
|
+
other_bot => TelegramAuctionController,
|
296
|
+
admin_chat: TelegramAdminChatController
|
270
297
|
```
|
271
298
|
|
272
299
|
For Rack applications you can also use `Telegram::Bot::Middleware` or just
|
@@ -309,6 +336,42 @@ Check out `telegram/bot/updates_controller/rspec_helpers` and
|
|
309
336
|
Use `rake telegram:bot:set_webhook` to update webhook url for all configured bots.
|
310
337
|
Certificate can be specified with `CERT=path/to/cert`.
|
311
338
|
|
339
|
+
### Botan.io metrics
|
340
|
+
|
341
|
+
Initialize with `bot = Telegram::Bot::Client.new(token, botan: 'botan token')`
|
342
|
+
or just add `botan` key in `secrets.yml`:
|
343
|
+
|
344
|
+
```yml
|
345
|
+
telegram:
|
346
|
+
bot:
|
347
|
+
token: bot_token
|
348
|
+
botan: botan_token
|
349
|
+
```
|
350
|
+
|
351
|
+
Access to Botan client with `bot.botan`.
|
352
|
+
Use `bot.botan.track(event, uid, payload)` to track events.
|
353
|
+
|
354
|
+
There are some helpers for controllers in `Telegram::Bot::UpdatesController::Botan`:
|
355
|
+
|
356
|
+
```ruby
|
357
|
+
class Telegram::WebhookController < Telegram::Bot::UpdatesController
|
358
|
+
include Telegram::Bot::UpdatesController::Botan
|
359
|
+
|
360
|
+
# This will track with event: action_name & data: payload
|
361
|
+
before_action :botan_track_action
|
362
|
+
|
363
|
+
def smth(*)
|
364
|
+
# This will track event for current user only when botan is configured.
|
365
|
+
botan_track :my_event, custom_data
|
366
|
+
|
367
|
+
# or get access directly to botan client:
|
368
|
+
botan.track(...)
|
369
|
+
end
|
370
|
+
end
|
371
|
+
```
|
372
|
+
|
373
|
+
There is no stubbing for botan clients, so don't set botan token in tests.
|
374
|
+
|
312
375
|
## Development
|
313
376
|
|
314
377
|
After checking out the repo, run `bin/setup` to install dependencies.
|
data/lib/telegram/bot.rb
CHANGED
@@ -0,0 +1,36 @@
|
|
1
|
+
module Telegram
|
2
|
+
module Bot
|
3
|
+
class Botan
|
4
|
+
TRACK_URI = 'https://api.botan.io/track'.freeze
|
5
|
+
|
6
|
+
class Error < Bot::Error; end
|
7
|
+
|
8
|
+
include DebugClient
|
9
|
+
|
10
|
+
attr_reader :client, :token
|
11
|
+
|
12
|
+
def initialize(token)
|
13
|
+
@client = HTTPClient.new
|
14
|
+
@token = token
|
15
|
+
end
|
16
|
+
|
17
|
+
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
|
+
)
|
24
|
+
status = res.status
|
25
|
+
return JSON.parse(res.body) if 300 > status
|
26
|
+
result = JSON.parse(res.body) rescue nil # rubocop:disable RescueModifier
|
27
|
+
err_msg = "#{res.reason}: #{result && result['info'] || '-'}"
|
28
|
+
raise Error, err_msg
|
29
|
+
end
|
30
|
+
|
31
|
+
def http_request(method, uri, query, body)
|
32
|
+
client.request(method, uri, query, body)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
data/lib/telegram/bot/client.rb
CHANGED
@@ -2,14 +2,16 @@ 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'
|
5
6
|
|
6
7
|
module Telegram
|
7
8
|
module Bot
|
8
9
|
class Client
|
9
|
-
autoload :TypedResponse, 'telegram/bot/client/typed_response'
|
10
|
-
|
11
10
|
URL_TEMPLATE = 'https://api.telegram.org/bot%s/'.freeze
|
12
11
|
|
12
|
+
autoload :TypedResponse, 'telegram/bot/client/typed_response'
|
13
|
+
include DebugClient
|
14
|
+
|
13
15
|
class << self
|
14
16
|
# Accepts different options to initialize bot.
|
15
17
|
def wrap(input)
|
@@ -18,7 +20,7 @@ module Telegram
|
|
18
20
|
when Array then input.map(&method(__callee__))
|
19
21
|
when Hash then
|
20
22
|
input = input.stringify_keys
|
21
|
-
new input['token'], input['username']
|
23
|
+
new input['token'], input['username'], botan: input['botan']
|
22
24
|
when Symbol
|
23
25
|
Telegram.bots[input] or
|
24
26
|
raise "Bot #{input} not configured, check Telegram.bots_config."
|
@@ -41,31 +43,14 @@ module Telegram
|
|
41
43
|
end
|
42
44
|
end
|
43
45
|
|
44
|
-
attr_reader :client, :token, :username, :base_uri
|
46
|
+
attr_reader :client, :token, :username, :base_uri, :botan
|
45
47
|
|
46
|
-
def initialize(token, username = nil)
|
48
|
+
def initialize(token, username = nil, botan: nil)
|
47
49
|
@client = HTTPClient.new
|
48
50
|
@token = token
|
49
51
|
@username = username
|
50
52
|
@base_uri = format URL_TEMPLATE, token
|
51
|
-
|
52
|
-
|
53
|
-
def debug!(dev = STDOUT)
|
54
|
-
if block_given?
|
55
|
-
begin
|
56
|
-
old_dev = client.debug_dev
|
57
|
-
client.debug_dev = dev
|
58
|
-
yield
|
59
|
-
ensure
|
60
|
-
client.debug_dev = old_dev
|
61
|
-
end
|
62
|
-
else
|
63
|
-
client.debug_dev = dev
|
64
|
-
end
|
65
|
-
end
|
66
|
-
|
67
|
-
def debug_off!
|
68
|
-
client.debug_dev = nil
|
53
|
+
@botan = Botan.new(botan) if botan
|
69
54
|
end
|
70
55
|
|
71
56
|
def request(action, body = {}) # rubocop:disable PerceivedComplexity
|
@@ -83,22 +68,32 @@ module Telegram
|
|
83
68
|
end
|
84
69
|
|
85
70
|
%w(
|
71
|
+
answerCallbackQuery
|
86
72
|
answerInlineQuery
|
87
73
|
forwardMessage
|
74
|
+
getChat
|
75
|
+
getChatAdministrators
|
76
|
+
getChatMember
|
77
|
+
getChatMembersCount
|
88
78
|
getFile
|
89
79
|
getMe
|
90
80
|
getUpdates
|
91
81
|
getUserProfilePhotos
|
82
|
+
kickChatMember
|
83
|
+
leaveChat
|
92
84
|
sendAudio
|
93
85
|
sendChatAction
|
86
|
+
sendContact
|
94
87
|
sendDocument
|
95
88
|
sendLocation
|
96
89
|
sendMessage
|
97
90
|
sendPhoto
|
98
91
|
sendSticker
|
92
|
+
sendVenue
|
99
93
|
sendVideo
|
100
94
|
sendVoice
|
101
95
|
setWebhook
|
96
|
+
unbanChatMember
|
102
97
|
).each do |method|
|
103
98
|
define_method(method.underscore) { |*args| request(method, *args) }
|
104
99
|
end
|
@@ -15,7 +15,8 @@ module Telegram
|
|
15
15
|
end
|
16
16
|
|
17
17
|
class << self
|
18
|
-
#
|
18
|
+
# Any call to Client.new will return ClientStub instance when `enabled` is true.
|
19
|
+
# Can be used with a block.
|
19
20
|
def stub_all!(enabled = true)
|
20
21
|
Client.extend(StubbedConstructor) unless Client < StubbedConstructor
|
21
22
|
return @_stub_all = enabled unless block_given?
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Telegram
|
2
|
+
module Bot
|
3
|
+
module DebugClient
|
4
|
+
def debug!(dev = STDOUT)
|
5
|
+
if block_given?
|
6
|
+
begin
|
7
|
+
old_dev = client.debug_dev
|
8
|
+
client.debug_dev = dev
|
9
|
+
yield
|
10
|
+
ensure
|
11
|
+
client.debug_dev = old_dev
|
12
|
+
end
|
13
|
+
else
|
14
|
+
client.debug_dev = dev
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def debug_off!
|
19
|
+
client.debug_dev = nil
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -50,13 +50,16 @@ 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
|
53
|
+
class UpdatesController < AbstractController::Base # rubocop:disable ClassLength
|
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
|
+
require 'telegram/bot/updates_controller/reply_helpers'
|
60
|
+
autoload :CallbackQueyContext, 'telegram/bot/updates_controller/callback_query_context'
|
59
61
|
autoload :MessageContext, 'telegram/bot/updates_controller/message_context'
|
62
|
+
autoload :Botan, 'telegram/bot/updates_controller/botan'
|
60
63
|
|
61
64
|
include AbstractController::Callbacks
|
62
65
|
# Redefine callbacks with default terminator.
|
@@ -70,6 +73,7 @@ module Telegram
|
|
70
73
|
end
|
71
74
|
|
72
75
|
include AbstractController::Translation
|
76
|
+
include ReplyHelpers
|
73
77
|
prepend Instrumentation
|
74
78
|
extend Session::ConfigMethods
|
75
79
|
|
@@ -79,6 +83,8 @@ module Telegram
|
|
79
83
|
message
|
80
84
|
inline_query
|
81
85
|
chosen_inline_result
|
86
|
+
callback_query
|
87
|
+
edited_message
|
82
88
|
).freeze
|
83
89
|
CMD_REGEX = %r{\A/([a-z\d_]{,31})(@(\S+))?(\s|$)}i
|
84
90
|
CONFLICT_CMD_REGEX = Regexp.new("^(#{PAYLOAD_TYPES.join('|')}|\\d)")
|
@@ -138,13 +144,13 @@ module Telegram
|
|
138
144
|
# Accessor to `'chat'` field of payload. Can be overriden with `chat` option
|
139
145
|
# for #initialize.
|
140
146
|
def chat
|
141
|
-
@_chat
|
147
|
+
@_chat ||= payload && payload['chat']
|
142
148
|
end
|
143
149
|
|
144
150
|
# Accessor to `'from'` field of payload. Can be overriden with `from` option
|
145
151
|
# for #initialize.
|
146
152
|
def from
|
147
|
-
@_from
|
153
|
+
@_from ||= payload && payload['from']
|
148
154
|
end
|
149
155
|
|
150
156
|
# Processes current update.
|
@@ -154,47 +160,47 @@ module Telegram
|
|
154
160
|
end
|
155
161
|
|
156
162
|
# Calculates action name and args for payload.
|
157
|
-
#
|
158
|
-
#
|
163
|
+
# Uses `action_for_#{payload_type}` methods.
|
164
|
+
# If this method doesn't return anything
|
165
|
+
# it uses fallback with action same as payload type.
|
159
166
|
# Returns array `[is_command?, action, args]`.
|
160
167
|
def action_for_payload
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
end || [false, payload_type, [payload]]
|
168
|
+
if payload_type
|
169
|
+
send("action_for_#{payload_type}") || [false, payload_type, [payload]]
|
170
|
+
else
|
171
|
+
[false, :unsupported_payload_type, []]
|
172
|
+
end
|
167
173
|
end
|
168
174
|
|
169
|
-
#
|
170
|
-
#
|
171
|
-
|
175
|
+
# If payload is a message with command, then returned action is an
|
176
|
+
# action for this command.
|
177
|
+
# Separate method, so it can be easily overriden (ex. MessageContext).
|
178
|
+
def action_for_message
|
179
|
+
cmd, args = self.class.command_from_text(payload['text'], bot_username)
|
180
|
+
cmd &&= self.class.action_for_command(cmd)
|
181
|
+
[true, cmd, args] if cmd
|
182
|
+
end
|
183
|
+
|
184
|
+
# It doesn't extract commands from edited messages. Just process
|
185
|
+
# them as usual ones.
|
186
|
+
def action_for_edited_message
|
172
187
|
end
|
173
188
|
|
174
|
-
|
175
|
-
|
176
|
-
#
|
177
|
-
# reply_with :message, text: 'Hello!'
|
178
|
-
# reply_with :message, text: '__Hello!__', parse_mode: :Markdown
|
179
|
-
# reply_with :photo, photo: File.open(photo_to_send), caption: "It's incredible!"
|
180
|
-
def reply_with(type, params)
|
181
|
-
method = "send_#{type}"
|
182
|
-
chat = self.chat
|
183
|
-
payload = self.payload
|
184
|
-
params = params.merge(
|
185
|
-
chat_id: (chat && chat['id'] or raise 'Can not reply_with when chat is not present'),
|
186
|
-
reply_to_message: payload && payload['message_id'],
|
187
|
-
)
|
188
|
-
bot.public_send(method, params)
|
189
|
+
def action_for_inline_query
|
190
|
+
[false, payload_type, [payload['query'], payload['offset']]]
|
189
191
|
end
|
190
192
|
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
193
|
+
def action_for_chosen_inline_result
|
194
|
+
[false, payload_type, [payload['result_id'], payload['query']]]
|
195
|
+
end
|
196
|
+
|
197
|
+
def action_for_callback_query
|
198
|
+
[false, payload_type, [payload['data']]]
|
199
|
+
end
|
200
|
+
|
201
|
+
# Silently ignore unsupported messages.
|
202
|
+
# Params are `action, *args`.
|
203
|
+
def action_missing(*)
|
198
204
|
end
|
199
205
|
|
200
206
|
ActiveSupport.run_load_hooks('telegram.bot.updates_controller', self)
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Telegram
|
2
|
+
module Bot
|
3
|
+
class UpdatesController
|
4
|
+
# Helpers for botan.io metrics.
|
5
|
+
module Botan
|
6
|
+
class MissingFrom < Error; end
|
7
|
+
|
8
|
+
protected
|
9
|
+
|
10
|
+
def botan
|
11
|
+
@botan ||= bot.try!(:botan)
|
12
|
+
end
|
13
|
+
|
14
|
+
# Track custom event for user taken from `from` field:
|
15
|
+
#
|
16
|
+
# botan_track :my_event, {data: :val}
|
17
|
+
#
|
18
|
+
def botan_track(event, data = {})
|
19
|
+
raise MissingFrom, 'Can not track without user' unless from
|
20
|
+
botan.try! { |x| x.track(event, from['id'], data) }
|
21
|
+
end
|
22
|
+
|
23
|
+
# Track current action and payload for current user. Best used with `before_action`:
|
24
|
+
#
|
25
|
+
# before_action :botan_track_action
|
26
|
+
#
|
27
|
+
def botan_track_action
|
28
|
+
botan_track(action_name, payload)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Telegram
|
2
|
+
module Bot
|
3
|
+
class UpdatesController
|
4
|
+
# Use separate actions for different callback queries.
|
5
|
+
# It doesn't require session support. Simply add `%{context}:` prefix to data.
|
6
|
+
module CallbackQueyContext
|
7
|
+
protected
|
8
|
+
|
9
|
+
# Uses #context_from_callback_query as context name.
|
10
|
+
# If context is present checks if `%context%_callback_query` is valid
|
11
|
+
# action method and returns it if so. Context is stripped from data
|
12
|
+
# in this case. Otherwise returns `super`.
|
13
|
+
#
|
14
|
+
# It wont raise ActionNotFound as MessageContext does,
|
15
|
+
# because `data` param is controlled by client.
|
16
|
+
def action_for_callback_query
|
17
|
+
context, new_data = context_from_callback_query
|
18
|
+
# binding.pry
|
19
|
+
if context
|
20
|
+
action_name = "#{context}_callback_query"
|
21
|
+
[false, action_name, [new_data]] if action_method?(action_name)
|
22
|
+
end || super
|
23
|
+
end
|
24
|
+
|
25
|
+
def context_from_callback_query
|
26
|
+
data = payload['data']
|
27
|
+
return unless data
|
28
|
+
parts = data.split(':', 2)
|
29
|
+
parts if parts.size > 1
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -39,6 +39,12 @@ module Telegram
|
|
39
39
|
Instrumentation.instrument(:reply_with, type: type) { super }
|
40
40
|
end
|
41
41
|
|
42
|
+
%i(answer_callback_query answer_inline_query).each do |type|
|
43
|
+
define_method(type) do |*args|
|
44
|
+
Instrumentation.instrument(:reply_with, type: type) { super(*args) }
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
42
48
|
private
|
43
49
|
|
44
50
|
# A hook invoked every time a before callback is halted.
|
@@ -10,7 +10,6 @@ module Telegram
|
|
10
10
|
included do
|
11
11
|
# As we use before_action context is cleared anyway,
|
12
12
|
# no matter we used it or not.
|
13
|
-
before_action :fetch_context
|
14
13
|
singleton_class.send :attr_reader, :context_handlers, :context_to_action
|
15
14
|
@context_handlers = {}
|
16
15
|
end
|
@@ -18,26 +17,26 @@ module Telegram
|
|
18
17
|
module ClassMethods
|
19
18
|
# Registers handler for context.
|
20
19
|
#
|
21
|
-
# context_handler :rename do
|
22
|
-
# resource.update!(name:
|
20
|
+
# context_handler :rename do |*|
|
21
|
+
# resource.update!(name: payload['text'])
|
23
22
|
# end
|
24
23
|
#
|
25
24
|
# # To run other action with all the callbacks:
|
26
|
-
# context_handler :rename do |
|
27
|
-
# process(:rename, *
|
25
|
+
# context_handler :rename do |*words|
|
26
|
+
# process(:rename, *words)
|
28
27
|
# end
|
29
28
|
#
|
30
29
|
# # Or just
|
31
30
|
# context_handler :rename, :your_action_to_call
|
32
31
|
# context_handler :rename # to call :rename
|
33
32
|
#
|
34
|
-
# # For messages without context use this instead of `message` method:
|
35
|
-
# context_handle do |message|
|
36
|
-
# end
|
37
|
-
#
|
38
33
|
def context_handler(context = nil, action = nil, &block)
|
39
34
|
context &&= context.to_sym
|
40
|
-
|
35
|
+
if block
|
36
|
+
action = "_context_handler_#{context}"
|
37
|
+
define_method(action, &block)
|
38
|
+
end
|
39
|
+
context_handlers[context] = action || context
|
41
40
|
end
|
42
41
|
|
43
42
|
# Use it to use context value as action name for all contexts
|
@@ -49,20 +48,9 @@ module Telegram
|
|
49
48
|
end
|
50
49
|
end
|
51
50
|
|
52
|
-
# Finds handler for current context and processes message with it.
|
53
|
-
def message(message)
|
54
|
-
handler = handler_for_context
|
55
|
-
return unless handler
|
56
|
-
if handler.respond_to?(:call)
|
57
|
-
instance_exec(message, &handler)
|
58
|
-
else
|
59
|
-
process(handler, *message['text'].split)
|
60
|
-
end
|
61
|
-
end
|
62
|
-
|
63
51
|
# Action to clear context.
|
64
52
|
def cancel
|
65
|
-
# Context is already cleared in
|
53
|
+
# Context is already cleared in action_for_message
|
66
54
|
end
|
67
55
|
|
68
56
|
private
|
@@ -71,11 +59,15 @@ module Telegram
|
|
71
59
|
# according to previous request.
|
72
60
|
attr_reader :context
|
73
61
|
|
74
|
-
# Fetches and
|
75
|
-
|
62
|
+
# Fetches context and finds handler for it. If message has new command,
|
63
|
+
# it has higher priority than contextual action.
|
64
|
+
def action_for_message
|
76
65
|
val = session.delete(:context)
|
77
66
|
@context = val && val.to_sym
|
78
|
-
|
67
|
+
super || context && begin
|
68
|
+
handler = handler_for_context
|
69
|
+
[true, handler, payload['text'].try!(:split) || []] if handler
|
70
|
+
end
|
79
71
|
end
|
80
72
|
|
81
73
|
# Save context for the next request.
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module Telegram
|
2
|
+
module Bot
|
3
|
+
class UpdatesController
|
4
|
+
module ReplyHelpers
|
5
|
+
# Helper to call bot's `send_#{type}` method with already set `chat_id` and
|
6
|
+
# `reply_to_message_id`:
|
7
|
+
#
|
8
|
+
# reply_with :message, text: 'Hello!'
|
9
|
+
# reply_with :message, text: '__Hello!__', parse_mode: :Markdown
|
10
|
+
# reply_with :photo, photo: File.open(photo_to_send), caption: "It's incredible!"
|
11
|
+
def reply_with(type, params)
|
12
|
+
method = "send_#{type}"
|
13
|
+
chat = self.chat
|
14
|
+
payload = self.payload
|
15
|
+
params = params.merge(
|
16
|
+
chat_id: (chat && chat['id'] or raise 'Can not reply_with when chat is not present'),
|
17
|
+
reply_to_message: payload && payload['message_id'],
|
18
|
+
)
|
19
|
+
bot.public_send(method, params)
|
20
|
+
end
|
21
|
+
|
22
|
+
# Same as reply_with, but for inline queries.
|
23
|
+
def answer_inline_query(results, params = {})
|
24
|
+
params = params.merge(
|
25
|
+
inline_query_id: payload['id'],
|
26
|
+
results: results,
|
27
|
+
)
|
28
|
+
bot.answer_inline_query(params)
|
29
|
+
end
|
30
|
+
|
31
|
+
# Same as reply_with, but for callback queries.
|
32
|
+
def answer_callback_query(text, params = {})
|
33
|
+
params = params.merge(
|
34
|
+
callback_query_id: payload['id'],
|
35
|
+
text: text,
|
36
|
+
)
|
37
|
+
bot.answer_callback_query(params)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -17,11 +17,15 @@ module Telegram
|
|
17
17
|
protected
|
18
18
|
|
19
19
|
def session
|
20
|
-
@_session ||=
|
20
|
+
@_session ||= begin
|
21
|
+
key = session_key
|
22
|
+
key ? SessionHash.new(self.class.session_store, key) : NullSessionHash.new
|
23
|
+
end
|
21
24
|
end
|
22
25
|
|
23
26
|
def session_key
|
24
|
-
|
27
|
+
subject = from || chat
|
28
|
+
"#{bot.username}:#{subject['id']}" if subject
|
25
29
|
end
|
26
30
|
|
27
31
|
# Rack::Session::Abstract::SessionHash is taken to provide lazy loading.
|
@@ -59,6 +63,18 @@ module Telegram
|
|
59
63
|
end
|
60
64
|
end
|
61
65
|
|
66
|
+
class NullSessionHash < Session::SessionHash
|
67
|
+
def initialize
|
68
|
+
@data = {}
|
69
|
+
@loaded = true
|
70
|
+
@exists = true
|
71
|
+
end
|
72
|
+
|
73
|
+
alias_method :destroy, :clear
|
74
|
+
alias_method :load!, :id
|
75
|
+
alias_method :commit, :id
|
76
|
+
end
|
77
|
+
|
62
78
|
module ConfigMethods
|
63
79
|
delegate :session_store, to: :config
|
64
80
|
|
@@ -27,19 +27,7 @@ module Telegram
|
|
27
27
|
|
28
28
|
# Stubs session.
|
29
29
|
def session
|
30
|
-
@_session ||=
|
31
|
-
end
|
32
|
-
|
33
|
-
class SessionHash < Session::SessionHash
|
34
|
-
def initialize
|
35
|
-
@data = {}
|
36
|
-
@loaded = true
|
37
|
-
@exists = true
|
38
|
-
end
|
39
|
-
|
40
|
-
alias_method :destroy, :clear
|
41
|
-
alias_method :load!, :id
|
42
|
-
alias_method :commit, :id
|
30
|
+
@_session ||= Session::NullSessionHash.new
|
43
31
|
end
|
44
32
|
end
|
45
33
|
end
|
data/lib/telegram/bot/version.rb
CHANGED
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.
|
4
|
+
version: 0.7.2
|
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-
|
11
|
+
date: 2016-05-31 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -102,17 +102,22 @@ files:
|
|
102
102
|
- bin/setup
|
103
103
|
- lib/tasks/telegram-bot.rake
|
104
104
|
- lib/telegram/bot.rb
|
105
|
+
- lib/telegram/bot/botan.rb
|
105
106
|
- lib/telegram/bot/client.rb
|
106
107
|
- lib/telegram/bot/client/typed_response.rb
|
107
108
|
- lib/telegram/bot/client_stub.rb
|
108
109
|
- lib/telegram/bot/config_methods.rb
|
110
|
+
- lib/telegram/bot/debug_client.rb
|
109
111
|
- lib/telegram/bot/middleware.rb
|
110
112
|
- lib/telegram/bot/railtie.rb
|
111
113
|
- lib/telegram/bot/routes_helper.rb
|
112
114
|
- lib/telegram/bot/updates_controller.rb
|
115
|
+
- lib/telegram/bot/updates_controller/botan.rb
|
116
|
+
- lib/telegram/bot/updates_controller/callback_query_context.rb
|
113
117
|
- lib/telegram/bot/updates_controller/instrumentation.rb
|
114
118
|
- lib/telegram/bot/updates_controller/log_subscriber.rb
|
115
119
|
- lib/telegram/bot/updates_controller/message_context.rb
|
120
|
+
- lib/telegram/bot/updates_controller/reply_helpers.rb
|
116
121
|
- lib/telegram/bot/updates_controller/rspec_helpers.rb
|
117
122
|
- lib/telegram/bot/updates_controller/session.rb
|
118
123
|
- lib/telegram/bot/updates_controller/testing.rb
|
@@ -140,9 +145,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
140
145
|
version: '0'
|
141
146
|
requirements: []
|
142
147
|
rubyforge_project:
|
143
|
-
rubygems_version: 2.
|
148
|
+
rubygems_version: 2.5.1
|
144
149
|
signing_key:
|
145
150
|
specification_version: 4
|
146
151
|
summary: Library for building Telegram Bots with Rails integration
|
147
152
|
test_files: []
|
148
|
-
has_rdoc:
|