telegram-bot 0.6.0 → 0.7.2
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 +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:
|