telegram-bot 0.3.0 → 0.4.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 +4 -4
- data/Gemfile +2 -0
- data/README.md +69 -10
- data/lib/telegram/bot.rb +5 -85
- data/lib/telegram/bot/client.rb +111 -0
- data/lib/telegram/bot/client/typed_response.rb +38 -0
- data/lib/telegram/bot/client_stub.rb +21 -0
- data/lib/telegram/bot/config_methods.rb +46 -0
- data/lib/telegram/bot/middleware.rb +1 -1
- data/lib/telegram/bot/railtie.rb +2 -1
- data/lib/telegram/bot/routes_helper.rb +3 -2
- data/lib/telegram/bot/updates_controller.rb +20 -4
- data/lib/telegram/bot/updates_controller/instrumentation.rb +1 -1
- data/lib/telegram/bot/updates_controller/log_subscriber.rb +1 -1
- data/lib/telegram/bot/updates_controller/rspec_helpers.rb +31 -0
- data/lib/telegram/bot/updates_controller/session.rb +84 -0
- data/lib/telegram/bot/updates_controller/typed_update.rb +14 -0
- data/lib/telegram/bot/updates_poller.rb +2 -2
- data/lib/telegram/bot/version.rb +2 -2
- data/telegram-bot.gemspec +1 -1
- metadata +10 -4
- data/lib/telegram/bottable.rb +0 -41
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 33815725b4d37b05fdc85130cdc70de21c1b1f42
|
4
|
+
data.tar.gz: 2cc11d12481d962c04cbaf440337a358d4ee6d2e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3e1b110b959fef3b8bc6d1837e4328f98baa8ec3bae7dd2679b2c3f90befbba714b5c19050699c9ba1f635becacabbf781acfd8e390281d099df72b25fef0881
|
7
|
+
data.tar.gz: b95595b9e1116c5441a3742b0c8953936279647f0f010a37f55a58e41c82de8170cb1953fec4e0169bf11c91105c40e84a4e6f595dac7220b70e37a317b58319
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -1,17 +1,17 @@
|
|
1
1
|
# Telegram::Bot
|
2
2
|
|
3
3
|
[](http://badge.fury.io/rb/telegram-bot)
|
4
|
-
[](https://codeclimate.com/github/telegram-bot-rb/telegram-bot)
|
5
|
+
[](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
|
8
|
+
standalone app. Supposed to be used in webhook-mode in production, and poller-mode
|
9
9
|
in development, but you can use poller in production if you want.
|
10
10
|
|
11
11
|
Package contains:
|
12
12
|
|
13
|
-
- Ligthweight client
|
14
|
-
[httpclient](https://github.com/nahi/httpclient)
|
13
|
+
- Ligthweight client for bot API (with fast and thread-safe
|
14
|
+
[httpclient](https://github.com/nahi/httpclient) under the hood).
|
15
15
|
- Controller with message parser. Allows to write separate methods for each command.
|
16
16
|
- Middleware and routes helpers for production env.
|
17
17
|
- Poller with automatic source-reloader for development env.
|
@@ -62,23 +62,39 @@ telegram:
|
|
62
62
|
From now clients will be accessible with `Telegram.bots[:chat]` or `Telegram.bots[:auction]`.
|
63
63
|
Single bot can be accessed with `Telegram.bot` or `Telegram.bots[:default]`.
|
64
64
|
|
65
|
-
You can create clients manually with `Telegram::Bot.new(token, username)`.
|
65
|
+
You can create clients manually with `Telegram::Bot::Client.new(token, username)`.
|
66
66
|
Username is optional and used only to parse commands with mentions.
|
67
67
|
|
68
|
-
|
68
|
+
There is `request(path_suffix, body)` method to perform any query.
|
69
|
+
And there are also shortcuts for available queries in underscored style
|
69
70
|
(`answer_inline_query` instead of `answerInlineQuery`).
|
70
71
|
All this methods just post given params to specific URL.
|
71
72
|
|
72
73
|
```ruby
|
74
|
+
bot.request(:getMe) or bot.get_me
|
75
|
+
bot.request(:getupdates, offset: 1) or bot.get_updates(offset: 1)
|
73
76
|
bot.send_message chat_id: chat_id, text: 'Test'
|
74
77
|
```
|
75
78
|
|
79
|
+
By default client will return parsed json responses. You can enable
|
80
|
+
response typecasting to virtus models using `telegram-bot-types` gem:
|
81
|
+
```ruby
|
82
|
+
# Add to your gemfile:
|
83
|
+
gem 'telegram-bot-types', '~> x.x.x'
|
84
|
+
# Enable typecasting:
|
85
|
+
Telegram::Bot::Client.typed_response!
|
86
|
+
# or for single instance:
|
87
|
+
bot.extend Telegram::Bot::Client::TypedResponse
|
88
|
+
|
89
|
+
bot.get_me.class # => Telegram::Bot::Types::User
|
90
|
+
```
|
91
|
+
|
76
92
|
### Controller
|
77
93
|
|
78
94
|
```ruby
|
79
95
|
class Telegram::WebhookController < Telegram::Bot::UpdatesController
|
80
96
|
# use callbacks like in any other controllers
|
81
|
-
around_action :
|
97
|
+
around_action :with_locale
|
82
98
|
|
83
99
|
# Every update can have one of: message, inline_query & chosen_inline_result.
|
84
100
|
# Define method with same name to respond to this updates.
|
@@ -107,7 +123,7 @@ class Telegram::WebhookController < Telegram::Bot::UpdatesController
|
|
107
123
|
|
108
124
|
private
|
109
125
|
|
110
|
-
def
|
126
|
+
def with_locale(&block)
|
111
127
|
I18n.with_locale(locale_for_update, &block)
|
112
128
|
end
|
113
129
|
|
@@ -121,6 +137,49 @@ class Telegram::WebhookController < Telegram::Bot::UpdatesController
|
|
121
137
|
end
|
122
138
|
```
|
123
139
|
|
140
|
+
You can enable typecasting of `update` with `telegram-bot-types` by including
|
141
|
+
`Telegram::Bot::UpdatesPoller::TypedUpdate`:
|
142
|
+
|
143
|
+
```ruby
|
144
|
+
class Telegram::WebhookController < Telegram::Bot::UpdatesController
|
145
|
+
include Telegram::Bot::UpdatesPoller::TypedUpdate
|
146
|
+
|
147
|
+
def message(message)
|
148
|
+
message.class # => Telegram::Bot::Types::Message
|
149
|
+
end
|
150
|
+
end
|
151
|
+
```
|
152
|
+
|
153
|
+
There is support for sessions using `ActiveSupport::Cache` stores.
|
154
|
+
|
155
|
+
```ruby
|
156
|
+
# configure store in env files:
|
157
|
+
config.telegram_updates_controller.session_store = :redis_store, {expires_in: 1.month}
|
158
|
+
|
159
|
+
class Telegram::WebhookController < Telegram::Bot::UpdatesController
|
160
|
+
include Telegram::Bot::UpdatesController::Session
|
161
|
+
# You can override global config
|
162
|
+
self.session_store = :file_store
|
163
|
+
|
164
|
+
def write(text = nil, *)
|
165
|
+
session[:text] = text
|
166
|
+
end
|
167
|
+
|
168
|
+
def read
|
169
|
+
session[:text]
|
170
|
+
end
|
171
|
+
|
172
|
+
private
|
173
|
+
# By default it uses bot's username and user's id as a session key.
|
174
|
+
# Chat's id is used only when `from` field is empty.
|
175
|
+
# Override `session_key` method to change this behavior.
|
176
|
+
def session_key
|
177
|
+
# In this case session will persist for user only in specific chat:
|
178
|
+
"#{bot.username}:#{chat['id']}:#{from['id']}"
|
179
|
+
end
|
180
|
+
end
|
181
|
+
```
|
182
|
+
|
124
183
|
### Routes
|
125
184
|
|
126
185
|
Use `telegram_webhooks` helper to add routes. It will create routes for bots
|
@@ -181,4 +240,4 @@ push git commits and tags, and push the `.gem` file to [rubygems.org](https://ru
|
|
181
240
|
|
182
241
|
## Contributing
|
183
242
|
|
184
|
-
Bug reports and pull requests are welcome on GitHub at https://github.com/
|
243
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/telegram-bot-rb/telegram-bot.
|
data/lib/telegram/bot.rb
CHANGED
@@ -1,97 +1,17 @@
|
|
1
|
-
require '
|
2
|
-
require 'json'
|
3
|
-
require 'active_support/core_ext/string/inflections'
|
4
|
-
require 'active_support/core_ext/hash/keys'
|
5
|
-
require 'active_support/core_ext/array/wrap'
|
6
|
-
require 'telegram/bottable'
|
1
|
+
require 'telegram/bot/config_methods'
|
7
2
|
|
8
3
|
module Telegram
|
9
|
-
extend
|
4
|
+
extend Bot::ConfigMethods
|
10
5
|
|
11
|
-
|
6
|
+
module Bot
|
12
7
|
class Error < StandardError; end
|
13
8
|
class NotFound < Error; end
|
14
9
|
|
10
|
+
autoload :Client, 'telegram/bot/client'
|
11
|
+
autoload :ClientStub, 'telegram/bot/client_stub'
|
15
12
|
autoload :Middleware, 'telegram/bot/middleware'
|
16
13
|
autoload :UpdatesController, 'telegram/bot/updates_controller'
|
17
14
|
autoload :UpdatesPoller, 'telegram/bot/updates_poller'
|
18
|
-
|
19
|
-
URL_TEMPLATE = 'https://api.telegram.org/bot%s/'.freeze
|
20
|
-
|
21
|
-
class << self
|
22
|
-
# Accepts different options to initialize bot.
|
23
|
-
def wrap(input)
|
24
|
-
case input
|
25
|
-
when self then input
|
26
|
-
when Array then input.map(&method(__callee__))
|
27
|
-
when Hash then
|
28
|
-
input = input.stringify_keys
|
29
|
-
new input['token'], input['username']
|
30
|
-
else
|
31
|
-
new(input)
|
32
|
-
end
|
33
|
-
end
|
34
|
-
end
|
35
|
-
|
36
|
-
attr_reader :client, :token, :username, :base_uri
|
37
|
-
|
38
|
-
def initialize(token, username = nil)
|
39
|
-
@client = HTTPClient.new
|
40
|
-
@token = token
|
41
|
-
@username = username
|
42
|
-
@base_uri = format URL_TEMPLATE, token
|
43
|
-
end
|
44
|
-
|
45
|
-
def debug!(dev = STDOUT)
|
46
|
-
client.debug_dev = dev
|
47
|
-
end
|
48
|
-
|
49
|
-
def debug_off!
|
50
|
-
client.debug_dev = nil
|
51
|
-
end
|
52
|
-
|
53
|
-
def request(action, data = {})
|
54
|
-
res = http_request("#{base_uri}#{action}", data)
|
55
|
-
status = res.status
|
56
|
-
return JSON.parse(res.body) if 300 > status
|
57
|
-
result = JSON.parse(res.body) rescue nil # rubocop:disable RescueModifier
|
58
|
-
err_msg = "#{res.reason}: #{result && result['description'] || '-'}"
|
59
|
-
# NotFound is raised only for valid responses from Telegram
|
60
|
-
raise NotFound, err_msg if 404 == status && result
|
61
|
-
raise Error, err_msg
|
62
|
-
end
|
63
|
-
|
64
|
-
%w(
|
65
|
-
answerInlineQuery
|
66
|
-
forwardMessage
|
67
|
-
getFile
|
68
|
-
getMe
|
69
|
-
getUpdates
|
70
|
-
getUserProfilePhotos
|
71
|
-
sendAudio
|
72
|
-
sendChatAction
|
73
|
-
sendDocument
|
74
|
-
sendLocation
|
75
|
-
sendMessage
|
76
|
-
sendPhoto
|
77
|
-
sendSticker
|
78
|
-
sendVideo
|
79
|
-
sendVoice
|
80
|
-
setWebhook
|
81
|
-
).each do |method|
|
82
|
-
define_method(method.underscore) { |*args| request(method, *args) }
|
83
|
-
end
|
84
|
-
|
85
|
-
# Endpoint for low-level request. For easy host highjacking & instrumentation.
|
86
|
-
# Params are not used directly but kept for instrumentation purpose.
|
87
|
-
# You probably don't want to use this method directly.
|
88
|
-
def http_request(uri, body)
|
89
|
-
client.post(uri, body)
|
90
|
-
end
|
91
|
-
|
92
|
-
def inspect
|
93
|
-
"#<Telegram::Bot##{object_id}(#{@username})>"
|
94
|
-
end
|
95
15
|
end
|
96
16
|
end
|
97
17
|
|
@@ -0,0 +1,111 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'httpclient'
|
3
|
+
require 'active_support/core_ext/string/inflections'
|
4
|
+
require 'active_support/core_ext/hash/keys'
|
5
|
+
|
6
|
+
module Telegram
|
7
|
+
module Bot
|
8
|
+
class Client
|
9
|
+
autoload :TypedResponse, 'telegram/bot/client/typed_response'
|
10
|
+
|
11
|
+
URL_TEMPLATE = 'https://api.telegram.org/bot%s/'.freeze
|
12
|
+
|
13
|
+
class << self
|
14
|
+
# Accepts different options to initialize bot.
|
15
|
+
def wrap(input)
|
16
|
+
case input
|
17
|
+
when self then input
|
18
|
+
when Array then input.map(&method(__callee__))
|
19
|
+
when Hash then
|
20
|
+
input = input.stringify_keys
|
21
|
+
new input['token'], input['username']
|
22
|
+
else
|
23
|
+
new(input)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# Prepend TypedResponse module.
|
28
|
+
def typed_response!
|
29
|
+
prepend TypedResponse
|
30
|
+
end
|
31
|
+
|
32
|
+
# Encodes nested hashes as json.
|
33
|
+
def prepare_body(body)
|
34
|
+
body.each do |k, val|
|
35
|
+
body[k] = val.to_json if val.is_a?(Hash)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
attr_reader :client, :token, :username, :base_uri
|
41
|
+
|
42
|
+
def initialize(token, username = nil)
|
43
|
+
@client = HTTPClient.new
|
44
|
+
@token = token
|
45
|
+
@username = username
|
46
|
+
@base_uri = format URL_TEMPLATE, token
|
47
|
+
end
|
48
|
+
|
49
|
+
def debug!(dev = STDOUT)
|
50
|
+
if block_given?
|
51
|
+
begin
|
52
|
+
old_dev = client.debug_dev
|
53
|
+
client.debug_dev = dev
|
54
|
+
yield
|
55
|
+
ensure
|
56
|
+
client.debug_dev = old_dev
|
57
|
+
end
|
58
|
+
else
|
59
|
+
client.debug_dev = dev
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def debug_off!
|
64
|
+
client.debug_dev = nil
|
65
|
+
end
|
66
|
+
|
67
|
+
def request(action, body = {})
|
68
|
+
res = http_request("#{base_uri}#{action}", self.class.prepare_body(body))
|
69
|
+
status = res.status
|
70
|
+
return JSON.parse(res.body) if 300 > status
|
71
|
+
result = JSON.parse(res.body) rescue nil # rubocop:disable RescueModifier
|
72
|
+
err_msg = "#{res.reason}: #{result && result['description'] || '-'}"
|
73
|
+
# NotFound is raised only for valid responses from Telegram
|
74
|
+
raise NotFound, err_msg if 404 == status && result
|
75
|
+
raise Error, err_msg
|
76
|
+
end
|
77
|
+
|
78
|
+
%w(
|
79
|
+
answerInlineQuery
|
80
|
+
forwardMessage
|
81
|
+
getFile
|
82
|
+
getMe
|
83
|
+
getUpdates
|
84
|
+
getUserProfilePhotos
|
85
|
+
sendAudio
|
86
|
+
sendChatAction
|
87
|
+
sendDocument
|
88
|
+
sendLocation
|
89
|
+
sendMessage
|
90
|
+
sendPhoto
|
91
|
+
sendSticker
|
92
|
+
sendVideo
|
93
|
+
sendVoice
|
94
|
+
setWebhook
|
95
|
+
).each do |method|
|
96
|
+
define_method(method.underscore) { |*args| request(method, *args) }
|
97
|
+
end
|
98
|
+
|
99
|
+
# Endpoint for low-level request. For easy host highjacking & instrumentation.
|
100
|
+
# Params are not used directly but kept for instrumentation purpose.
|
101
|
+
# You probably don't want to use this method directly.
|
102
|
+
def http_request(uri, body)
|
103
|
+
client.post(uri, body)
|
104
|
+
end
|
105
|
+
|
106
|
+
def inspect
|
107
|
+
"#<Telegram::Bot::Client##{object_id}(#{@username})>"
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module Telegram
|
2
|
+
module Bot
|
3
|
+
class Client
|
4
|
+
# Actions with type-casted results. Install `telegram-bot-types` gem first.
|
5
|
+
module TypedResponse
|
6
|
+
{
|
7
|
+
getFile: :File,
|
8
|
+
getMe: :User,
|
9
|
+
getUpdates: [:Update],
|
10
|
+
getUserProfilePhotos: :UserProfilePhotos,
|
11
|
+
|
12
|
+
forwardMessage: :Message,
|
13
|
+
sendAudio: :Message,
|
14
|
+
sendDocument: :Message,
|
15
|
+
sendLocation: :Message,
|
16
|
+
sendMessage: :Message,
|
17
|
+
sendPhoto: :Message,
|
18
|
+
sendSticker: :Message,
|
19
|
+
sendVideo: :Message,
|
20
|
+
sendVoice: :Message,
|
21
|
+
}.each do |method, type|
|
22
|
+
next unless type
|
23
|
+
if type.is_a?(Array)
|
24
|
+
type_class = Types.const_get(type.first)
|
25
|
+
define_method(method.to_s.underscore) do |*args|
|
26
|
+
request(method, *args)['result'].map { |x| type_class.new(x) }
|
27
|
+
end
|
28
|
+
else
|
29
|
+
type_class = Types.const_get(type)
|
30
|
+
define_method(method.to_s.underscore) do |*args|
|
31
|
+
type_class.new request(method, *args)['result']
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Telegram
|
2
|
+
module Bot
|
3
|
+
# Stubbed client for tests. Saves all requests into #requests hash.
|
4
|
+
class ClientStub < Client
|
5
|
+
attr_reader :requests
|
6
|
+
|
7
|
+
def initialize(username = nil)
|
8
|
+
@username = username
|
9
|
+
reset
|
10
|
+
end
|
11
|
+
|
12
|
+
def reset
|
13
|
+
@requests = Hash.new { |h, k| h[k] = [] }
|
14
|
+
end
|
15
|
+
|
16
|
+
def request(action, body)
|
17
|
+
requests[action.to_sym] << body
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'active_support/core_ext/hash/keys'
|
2
|
+
require 'active_support/core_ext/hash/transform_values'
|
3
|
+
|
4
|
+
module Telegram
|
5
|
+
module Bot
|
6
|
+
module ConfigMethods
|
7
|
+
# Overwrite config.
|
8
|
+
attr_writer :bots_config
|
9
|
+
|
10
|
+
# Keep this setting here, so we can avoid loading Bot::UpdatesPoller
|
11
|
+
# when polling is disabled.
|
12
|
+
attr_writer :bot_poller_mode
|
13
|
+
|
14
|
+
# It just tells routes helpers whether to add routed bots to
|
15
|
+
# Bot::UpdatesPoller, so their config will be available by bot key in
|
16
|
+
# Bot::UpdatesPoller.start.
|
17
|
+
def bot_poller_mode?
|
18
|
+
return @bot_poller_mode if defined?(@bot_poller_mode)
|
19
|
+
Rails.env.development? if defined?(Rails)
|
20
|
+
end
|
21
|
+
|
22
|
+
# Hash of bots made with bots_config.
|
23
|
+
def bots
|
24
|
+
@bots ||= bots_config.transform_values(&Client.method(:wrap))
|
25
|
+
end
|
26
|
+
|
27
|
+
# Default bot.
|
28
|
+
def bot
|
29
|
+
@bot ||= bots[:default]
|
30
|
+
end
|
31
|
+
|
32
|
+
# Returns config for .bots method. By default uses `telegram['bots']` section
|
33
|
+
# from `secrets.yml` merging `telegram['bot']` at `:default` key.
|
34
|
+
#
|
35
|
+
# Can be overwritten with .bots_config=
|
36
|
+
def bots_config
|
37
|
+
return @bots_config if @bots_config
|
38
|
+
telegram_config = Rails.application.secrets[:telegram]
|
39
|
+
(telegram_config['bots'] || {}).symbolize_keys.tap do |config|
|
40
|
+
default = telegram_config['bot']
|
41
|
+
config[:default] = default if default
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
data/lib/telegram/bot/railtie.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
require 'telegram/bot/routes_helper'
|
2
2
|
|
3
3
|
module Telegram
|
4
|
-
|
4
|
+
module Bot
|
5
5
|
class Railtie < Rails::Railtie
|
6
6
|
config.telegram_updates_controller = ActiveSupport::OrderedOptions.new
|
7
7
|
|
@@ -18,6 +18,7 @@ module Telegram
|
|
18
18
|
|
19
19
|
ActiveSupport.on_load('telegram.bot.updates_controller') do
|
20
20
|
self.logger = options.logger || Rails.logger
|
21
|
+
self.session_store = options.session_store || Rails.cache
|
21
22
|
end
|
22
23
|
end
|
23
24
|
|
@@ -1,7 +1,8 @@
|
|
1
1
|
require 'telegram/bot'
|
2
|
+
require 'active_support/core_ext/array/wrap'
|
2
3
|
|
3
4
|
module Telegram
|
4
|
-
|
5
|
+
module Bot
|
5
6
|
module RoutesHelper
|
6
7
|
class << self
|
7
8
|
# Returns route name for given bot. Result depends on `Telegram.bots`.
|
@@ -42,7 +43,7 @@ module Telegram
|
|
42
43
|
controllers = Hash[bots.map { |x| [x, controllers] }]
|
43
44
|
end
|
44
45
|
controllers.each do |bot, controller|
|
45
|
-
bot =
|
46
|
+
bot = Client.wrap(bot)
|
46
47
|
controller, bot_options = controller if controller.is_a?(Array)
|
47
48
|
params = {
|
48
49
|
to: Middleware.new(bot, controller),
|
@@ -1,15 +1,32 @@
|
|
1
1
|
require 'abstract_controller'
|
2
2
|
require 'active_support/callbacks'
|
3
|
+
require 'active_support/version'
|
3
4
|
|
4
5
|
module Telegram
|
5
|
-
|
6
|
+
module Bot
|
6
7
|
class UpdatesController < AbstractController::Base
|
7
|
-
|
8
|
-
include AbstractController::Translation
|
8
|
+
abstract!
|
9
9
|
|
10
|
+
require 'telegram/bot/updates_controller/session'
|
10
11
|
require 'telegram/bot/updates_controller/log_subscriber'
|
11
12
|
require 'telegram/bot/updates_controller/instrumentation'
|
13
|
+
|
14
|
+
include AbstractController::Callbacks
|
15
|
+
# Redefine callbacks with default terminator.
|
16
|
+
if ActiveSupport.gem_version >= Gem::Version.new('5')
|
17
|
+
define_callbacks :process_action,
|
18
|
+
skip_after_callbacks_if_terminated: true
|
19
|
+
else
|
20
|
+
define_callbacks :process_action,
|
21
|
+
terminator: ->(_, result) { result == false },
|
22
|
+
skip_after_callbacks_if_terminated: true
|
23
|
+
end
|
24
|
+
|
25
|
+
include AbstractController::Translation
|
12
26
|
prepend Instrumentation
|
27
|
+
extend Session::ConfigMethods
|
28
|
+
|
29
|
+
autoload :TypedUpdate, 'telegram/bot/updates_controller/typed_update'
|
13
30
|
|
14
31
|
PAYLOAD_TYPES = %w(
|
15
32
|
message
|
@@ -18,7 +35,6 @@ module Telegram
|
|
18
35
|
).freeze
|
19
36
|
CMD_REGEX = %r{\A/([a-z\d_]{,31})(@(\S+))?(\s|$)}i
|
20
37
|
CONFLICT_CMD_REGEX = Regexp.new("^(#{PAYLOAD_TYPES.join('|')}|\\d)")
|
21
|
-
abstract!
|
22
38
|
|
23
39
|
class << self
|
24
40
|
def dispatch(*args)
|
@@ -0,0 +1,31 @@
|
|
1
|
+
RSpec.shared_context 'telegram/bot/updates_controller' do
|
2
|
+
let(:controller_class) { described_class }
|
3
|
+
let(:instance) { controller_class.new(bot, update) }
|
4
|
+
let(:update) { {payload_type => payload} }
|
5
|
+
let(:payload_type) { 'some_type' }
|
6
|
+
let(:payload) { double(:payload) }
|
7
|
+
let(:bot) { Telegram::Bot::ClientStub.new(bot_name) }
|
8
|
+
let(:bot_name) { 'bot' }
|
9
|
+
let(:session) do
|
10
|
+
session = Telegram::Bot::UpdatesController::Session::TestSessionHash.new
|
11
|
+
allow_any_instance_of(controller_class).to receive(:session) { session }
|
12
|
+
session
|
13
|
+
end
|
14
|
+
|
15
|
+
def dispatch_message(text, options = {})
|
16
|
+
payload = build_payload :message, options.merge(text: text)
|
17
|
+
controller_class.dispatch bot, payload
|
18
|
+
end
|
19
|
+
|
20
|
+
def build_payload(type, content)
|
21
|
+
deep_stringify type => content
|
22
|
+
end
|
23
|
+
|
24
|
+
def deep_stringify(input)
|
25
|
+
case input
|
26
|
+
when Array then input.map(&method(__callee__))
|
27
|
+
when Hash then input.transform_keys(&:to_s).transform_values(&method(__callee__))
|
28
|
+
else input
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
require 'rack/session/abstract/id'
|
2
|
+
require 'active_support/cache'
|
3
|
+
|
4
|
+
module Telegram
|
5
|
+
module Bot
|
6
|
+
class UpdatesController
|
7
|
+
# Add functionality to store data between requests.
|
8
|
+
module Session
|
9
|
+
extend ActiveSupport::Concern
|
10
|
+
|
11
|
+
def process_action(*)
|
12
|
+
super
|
13
|
+
ensure
|
14
|
+
session.commit
|
15
|
+
end
|
16
|
+
|
17
|
+
protected
|
18
|
+
|
19
|
+
def session
|
20
|
+
@_session ||= SessionHash.new(self.class.session_store, session_key)
|
21
|
+
end
|
22
|
+
|
23
|
+
def session_key
|
24
|
+
"#{bot.username}:#{from ? "from:#{from['id']}" : "chat:#{chat['id']}"}"
|
25
|
+
end
|
26
|
+
|
27
|
+
# Rack::Session::Abstract::SessionHash is taken to provide lazy loading.
|
28
|
+
# All methods that access store are overriden to support
|
29
|
+
# ActiveSupport::Cache::Store stores.
|
30
|
+
class SessionHash < Rack::Session::Abstract::SessionHash
|
31
|
+
attr_reader :id
|
32
|
+
|
33
|
+
def initialize(store, id)
|
34
|
+
@store = store
|
35
|
+
@id = id
|
36
|
+
end
|
37
|
+
|
38
|
+
def destroy
|
39
|
+
clear
|
40
|
+
@store.delete(id)
|
41
|
+
end
|
42
|
+
|
43
|
+
def exists?
|
44
|
+
return @exists if defined?(@exists)
|
45
|
+
@data = {}
|
46
|
+
@exists = @store.exist? id
|
47
|
+
end
|
48
|
+
|
49
|
+
def load!
|
50
|
+
session = @store.read(id)
|
51
|
+
@data = session ? stringify_keys(session) : {}
|
52
|
+
@loaded = true
|
53
|
+
end
|
54
|
+
|
55
|
+
def commit
|
56
|
+
return unless loaded?
|
57
|
+
data = to_hash.delete_if { |_, v| v.nil? }
|
58
|
+
@store.write(id, data)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
class TestSessionHash < SessionHash
|
63
|
+
def initialize
|
64
|
+
@data = {}
|
65
|
+
@loaded = true
|
66
|
+
@exists = true
|
67
|
+
end
|
68
|
+
|
69
|
+
alias_method :destroy, :clear
|
70
|
+
alias_method :load!, :id
|
71
|
+
alias_method :commit, :id
|
72
|
+
end
|
73
|
+
|
74
|
+
module ConfigMethods
|
75
|
+
delegate :session_store, to: :config
|
76
|
+
|
77
|
+
def session_store=(store)
|
78
|
+
config.session_store = ActiveSupport::Cache.lookup_store(store)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module Telegram
|
2
|
+
module Bot
|
3
|
+
class UpdatesController
|
4
|
+
# Include this module to type cast update to Virtus model
|
5
|
+
# using `telegram-bot-types` gem (install this gem first).
|
6
|
+
module TypedUpdate
|
7
|
+
def initialize(bot, update)
|
8
|
+
update = Types::Update.new(update) if update && !update.is_a?(Types::Update)
|
9
|
+
super
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -1,5 +1,5 @@
|
|
1
1
|
module Telegram
|
2
|
-
|
2
|
+
module Bot
|
3
3
|
# Supposed to be used in development environments only.
|
4
4
|
class UpdatesPoller
|
5
5
|
class << self
|
@@ -15,7 +15,7 @@ module Telegram
|
|
15
15
|
end
|
16
16
|
|
17
17
|
def start(bot_id, controller = nil)
|
18
|
-
bot = bot_id.is_a?(Symbol) ? Telegram.bots[bot_id] :
|
18
|
+
bot = bot_id.is_a?(Symbol) ? Telegram.bots[bot_id] : Client.wrap(bot_id)
|
19
19
|
instance = controller ? new(bot, controller) : instances[bot]
|
20
20
|
raise "Poller not found for #{bot_id.inspect}" unless instance
|
21
21
|
instance.start
|
data/lib/telegram/bot/version.rb
CHANGED
data/telegram-bot.gemspec
CHANGED
@@ -10,7 +10,7 @@ Gem::Specification.new do |spec|
|
|
10
10
|
spec.email = ['melentievm@gmail.com']
|
11
11
|
|
12
12
|
spec.summary = 'Library for building Telegram Bots with Rails integration'
|
13
|
-
spec.homepage = 'https://github.com/
|
13
|
+
spec.homepage = 'https://github.com/telegram-bot-rb/telegram-bot'
|
14
14
|
spec.license = 'MIT'
|
15
15
|
|
16
16
|
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^spec/}) }
|
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.4.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-02-
|
11
|
+
date: 2016-02-28 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -101,17 +101,23 @@ files:
|
|
101
101
|
- bin/setup
|
102
102
|
- lib/tasks/telegram-bot.rake
|
103
103
|
- lib/telegram/bot.rb
|
104
|
+
- lib/telegram/bot/client.rb
|
105
|
+
- lib/telegram/bot/client/typed_response.rb
|
106
|
+
- lib/telegram/bot/client_stub.rb
|
107
|
+
- lib/telegram/bot/config_methods.rb
|
104
108
|
- lib/telegram/bot/middleware.rb
|
105
109
|
- lib/telegram/bot/railtie.rb
|
106
110
|
- lib/telegram/bot/routes_helper.rb
|
107
111
|
- lib/telegram/bot/updates_controller.rb
|
108
112
|
- lib/telegram/bot/updates_controller/instrumentation.rb
|
109
113
|
- lib/telegram/bot/updates_controller/log_subscriber.rb
|
114
|
+
- lib/telegram/bot/updates_controller/rspec_helpers.rb
|
115
|
+
- lib/telegram/bot/updates_controller/session.rb
|
116
|
+
- lib/telegram/bot/updates_controller/typed_update.rb
|
110
117
|
- lib/telegram/bot/updates_poller.rb
|
111
118
|
- lib/telegram/bot/version.rb
|
112
|
-
- lib/telegram/bottable.rb
|
113
119
|
- telegram-bot.gemspec
|
114
|
-
homepage: https://github.com/
|
120
|
+
homepage: https://github.com/telegram-bot-rb/telegram-bot
|
115
121
|
licenses:
|
116
122
|
- MIT
|
117
123
|
metadata: {}
|
data/lib/telegram/bottable.rb
DELETED
@@ -1,41 +0,0 @@
|
|
1
|
-
module Telegram
|
2
|
-
module Bottable
|
3
|
-
# Overwrite config.
|
4
|
-
attr_writer :bots_config
|
5
|
-
|
6
|
-
# Keep this setting here, so we can avoid loading Bot::UpdatesPoller
|
7
|
-
# when polling is disabled.
|
8
|
-
attr_writer :bot_poller_mode
|
9
|
-
|
10
|
-
# It just tells routes helpers whether to add routed bots to
|
11
|
-
# Bot::UpdatesPoller, so their config will be available by bot key in
|
12
|
-
# Bot::UpdatesPoller.start.
|
13
|
-
def bot_poller_mode?
|
14
|
-
return @bot_poller_mode if defined?(@bot_poller_mode)
|
15
|
-
Rails.env.development? if defined?(Rails)
|
16
|
-
end
|
17
|
-
|
18
|
-
# Hash of bots made with bots_config.
|
19
|
-
def bots
|
20
|
-
@bots ||= bots_config.transform_values(&Bot.method(:wrap))
|
21
|
-
end
|
22
|
-
|
23
|
-
# Default bot.
|
24
|
-
def bot
|
25
|
-
@bot ||= bots[:default]
|
26
|
-
end
|
27
|
-
|
28
|
-
# Returns config for .bots method. By default uses `telegram['bots']` section
|
29
|
-
# from `secrets.yml` merging `telegram['bot']` at `:default` key.
|
30
|
-
#
|
31
|
-
# Can be overwritten with .bots_config=
|
32
|
-
def bots_config
|
33
|
-
return @bots_config if @bots_config
|
34
|
-
telegram_config = Rails.application.secrets[:telegram]
|
35
|
-
(telegram_config['bots'] || {}).symbolize_keys.tap do |config|
|
36
|
-
default = telegram_config['bot']
|
37
|
-
config[:default] = default if default
|
38
|
-
end
|
39
|
-
end
|
40
|
-
end
|
41
|
-
end
|