telegram-bot 0.3.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 +7 -0
- data/.gitignore +9 -0
- data/.rspec +2 -0
- data/.rubocop.yml +27 -0
- data/.travis.yml +6 -0
- data/Gemfile +15 -0
- data/LICENSE.txt +21 -0
- data/README.md +184 -0
- data/Rakefile +22 -0
- data/bin/console +7 -0
- data/bin/git-hooks/pre-commit +14 -0
- data/bin/install_git_hooks +8 -0
- data/bin/setup +8 -0
- data/lib/tasks/telegram-bot.rake +22 -0
- data/lib/telegram/bot/middleware.rb +26 -0
- data/lib/telegram/bot/railtie.rb +33 -0
- data/lib/telegram/bot/routes_helper.rb +58 -0
- data/lib/telegram/bot/updates_controller/instrumentation.rb +79 -0
- data/lib/telegram/bot/updates_controller/log_subscriber.rb +38 -0
- data/lib/telegram/bot/updates_controller.rb +105 -0
- data/lib/telegram/bot/updates_poller.rb +88 -0
- data/lib/telegram/bot/version.rb +9 -0
- data/lib/telegram/bot.rb +98 -0
- data/lib/telegram/bottable.rb +41 -0
- data/telegram-bot.gemspec +28 -0
- metadata +138 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: d30226c4449c1cbe5ebd12cf4ec90203532fea57
|
4
|
+
data.tar.gz: 8fc903c5a4cf84377bf258584f7d2adf04741ef5
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 1a436e375c7b22d18c5c18be2a55e4189f909e473106350b0c8e7d0d8336b55e5b76c5cf625800e753627715f665300049c282f96c74e48200ed39d36f9e1094
|
7
|
+
data.tar.gz: d2c166e8ffaeec12fb83a3a391cc7aa05b431ad5a6151614ca0bbe3449ce0f4c5bf5e33538cbb0ba1d75923dc86e4ae600dc971755367f22916abf3834df38c6
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.rubocop.yml
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
Rails: {Enabled: true}
|
2
|
+
|
3
|
+
Style/Alias: {Enabled: false}
|
4
|
+
Style/AlignParameters:
|
5
|
+
# Disable, till rubocop supports combination of styles.
|
6
|
+
# Use one of this styles where appropriate, keep it clean, compact and readable.
|
7
|
+
Enabled: false
|
8
|
+
# EnforcedStyle:
|
9
|
+
# - with_first_parameter
|
10
|
+
# - with_fixed_indentation
|
11
|
+
Style/ClosingParenthesisIndentation: {Enabled: false}
|
12
|
+
Style/Documentation: {Enabled: false}
|
13
|
+
Style/DotPosition: {EnforcedStyle: trailing}
|
14
|
+
Style/IfUnlessModifier: {Enabled: false}
|
15
|
+
Style/ModuleFunction: {Enabled: false}
|
16
|
+
Style/MultilineOperationIndentation: {EnforcedStyle: indented}
|
17
|
+
Style/NestedParenthesizedCalls: {Enabled: false}
|
18
|
+
Style/PredicateName: {Enabled: false}
|
19
|
+
Style/SignalException: {EnforcedStyle: only_raise}
|
20
|
+
Style/SpaceInsideHashLiteralBraces: {EnforcedStyle: no_space}
|
21
|
+
Style/TrailingCommaInArguments: {Enabled: false}
|
22
|
+
Style/TrailingCommaInLiteral: {EnforcedStyleForMultiline: comma}
|
23
|
+
|
24
|
+
Metrics/AbcSize: {Max: 21}
|
25
|
+
Metrics/LineLength: {Max: 100}
|
26
|
+
Metrics/MethodLength: {Max: 30}
|
27
|
+
Metrics/CyclomaticComplexity: {Max: 8}
|
data/.travis.yml
ADDED
data/Gemfile
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
source 'https://rubygems.org'
|
2
|
+
gemspec
|
3
|
+
|
4
|
+
group :development do
|
5
|
+
gem 'sdoc', '~> 0.4.1'
|
6
|
+
gem 'pry', '~> 0.10.1'
|
7
|
+
gem 'pry-byebug', '~> 3.2.0'
|
8
|
+
|
9
|
+
gem 'rspec', '~> 3.3.0'
|
10
|
+
gem 'rspec-its', '~> 1.1.0'
|
11
|
+
|
12
|
+
gem 'rubocop', '~> 0.37.0'
|
13
|
+
|
14
|
+
gem 'coveralls', '~> 0.8.2', require: false
|
15
|
+
end
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2015 Max Melentiev
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,184 @@
|
|
1
|
+
# Telegram::Bot
|
2
|
+
|
3
|
+
[](http://badge.fury.io/rb/telegram-bot)
|
4
|
+
[](https://codeclimate.com/github/printercu/telegram-bot)
|
5
|
+
[](https://travis-ci.org/printercu/telegram-bot)
|
6
|
+
|
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 mode
|
9
|
+
in development, but you can use poller in production if you want.
|
10
|
+
|
11
|
+
Package contains:
|
12
|
+
|
13
|
+
- Ligthweight client to bot API (with fast and thread-safe
|
14
|
+
[httpclient](https://github.com/nahi/httpclient) is under the hood.)
|
15
|
+
- Controller with message parser. Allows to write separate methods for each command.
|
16
|
+
- Middleware and routes helpers for production env.
|
17
|
+
- Poller with automatic source-reloader for development env.
|
18
|
+
- Rake tasks to update webhook urls.
|
19
|
+
|
20
|
+
## Installation
|
21
|
+
|
22
|
+
Add this line to your application's Gemfile:
|
23
|
+
|
24
|
+
```ruby
|
25
|
+
gem 'telegram-bot'
|
26
|
+
```
|
27
|
+
|
28
|
+
And then execute:
|
29
|
+
|
30
|
+
$ bundle
|
31
|
+
|
32
|
+
Or install it yourself as:
|
33
|
+
|
34
|
+
$ gem install telegram-bot
|
35
|
+
|
36
|
+
## Usage
|
37
|
+
|
38
|
+
### Configuration
|
39
|
+
|
40
|
+
Add `telegram` section into `secrets.yml`:
|
41
|
+
|
42
|
+
```yml
|
43
|
+
telegram:
|
44
|
+
bots:
|
45
|
+
# just set the token
|
46
|
+
chat: TOKEN_1
|
47
|
+
# or add username to support commands with mentions (/help@ChatBot)
|
48
|
+
auction:
|
49
|
+
token: TOKEN_2
|
50
|
+
username: ChatBot
|
51
|
+
|
52
|
+
# Single bot can be specified like this
|
53
|
+
bot: TOKEN
|
54
|
+
# or
|
55
|
+
bot:
|
56
|
+
token: TOKEN
|
57
|
+
username: SomeBot
|
58
|
+
```
|
59
|
+
|
60
|
+
### Client
|
61
|
+
|
62
|
+
From now clients will be accessible with `Telegram.bots[:chat]` or `Telegram.bots[:auction]`.
|
63
|
+
Single bot can be accessed with `Telegram.bot` or `Telegram.bots[:default]`.
|
64
|
+
|
65
|
+
You can create clients manually with `Telegram::Bot.new(token, username)`.
|
66
|
+
Username is optional and used only to parse commands with mentions.
|
67
|
+
|
68
|
+
Client has all available methods in underscored style
|
69
|
+
(`answer_inline_query` instead of `answerInlineQuery`).
|
70
|
+
All this methods just post given params to specific URL.
|
71
|
+
|
72
|
+
```ruby
|
73
|
+
bot.send_message chat_id: chat_id, text: 'Test'
|
74
|
+
```
|
75
|
+
|
76
|
+
### Controller
|
77
|
+
|
78
|
+
```ruby
|
79
|
+
class Telegram::WebhookController < Telegram::Bot::UpdatesController
|
80
|
+
# use callbacks like in any other controllers
|
81
|
+
around_action :set_locale
|
82
|
+
|
83
|
+
# Every update can have one of: message, inline_query & chosen_inline_result.
|
84
|
+
# Define method with same name to respond to this updates.
|
85
|
+
def message(message)
|
86
|
+
# message can be also accessed via instance method
|
87
|
+
message == self.payload # true
|
88
|
+
# store_message(message['text'])
|
89
|
+
end
|
90
|
+
|
91
|
+
# Define public methods to respond to commands.
|
92
|
+
# Command arguments will be parsed and passed to the method.
|
93
|
+
# Be sure to use splat args and default values to not get errors when
|
94
|
+
# someone passed more or less arguments in the message.
|
95
|
+
#
|
96
|
+
# For some commands like /message or /123 method names should start with
|
97
|
+
# `on_` to avoid conflicts.
|
98
|
+
def start(data = nil, *)
|
99
|
+
# do_smth_with(data)
|
100
|
+
|
101
|
+
# There are `chat` & `from` shortcut methods.
|
102
|
+
response = from ? "Hello #{from['username']}!" : 'Hi there!'
|
103
|
+
# There is `reply_with` helper to set basic fields
|
104
|
+
# like `reply_to_message` & `chat_id`.
|
105
|
+
reply_with :message, text: response
|
106
|
+
end
|
107
|
+
|
108
|
+
private
|
109
|
+
|
110
|
+
def set_locale(&block)
|
111
|
+
I18n.with_locale(locale_for_update, &block)
|
112
|
+
end
|
113
|
+
|
114
|
+
def locale_for_update
|
115
|
+
if from
|
116
|
+
# locale for user
|
117
|
+
elsif chat
|
118
|
+
# locale for chat
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
```
|
123
|
+
|
124
|
+
### Routes
|
125
|
+
|
126
|
+
Use `telegram_webhooks` helper to add routes. It will create routes for bots
|
127
|
+
at "telegram/#{bot.token}" path.
|
128
|
+
|
129
|
+
```ruby
|
130
|
+
# Create routes for all Telegram.bots to use same controller:
|
131
|
+
telegram_webhooks TelegramController
|
132
|
+
|
133
|
+
# Or pass custom bots usin any of supported config options:
|
134
|
+
telegram_webhooks TelegramController,
|
135
|
+
bot,
|
136
|
+
{token: token, username: username},
|
137
|
+
other_bot_token
|
138
|
+
|
139
|
+
# Use different controllers for each bot:
|
140
|
+
telegram_webhooks bot => TelegramChatController,
|
141
|
+
other_bot => TelegramAuctionController
|
142
|
+
|
143
|
+
# telegram_webhooks creates named routes.
|
144
|
+
# Route name depends on `Telegram.bots`.
|
145
|
+
# When there is single bot it will use 'telegram_webhook'.
|
146
|
+
# When there are it will use bot's key in the `Telegram.bots` as prefix
|
147
|
+
# (eg. `chat_telegram_webhook`).
|
148
|
+
# You can override this options or specify others:
|
149
|
+
telegram_webhooks TelegramController, as: :my_webhook
|
150
|
+
telegram_webhooks bot => [TelegramChatController, as: :chat_webhook],
|
151
|
+
other_bot => [TelegramAuctionController,
|
152
|
+
```
|
153
|
+
|
154
|
+
For Rack applications you can also use `Telegram::Bot::Middleware` or just
|
155
|
+
call `.dispatch(bot, update)` on controller.
|
156
|
+
|
157
|
+
### Development & Debugging
|
158
|
+
|
159
|
+
Use `rake telegram:bot:poller BOT=chat` to run poller. It'll automatically load
|
160
|
+
changes without restart in development env. This task will not if you don't use
|
161
|
+
`telegram_webhooks`.
|
162
|
+
|
163
|
+
You can run poller manually with
|
164
|
+
`Telegram::Bot::UpdatesPoller.start(bot, controller_class)`.
|
165
|
+
|
166
|
+
### Deploying
|
167
|
+
|
168
|
+
Use `rake telegram:bot:set_webhook` to update webhook url for all configured bots.
|
169
|
+
Certificate can be specified with `CERT=path/to/cert`.
|
170
|
+
|
171
|
+
## Development
|
172
|
+
|
173
|
+
After checking out the repo, run `bin/setup` to install dependencies.
|
174
|
+
Then, run `rake spec` to run the tests.
|
175
|
+
You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
176
|
+
|
177
|
+
To install this gem onto your local machine, run `bundle exec rake install`.
|
178
|
+
To release a new version, update the version number in `version.rb`,
|
179
|
+
and then run `bundle exec rake release`, which will create a git tag for the version,
|
180
|
+
push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
181
|
+
|
182
|
+
## Contributing
|
183
|
+
|
184
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/printercu/telegram-bot.
|
data/Rakefile
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'bundler/gem_tasks'
|
2
|
+
require 'rspec/core/rake_task'
|
3
|
+
|
4
|
+
RSpec::Core::RakeTask.new(:spec)
|
5
|
+
|
6
|
+
task default: :spec
|
7
|
+
|
8
|
+
require 'sdoc'
|
9
|
+
RDoc::Task.new(:doc) do |rdoc|
|
10
|
+
rdoc.rdoc_dir = 'doc'
|
11
|
+
|
12
|
+
rdoc.title = 'RailsStuff'
|
13
|
+
|
14
|
+
rdoc.options << '--markup' << 'markdown'
|
15
|
+
rdoc.options << '-e' << 'UTF-8'
|
16
|
+
rdoc.options << '--format' << 'sdoc'
|
17
|
+
rdoc.options << '--template' << 'rails'
|
18
|
+
rdoc.options << '--all'
|
19
|
+
|
20
|
+
rdoc.rdoc_files.include('README.md')
|
21
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
22
|
+
end
|
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/bin/bash
|
2
|
+
|
3
|
+
pattern=$(echo -n '\.rb
|
4
|
+
\.gemspec
|
5
|
+
\.jbuilder
|
6
|
+
\.rake
|
7
|
+
config\.ru
|
8
|
+
Gemfile
|
9
|
+
Rakefile' | tr "\\n" '|')
|
10
|
+
|
11
|
+
files=`git diff --cached --name-status | grep -E "^[AM].*($pattern)$" | cut -f2-`
|
12
|
+
if [ -n "$files" ]; then
|
13
|
+
bundle exec rubocop $files --force-exclusion
|
14
|
+
fi
|
data/bin/setup
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
namespace :telegram do
|
2
|
+
namespace :bot do
|
3
|
+
desc 'Run poller'
|
4
|
+
task poller: :environment do
|
5
|
+
console = ActiveSupport::Logger.new(STDERR)
|
6
|
+
Rails.logger.extend ActiveSupport::Logger.broadcast console
|
7
|
+
Telegram::Bot::UpdatesPoller.start(ENV['BOT'].try!(:to_sym) || :default)
|
8
|
+
end
|
9
|
+
|
10
|
+
desc 'Set webhook urls for all bots'
|
11
|
+
task set_webhook: :environment do
|
12
|
+
routes = Rails.application.routes.url_helpers
|
13
|
+
cert_file = ENV['CERT']
|
14
|
+
cert = File.open(cert_file) if cert_file
|
15
|
+
Telegram.bots.each_value do |bot|
|
16
|
+
route_name = Telegram::RoutesHelper.route_name_for_bot(bot)
|
17
|
+
url = routes.send("#{route_name}_url")
|
18
|
+
bot.set_webhook(url: url, certificate: cert)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'active_support/concern'
|
2
|
+
require 'action_dispatch/http/mime_type'
|
3
|
+
require 'action_dispatch/middleware/params_parser'
|
4
|
+
|
5
|
+
module Telegram
|
6
|
+
class Bot
|
7
|
+
class Middleware
|
8
|
+
attr_reader :bot, :controller
|
9
|
+
|
10
|
+
def initialize(bot, controller)
|
11
|
+
@bot = bot
|
12
|
+
@controller = controller
|
13
|
+
end
|
14
|
+
|
15
|
+
def call(env)
|
16
|
+
update = env['action_dispatch.request.request_parameters']
|
17
|
+
controller.dispatch(bot, update)
|
18
|
+
[200, {}, '']
|
19
|
+
end
|
20
|
+
|
21
|
+
def inspect
|
22
|
+
"#<#{self.class.name}(#{controller.try!(:name)})>"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'telegram/bot/routes_helper'
|
2
|
+
|
3
|
+
module Telegram
|
4
|
+
class Bot
|
5
|
+
class Railtie < Rails::Railtie
|
6
|
+
config.telegram_updates_controller = ActiveSupport::OrderedOptions.new
|
7
|
+
|
8
|
+
rake_tasks do
|
9
|
+
load 'tasks/telegram-bot.rake'
|
10
|
+
end
|
11
|
+
|
12
|
+
config.before_initialize do
|
13
|
+
::ActionDispatch::Routing::Mapper.send(:include, RoutesHelper)
|
14
|
+
end
|
15
|
+
|
16
|
+
initializer 'telegram.bot.updates_controller.set_config' do |app|
|
17
|
+
options = app.config.telegram_updates_controller
|
18
|
+
|
19
|
+
ActiveSupport.on_load('telegram.bot.updates_controller') do
|
20
|
+
self.logger = options.logger || Rails.logger
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
initializer 'telegram.bot.updates_controller.add_ar_runtime' do
|
25
|
+
ActiveSupport.on_load('telegram.bot.updates_controller') do
|
26
|
+
if defined?(ActiveRecord::Railties::ControllerRuntime)
|
27
|
+
include ActiveRecord::Railties::ControllerRuntime
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'telegram/bot'
|
2
|
+
|
3
|
+
module Telegram
|
4
|
+
class Bot
|
5
|
+
module RoutesHelper
|
6
|
+
class << self
|
7
|
+
# Returns route name for given bot. Result depends on `Telegram.bots`.
|
8
|
+
# When there is single bot it returns 'telegram_webhook'.
|
9
|
+
# When there are it will use bot's key in the `Telegram.bots` as prefix
|
10
|
+
# (eg. `chat_telegram_webhook`).
|
11
|
+
def route_name_for_bot(bot)
|
12
|
+
bots = Telegram.bots
|
13
|
+
if bots.size != 1
|
14
|
+
name = bots.invert[bot]
|
15
|
+
name && "#{name}_telegram_webhook"
|
16
|
+
end || 'telegram_webhook'
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# # Create routes for all Telegram.bots to use same controller:
|
21
|
+
# telegram_webhooks TelegramController
|
22
|
+
#
|
23
|
+
# # Or pass custom bots usin any of supported config options:
|
24
|
+
# telegram_webhooks TelegramController,
|
25
|
+
# bot,
|
26
|
+
# {token: token, username: username},
|
27
|
+
# other_bot_token
|
28
|
+
#
|
29
|
+
# # Use different controllers for each bot:
|
30
|
+
# telegram_webhooks bot => TelegramChatController,
|
31
|
+
# other_bot => TelegramAuctionController
|
32
|
+
#
|
33
|
+
# # telegram_webhooks creates named routes. See
|
34
|
+
# # RoutesHelper.route_name_for_bot for more info.
|
35
|
+
# # You can override this options or specify others:
|
36
|
+
# telegram_webhooks TelegramController, as: :my_webhook
|
37
|
+
# telegram_webhooks bot => [TelegramChatController, as: :chat_webhook],
|
38
|
+
# other_bot => [TelegramAuctionController,
|
39
|
+
def telegram_webhooks(controllers, bots = nil, **options)
|
40
|
+
unless controllers.is_a?(Hash)
|
41
|
+
bots = bots ? Array.wrap(bots) : Telegram.bots.values
|
42
|
+
controllers = Hash[bots.map { |x| [x, controllers] }]
|
43
|
+
end
|
44
|
+
controllers.each do |bot, controller|
|
45
|
+
bot = Bot.wrap(bot)
|
46
|
+
controller, bot_options = controller if controller.is_a?(Array)
|
47
|
+
params = {
|
48
|
+
to: Middleware.new(bot, controller),
|
49
|
+
as: RoutesHelper.route_name_for_bot(bot),
|
50
|
+
format: false,
|
51
|
+
}.merge!(options).merge!(bot_options || {})
|
52
|
+
post("telegram/#{bot.token}", params)
|
53
|
+
UpdatesPoller.add(bot, controller) if Telegram.bot_poller_mode?
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
module Telegram
|
2
|
+
class Bot
|
3
|
+
class UpdatesController
|
4
|
+
# Most methods are taken from ActionController::Instrumentation,
|
5
|
+
# some are slightly modified.
|
6
|
+
module Instrumentation
|
7
|
+
class << self
|
8
|
+
def prepended(base)
|
9
|
+
base.config_accessor :logger
|
10
|
+
base.extend ClassMethods
|
11
|
+
end
|
12
|
+
|
13
|
+
def instrument(action, *args, &block)
|
14
|
+
ActiveSupport::Notifications.instrument(
|
15
|
+
"#{action}.updates_controller.bot.telegram",
|
16
|
+
*args,
|
17
|
+
&block
|
18
|
+
)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def process_action(*args)
|
23
|
+
raw_payload = {
|
24
|
+
controller: self.class.name,
|
25
|
+
action: action_name,
|
26
|
+
update: update,
|
27
|
+
}
|
28
|
+
Instrumentation.instrument(:start_processing, raw_payload.dup)
|
29
|
+
Instrumentation.instrument(:process_action, raw_payload) do |payload|
|
30
|
+
begin
|
31
|
+
super
|
32
|
+
ensure
|
33
|
+
append_info_to_payload(payload)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def reply_with(type, *)
|
39
|
+
Instrumentation.instrument(:reply_with, type: type) { super }
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
# A hook invoked every time a before callback is halted.
|
45
|
+
def halted_callback_hook(filter)
|
46
|
+
Instrumentation.instrument(:halted_callback, filter: filter)
|
47
|
+
end
|
48
|
+
|
49
|
+
# A hook which allows you to clean up any time taken into account in
|
50
|
+
# views wrongly, like database querying time.
|
51
|
+
#
|
52
|
+
# def cleanup_view_runtime
|
53
|
+
# super - time_taken_in_something_expensive
|
54
|
+
# end
|
55
|
+
#
|
56
|
+
# :api: plugin
|
57
|
+
def cleanup_view_runtime #:nodoc:
|
58
|
+
yield
|
59
|
+
end
|
60
|
+
|
61
|
+
# Every time after an action is processed, this method is invoked
|
62
|
+
# with the payload, so you can add more information.
|
63
|
+
# :api: plugin
|
64
|
+
def append_info_to_payload(_payload) #:nodoc:
|
65
|
+
end
|
66
|
+
|
67
|
+
module ClassMethods
|
68
|
+
# A hook which allows other frameworks to log what happened during
|
69
|
+
# controller process action. This method should return an array
|
70
|
+
# with the messages to be added.
|
71
|
+
# :api: plugin
|
72
|
+
def log_process_action(_payload) #:nodoc:
|
73
|
+
[]
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'active_support/log_subscriber'
|
2
|
+
|
3
|
+
module Telegram
|
4
|
+
class Bot
|
5
|
+
class UpdatesController
|
6
|
+
class LogSubscriber < ActiveSupport::LogSubscriber
|
7
|
+
def start_processing(event)
|
8
|
+
info do
|
9
|
+
payload = event.payload
|
10
|
+
"Processing by #{payload[:controller]}##{payload[:action]}\n" \
|
11
|
+
" Update: #{payload[:update].to_json}"
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def process_action(event)
|
16
|
+
info do
|
17
|
+
payload = event.payload
|
18
|
+
additions = UpdatesController.log_process_action(payload)
|
19
|
+
message = "Completed in #{event.duration.round}ms"
|
20
|
+
message << " (#{additions.join(' | ')})" unless additions.blank?
|
21
|
+
message
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def reply_with(event)
|
26
|
+
info { "Replied with #{event.payload[:type]}" }
|
27
|
+
end
|
28
|
+
|
29
|
+
def halted_callback(event)
|
30
|
+
info { "Filter chain halted at #{event.payload[:filter].inspect}" }
|
31
|
+
end
|
32
|
+
|
33
|
+
delegate :logger, to: UpdatesController
|
34
|
+
attach_to 'updates_controller.bot.telegram'
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
require 'abstract_controller'
|
2
|
+
require 'active_support/callbacks'
|
3
|
+
|
4
|
+
module Telegram
|
5
|
+
class Bot
|
6
|
+
class UpdatesController < AbstractController::Base
|
7
|
+
include AbstractController::Callbacks
|
8
|
+
include AbstractController::Translation
|
9
|
+
|
10
|
+
require 'telegram/bot/updates_controller/log_subscriber'
|
11
|
+
require 'telegram/bot/updates_controller/instrumentation'
|
12
|
+
prepend Instrumentation
|
13
|
+
|
14
|
+
PAYLOAD_TYPES = %w(
|
15
|
+
message
|
16
|
+
inline_query
|
17
|
+
chosen_inline_result
|
18
|
+
).freeze
|
19
|
+
CMD_REGEX = %r{\A/([a-z\d_]{,31})(@(\S+))?(\s|$)}i
|
20
|
+
CONFLICT_CMD_REGEX = Regexp.new("^(#{PAYLOAD_TYPES.join('|')}|\\d)")
|
21
|
+
abstract!
|
22
|
+
|
23
|
+
class << self
|
24
|
+
def dispatch(*args)
|
25
|
+
new(*args).dispatch
|
26
|
+
end
|
27
|
+
|
28
|
+
# Overrid it to filter or transform commands.
|
29
|
+
# Default implementation is to convert to downcase and add `on_` prefix
|
30
|
+
# for conflicting commands.
|
31
|
+
def action_for_command(cmd)
|
32
|
+
cmd.downcase!
|
33
|
+
cmd.match(CONFLICT_CMD_REGEX) ? "on_#{cmd}" : cmd
|
34
|
+
end
|
35
|
+
|
36
|
+
# Fetches command from text message. All subsequent words are returned
|
37
|
+
# as arguments.
|
38
|
+
# If command has mention (eg. `/test@SomeBot`), it returns commands only
|
39
|
+
# for specified username. Set `username` to `true` to accept
|
40
|
+
# any commands.
|
41
|
+
def command_from_text(text, username = nil)
|
42
|
+
return unless text
|
43
|
+
match = text.match CMD_REGEX
|
44
|
+
return unless match
|
45
|
+
return if match[3] && username != true && match[3] != username
|
46
|
+
[match[1], text.split(' ').drop(1)]
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
attr_internal_reader :update, :bot, :payload, :payload_type, :is_command
|
51
|
+
alias_method :command?, :is_command
|
52
|
+
delegate :username, to: :bot, prefix: true, allow_nil: true
|
53
|
+
|
54
|
+
def initialize(bot = nil, update = nil)
|
55
|
+
@_update = update
|
56
|
+
@_bot = bot
|
57
|
+
|
58
|
+
update && PAYLOAD_TYPES.find do |type|
|
59
|
+
item = update[type]
|
60
|
+
next unless item
|
61
|
+
@_payload = item
|
62
|
+
@_payload_type = type
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def dispatch
|
67
|
+
@_is_command, action, args = action_for_payload
|
68
|
+
process(action, *args)
|
69
|
+
end
|
70
|
+
|
71
|
+
# Calculates action name and args for payload.
|
72
|
+
# If payload is a message with command, then returned action is an
|
73
|
+
# action for this command. Otherwise it's the same as payload type.
|
74
|
+
# Returns array `[is_command?, action, args]`.
|
75
|
+
def action_for_payload
|
76
|
+
case payload_type
|
77
|
+
when 'message'
|
78
|
+
cmd, args = self.class.command_from_text(payload['text'], bot_username)
|
79
|
+
cmd &&= self.class.action_for_command(cmd)
|
80
|
+
[true, cmd, args] if cmd
|
81
|
+
end || [false, payload_type, [payload]]
|
82
|
+
end
|
83
|
+
|
84
|
+
# Silently ignore unsupported messages.
|
85
|
+
# Params are `action, *args`.
|
86
|
+
def action_missing(*)
|
87
|
+
end
|
88
|
+
|
89
|
+
%w(chat from).each do |field|
|
90
|
+
define_method(field) { payload[field] }
|
91
|
+
end
|
92
|
+
|
93
|
+
def reply_with(type, params)
|
94
|
+
method = "send_#{type}"
|
95
|
+
params = params.merge(
|
96
|
+
chat_id: chat['id'],
|
97
|
+
reply_to_message: payload['message_id'],
|
98
|
+
)
|
99
|
+
bot.public_send(method, params)
|
100
|
+
end
|
101
|
+
|
102
|
+
ActiveSupport.run_load_hooks('telegram.bot.updates_controller', self)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
module Telegram
|
2
|
+
class Bot
|
3
|
+
# Supposed to be used in development environments only.
|
4
|
+
class UpdatesPoller
|
5
|
+
class << self
|
6
|
+
@@instances = {} # rubocop:disable ClassVars
|
7
|
+
|
8
|
+
def instances
|
9
|
+
@@instances
|
10
|
+
end
|
11
|
+
|
12
|
+
# Create, start and add poller instnace to tracked instances list.
|
13
|
+
def add(bot, controller)
|
14
|
+
new(bot, controller).tap { |x| instances[bot] = x }
|
15
|
+
end
|
16
|
+
|
17
|
+
def start(bot_id, controller = nil)
|
18
|
+
bot = bot_id.is_a?(Symbol) ? Telegram.bots[bot_id] : Bot.wrap(bot_id)
|
19
|
+
instance = controller ? new(bot, controller) : instances[bot]
|
20
|
+
raise "Poller not found for #{bot_id.inspect}" unless instance
|
21
|
+
instance.start
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
DEFAULT_TIMEOUT = 5
|
26
|
+
|
27
|
+
attr_reader :bot, :controller, :timeout, :offset, :logger, :running, :reload
|
28
|
+
|
29
|
+
def initialize(bot, controller, **options)
|
30
|
+
@logger = options.fetch(:logger) { defined?(Rails) && Rails.logger }
|
31
|
+
@bot = bot
|
32
|
+
@controller = controller
|
33
|
+
@timeout = options.fetch(:timeout) { DEFAULT_TIMEOUT }
|
34
|
+
@offset = options[:offset]
|
35
|
+
@reload = options.fetch(:reload) { defined?(Rails) && Rails.env.development? }
|
36
|
+
end
|
37
|
+
|
38
|
+
def log(&block)
|
39
|
+
logger.info(&block) if logger
|
40
|
+
end
|
41
|
+
|
42
|
+
def start
|
43
|
+
return if running
|
44
|
+
@running = true
|
45
|
+
log { 'Started bot poller.' }
|
46
|
+
while running
|
47
|
+
begin
|
48
|
+
fetch_updates do |update|
|
49
|
+
controller.dispatch(bot, update)
|
50
|
+
end
|
51
|
+
rescue Interrupt
|
52
|
+
@running = false
|
53
|
+
rescue => e
|
54
|
+
logger.error { ([e.message] + e.backtrace).join("\n") } if logger
|
55
|
+
end
|
56
|
+
end
|
57
|
+
log { 'Stop polling bot updates.' }
|
58
|
+
end
|
59
|
+
|
60
|
+
def stop
|
61
|
+
return unless running
|
62
|
+
log { 'Killing polling thread.' }
|
63
|
+
@running = false
|
64
|
+
end
|
65
|
+
|
66
|
+
def fetch_updates
|
67
|
+
response = bot.get_updates(offset: offset, timeout: timeout)
|
68
|
+
return unless response['ok'] && response['result'].any?
|
69
|
+
reload! do
|
70
|
+
response['result'].each do |update|
|
71
|
+
@offset = update['update_id'] + 1
|
72
|
+
yield update
|
73
|
+
end
|
74
|
+
end
|
75
|
+
rescue Timeout::Error # rubocop:disable HandleExceptions
|
76
|
+
end
|
77
|
+
|
78
|
+
def reload!
|
79
|
+
return yield unless reload
|
80
|
+
ActionDispatch::Reloader.prepare!
|
81
|
+
if controller.is_a?(Class) && controller.name
|
82
|
+
@controller = Object.const_get(controller.name)
|
83
|
+
end
|
84
|
+
yield.tap { ActionDispatch::Reloader.cleanup! }
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
data/lib/telegram/bot.rb
ADDED
@@ -0,0 +1,98 @@
|
|
1
|
+
require 'httpclient'
|
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'
|
7
|
+
|
8
|
+
module Telegram
|
9
|
+
extend Bottable
|
10
|
+
|
11
|
+
class Bot
|
12
|
+
class Error < StandardError; end
|
13
|
+
class NotFound < Error; end
|
14
|
+
|
15
|
+
autoload :Middleware, 'telegram/bot/middleware'
|
16
|
+
autoload :UpdatesController, 'telegram/bot/updates_controller'
|
17
|
+
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
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
require 'telegram/bot/railtie' if defined?(Rails)
|
@@ -0,0 +1,41 @@
|
|
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
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'telegram/bot/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = 'telegram-bot'
|
8
|
+
spec.version = Telegram::Bot::VERSION
|
9
|
+
spec.authors = ['Max Melentiev']
|
10
|
+
spec.email = ['melentievm@gmail.com']
|
11
|
+
|
12
|
+
spec.summary = 'Library for building Telegram Bots with Rails integration'
|
13
|
+
spec.homepage = 'https://github.com/printercu/telegram-bot'
|
14
|
+
spec.license = 'MIT'
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^spec/}) }
|
17
|
+
spec.bindir = 'exe'
|
18
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
19
|
+
spec.require_paths = ['lib']
|
20
|
+
|
21
|
+
spec.required_ruby_version = '~> 2.0'
|
22
|
+
|
23
|
+
spec.add_dependency 'activesupport', '~> 4.0'
|
24
|
+
spec.add_dependency 'actionpack', '~> 4.0'
|
25
|
+
spec.add_dependency 'httpclient', '~> 2.7'
|
26
|
+
spec.add_development_dependency 'bundler', '~> 1.11'
|
27
|
+
spec.add_development_dependency 'rake', '~> 10.0'
|
28
|
+
end
|
metadata
ADDED
@@ -0,0 +1,138 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: telegram-bot
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.3.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Max Melentiev
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-02-22 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: activesupport
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '4.0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '4.0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: actionpack
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '4.0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '4.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: httpclient
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '2.7'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '2.7'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: bundler
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '1.11'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '1.11'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rake
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '10.0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '10.0'
|
83
|
+
description:
|
84
|
+
email:
|
85
|
+
- melentievm@gmail.com
|
86
|
+
executables: []
|
87
|
+
extensions: []
|
88
|
+
extra_rdoc_files: []
|
89
|
+
files:
|
90
|
+
- ".gitignore"
|
91
|
+
- ".rspec"
|
92
|
+
- ".rubocop.yml"
|
93
|
+
- ".travis.yml"
|
94
|
+
- Gemfile
|
95
|
+
- LICENSE.txt
|
96
|
+
- README.md
|
97
|
+
- Rakefile
|
98
|
+
- bin/console
|
99
|
+
- bin/git-hooks/pre-commit
|
100
|
+
- bin/install_git_hooks
|
101
|
+
- bin/setup
|
102
|
+
- lib/tasks/telegram-bot.rake
|
103
|
+
- lib/telegram/bot.rb
|
104
|
+
- lib/telegram/bot/middleware.rb
|
105
|
+
- lib/telegram/bot/railtie.rb
|
106
|
+
- lib/telegram/bot/routes_helper.rb
|
107
|
+
- lib/telegram/bot/updates_controller.rb
|
108
|
+
- lib/telegram/bot/updates_controller/instrumentation.rb
|
109
|
+
- lib/telegram/bot/updates_controller/log_subscriber.rb
|
110
|
+
- lib/telegram/bot/updates_poller.rb
|
111
|
+
- lib/telegram/bot/version.rb
|
112
|
+
- lib/telegram/bottable.rb
|
113
|
+
- telegram-bot.gemspec
|
114
|
+
homepage: https://github.com/printercu/telegram-bot
|
115
|
+
licenses:
|
116
|
+
- MIT
|
117
|
+
metadata: {}
|
118
|
+
post_install_message:
|
119
|
+
rdoc_options: []
|
120
|
+
require_paths:
|
121
|
+
- lib
|
122
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
123
|
+
requirements:
|
124
|
+
- - "~>"
|
125
|
+
- !ruby/object:Gem::Version
|
126
|
+
version: '2.0'
|
127
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - ">="
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '0'
|
132
|
+
requirements: []
|
133
|
+
rubyforge_project:
|
134
|
+
rubygems_version: 2.4.6
|
135
|
+
signing_key:
|
136
|
+
specification_version: 4
|
137
|
+
summary: Library for building Telegram Bots with Rails integration
|
138
|
+
test_files: []
|