@@ -25,17 +25,18 @@ end |
||
| 25 | 25 |
|
| 26 | 26 |
# Optional libraries. To conserve RAM, comment out any that you don't need, |
| 27 | 27 |
# then run `bundle` and commit the updated Gemfile and Gemfile.lock. |
| 28 |
-gem 'twilio-ruby', '~> 3.11.5' # TwilioAgent |
|
| 29 |
-gem 'ruby-growl', '~> 4.1.0' # GrowlAgent |
|
| 30 |
-gem 'net-ftp-list', '~> 3.2.8' # FtpsiteAgent |
|
| 31 |
-gem 'wunderground', '~> 1.2.0' # WeatherAgent |
|
| 32 |
-gem 'forecast_io', '~> 2.0.0' # WeatherAgent |
|
| 33 |
-gem 'rturk', '~> 2.12.1' # HumanTaskAgent |
|
| 34 |
-gem 'hipchat', '~> 1.2.0' # HipchatAgent |
|
| 35 |
-gem 'xmpp4r', '~> 0.5.6' # JabberAgent |
|
| 36 |
-gem 'mqtt' # MQTTAgent |
|
| 37 |
-gem 'slack-notifier', '~> 1.0.0' # SlackAgent |
|
| 38 |
-gem 'hypdf', '~> 1.0.7' # PDFInfoAgent |
|
| 28 |
+gem 'twilio-ruby', '~> 3.11.5' # TwilioAgent |
|
| 29 |
+gem 'ruby-growl', '~> 4.1.0' # GrowlAgent |
|
| 30 |
+gem 'net-ftp-list', '~> 3.2.8' # FtpsiteAgent |
|
| 31 |
+gem 'wunderground', '~> 1.2.0' # WeatherAgent |
|
| 32 |
+gem 'forecast_io', '~> 2.0.0' # WeatherAgent |
|
| 33 |
+gem 'rturk', '~> 2.12.1' # HumanTaskAgent |
|
| 34 |
+gem 'hipchat', '~> 1.2.0' # HipchatAgent |
|
| 35 |
+gem 'xmpp4r', '~> 0.5.6' # JabberAgent |
|
| 36 |
+gem 'mqtt' # MQTTAgent |
|
| 37 |
+gem 'slack-notifier', '~> 1.0.0' # SlackAgent |
|
| 38 |
+gem 'hypdf', '~> 1.0.7' # PDFInfoAgent |
|
| 39 |
+gem 'telegram-bot-ruby', '~> 0.4.1' # TelegramAgent |
|
| 39 | 40 |
|
| 40 | 41 |
# Weibo Agents |
| 41 | 42 |
gem 'weibo_2', github: 'cantino/weibo_2', branch: 'master' |
@@ -112,6 +112,10 @@ GEM |
||
| 112 | 112 |
multi_json (>= 1.0.0) |
| 113 | 113 |
aws-sdk-core (2.2.15) |
| 114 | 114 |
jmespath (~> 1.0) |
| 115 |
+ axiom-types (0.1.1) |
|
| 116 |
+ descendants_tracker (~> 0.0.4) |
|
| 117 |
+ ice_nine (~> 0.11.0) |
|
| 118 |
+ thread_safe (~> 0.3, >= 0.3.1) |
|
| 115 | 119 |
bcrypt (3.1.10) |
| 116 | 120 |
better_errors (1.1.0) |
| 117 | 121 |
coderay (>= 1.0.0) |
@@ -146,6 +150,8 @@ GEM |
||
| 146 | 150 |
chronic (0.10.2) |
| 147 | 151 |
cliver (0.3.2) |
| 148 | 152 |
coderay (1.1.0) |
| 153 |
+ coercible (1.0.0) |
|
| 154 |
+ descendants_tracker (~> 0.0.1) |
|
| 149 | 155 |
coffee-rails (4.1.1) |
| 150 | 156 |
coffee-script (>= 2.2.0) |
| 151 | 157 |
railties (>= 4.0.0, < 5.1.x) |
@@ -170,6 +176,8 @@ GEM |
||
| 170 | 176 |
activesupport (>= 3.0, < 5.0) |
| 171 | 177 |
delorean (2.1.0) |
| 172 | 178 |
chronic |
| 179 |
+ descendants_tracker (0.0.4) |
|
| 180 |
+ thread_safe (~> 0.3, >= 0.3.1) |
|
| 173 | 181 |
devise (3.5.4) |
| 174 | 182 |
bcrypt (~> 3.0) |
| 175 | 183 |
orm_adapter (~> 0.1) |
@@ -225,6 +233,8 @@ GEM |
||
| 225 | 233 |
dotenv (>= 0.7) |
| 226 | 234 |
thor (>= 0.13.6) |
| 227 | 235 |
formatador (0.2.5) |
| 236 |
+ gene_pool (1.4.1) |
|
| 237 |
+ thread_safe |
|
| 228 | 238 |
geokit (1.8.5) |
| 229 | 239 |
multi_json (>= 1.3.2) |
| 230 | 240 |
geokit-rails (2.0.1) |
@@ -281,6 +291,7 @@ GEM |
||
| 281 | 291 |
hypdf (1.0.7) |
| 282 | 292 |
httmultiparty (= 0.3.10) |
| 283 | 293 |
i18n (0.7.0) |
| 294 |
+ ice_nine (0.11.2) |
|
| 284 | 295 |
jmespath (1.1.3) |
| 285 | 296 |
jquery-rails (3.1.3) |
| 286 | 297 |
railties (>= 3.0, < 5.0) |
@@ -371,6 +382,11 @@ GEM |
||
| 371 | 382 |
multi_json (~> 1.3) |
| 372 | 383 |
omniauth-oauth (~> 1.0) |
| 373 | 384 |
orm_adapter (0.5.0) |
| 385 |
+ persistent_http (1.0.6) |
|
| 386 |
+ gene_pool (>= 1.3) |
|
| 387 |
+ persistent_httparty (0.1.2) |
|
| 388 |
+ httparty (~> 0.9) |
|
| 389 |
+ persistent_http (< 2) |
|
| 374 | 390 |
pg (0.18.3) |
| 375 | 391 |
poltergeist (1.8.1) |
| 376 | 392 |
capybara (~> 2.1) |
@@ -515,6 +531,10 @@ GEM |
||
| 515 | 531 |
net-ssh (>= 2.8.0) |
| 516 | 532 |
string-scrub (0.0.5) |
| 517 | 533 |
systemu (2.6.4) |
| 534 |
+ telegram-bot-ruby (0.4.1) |
|
| 535 |
+ httmultiparty |
|
| 536 |
+ persistent_httparty |
|
| 537 |
+ virtus |
|
| 518 | 538 |
term-ansicolor (1.3.0) |
| 519 | 539 |
tins (~> 1.0) |
| 520 | 540 |
therubyracer (0.12.2) |
@@ -559,6 +579,11 @@ GEM |
||
| 559 | 579 |
macaddr (~> 1.0) |
| 560 | 580 |
uuidtools (2.1.5) |
| 561 | 581 |
vcr (2.9.2) |
| 582 |
+ virtus (1.0.5) |
|
| 583 |
+ axiom-types (~> 0.1) |
|
| 584 |
+ coercible (~> 1.0) |
|
| 585 |
+ descendants_tracker (~> 0.0, >= 0.0.3) |
|
| 586 |
+ equalizer (~> 0.0, >= 0.0.9) |
|
| 562 | 587 |
warden (1.2.4) |
| 563 | 588 |
rack (>= 1.0) |
| 564 | 589 |
webmock (1.17.4) |
@@ -664,6 +689,7 @@ DEPENDENCIES |
||
| 664 | 689 |
spring (~> 1.6.3) |
| 665 | 690 |
spring-commands-rspec (~> 1.0.4) |
| 666 | 691 |
string-scrub |
| 692 |
+ telegram-bot-ruby (~> 0.4.1) |
|
| 667 | 693 |
therubyracer (~> 0.12.2) |
| 668 | 694 |
tumblr_client! |
| 669 | 695 |
twilio-ruby (~> 3.11.5) |
@@ -0,0 +1,92 @@ |
||
| 1 |
+require 'telegram/bot' |
|
| 2 |
+require 'open-uri' |
|
| 3 |
+require 'tempfile' |
|
| 4 |
+ |
|
| 5 |
+module Agents |
|
| 6 |
+ class TelegramAgent < Agent |
|
| 7 |
+ cannot_be_scheduled! |
|
| 8 |
+ cannot_create_events! |
|
| 9 |
+ no_bulk_receive! |
|
| 10 |
+ |
|
| 11 |
+ gem_dependency_check { defined?(Telegram) }
|
|
| 12 |
+ |
|
| 13 |
+ description <<-MD |
|
| 14 |
+ #{'# Include `telegram-bot-ruby` in your Gemfile to use this Agent!' if dependencies_missing?}
|
|
| 15 |
+ |
|
| 16 |
+ The Telegram Agent receives and collects events and sends them via [Telegram](https://telegram.org/). |
|
| 17 |
+ |
|
| 18 |
+ It is assumed that events have either a `text`, `photo`, `audio`, `document` or `video` key. You can use the EventFormattingAgent if your event does not provide these keys. |
|
| 19 |
+ |
|
| 20 |
+ The value of `text` key is sent as a plain text message. |
|
| 21 |
+ The value of `photo`, `audio`, `document` and `video` keys should be an url which contents are sent to you according to the type. |
|
| 22 |
+ |
|
| 23 |
+ **Setup** |
|
| 24 |
+ |
|
| 25 |
+ 1. obtain an `auth_token` by [creating a new bot](https://telegram.me/botfather). |
|
| 26 |
+ 2. [send a private message to your bot](https://telegram.me/YourHuginnBot) |
|
| 27 |
+ 3. obtain your private `chat_id` [from the recently started conversation](https://api.telegram.org/bot<auth_token>/getUpdates) |
|
| 28 |
+ MD |
|
| 29 |
+ |
|
| 30 |
+ def default_options |
|
| 31 |
+ {
|
|
| 32 |
+ auth_token: 'xxxxxxxxx:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx', |
|
| 33 |
+ chat_id: 'xxxxxxxx' |
|
| 34 |
+ } |
|
| 35 |
+ end |
|
| 36 |
+ |
|
| 37 |
+ def validate_options |
|
| 38 |
+ errors.add(:base, 'auth_token is required') unless options['auth_token'].present? |
|
| 39 |
+ errors.add(:base, 'chat_id is required') unless options['chat_id'].present? |
|
| 40 |
+ end |
|
| 41 |
+ |
|
| 42 |
+ def working? |
|
| 43 |
+ received_event_without_error? && !recent_error_logs? |
|
| 44 |
+ end |
|
| 45 |
+ |
|
| 46 |
+ def receive(incoming_events) |
|
| 47 |
+ incoming_events.each do |event| |
|
| 48 |
+ receive_event event |
|
| 49 |
+ end |
|
| 50 |
+ end |
|
| 51 |
+ |
|
| 52 |
+ private |
|
| 53 |
+ |
|
| 54 |
+ TELEGRAM_FIELDS = {
|
|
| 55 |
+ text: :send_message, |
|
| 56 |
+ photo: :send_photo, |
|
| 57 |
+ audio: :send_audio, |
|
| 58 |
+ document: :send_document, |
|
| 59 |
+ video: :send_video |
|
| 60 |
+ }.freeze |
|
| 61 |
+ |
|
| 62 |
+ def receive_event(event) |
|
| 63 |
+ TELEGRAM_FIELDS.each do |field, method| |
|
| 64 |
+ payload = load_field event, field |
|
| 65 |
+ next unless payload |
|
| 66 |
+ send_telegram_message method, field => payload |
|
| 67 |
+ end |
|
| 68 |
+ end |
|
| 69 |
+ |
|
| 70 |
+ def send_telegram_message(method, params) |
|
| 71 |
+ params[:chat_id] = interpolated['chat_id'] |
|
| 72 |
+ Telegram::Bot::Client.run interpolated['auth_token'] do |bot| |
|
| 73 |
+ bot.api.send method, params |
|
| 74 |
+ end |
|
| 75 |
+ end |
|
| 76 |
+ |
|
| 77 |
+ def load_field(event, field) |
|
| 78 |
+ payload = event.payload[field] |
|
| 79 |
+ return false unless payload.present? |
|
| 80 |
+ return payload if field == :text |
|
| 81 |
+ load_file payload |
|
| 82 |
+ end |
|
| 83 |
+ |
|
| 84 |
+ def load_file(url) |
|
| 85 |
+ file = Tempfile.new [File.basename(url), File.extname(url)] |
|
| 86 |
+ file.binmode |
|
| 87 |
+ file.write open(url).read |
|
| 88 |
+ file.rewind |
|
| 89 |
+ file |
|
| 90 |
+ end |
|
| 91 |
+ end |
|
| 92 |
+end |
@@ -0,0 +1,102 @@ |
||
| 1 |
+require 'rails_helper' |
|
| 2 |
+ |
|
| 3 |
+describe Agents::TelegramAgent do |
|
| 4 |
+ before do |
|
| 5 |
+ default_options = {
|
|
| 6 |
+ auth_token: 'xxxxxxxxx:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx', |
|
| 7 |
+ chat_id: 'xxxxxxxx' |
|
| 8 |
+ } |
|
| 9 |
+ @checker = Agents::TelegramAgent.new name: 'Telegram Tester', options: default_options |
|
| 10 |
+ @checker.user = users(:bob) |
|
| 11 |
+ @checker.save! |
|
| 12 |
+ |
|
| 13 |
+ @sent_messages = [] |
|
| 14 |
+ stub_methods |
|
| 15 |
+ end |
|
| 16 |
+ |
|
| 17 |
+ def stub_methods |
|
| 18 |
+ stub.any_instance_of(Agents::TelegramAgent).send_telegram_message do |method, params| |
|
| 19 |
+ @sent_messages << { method => params }
|
|
| 20 |
+ end |
|
| 21 |
+ |
|
| 22 |
+ stub.any_instance_of(Agents::TelegramAgent).load_file do |_url| |
|
| 23 |
+ :stubbed_file |
|
| 24 |
+ end |
|
| 25 |
+ end |
|
| 26 |
+ |
|
| 27 |
+ def event_with_payload(payload) |
|
| 28 |
+ event = Event.new |
|
| 29 |
+ event.agent = agents(:bob_weather_agent) |
|
| 30 |
+ event.payload = payload |
|
| 31 |
+ event.save! |
|
| 32 |
+ event |
|
| 33 |
+ end |
|
| 34 |
+ |
|
| 35 |
+ describe 'validation' do |
|
| 36 |
+ before do |
|
| 37 |
+ expect(@checker).to be_valid |
|
| 38 |
+ end |
|
| 39 |
+ |
|
| 40 |
+ it 'should validate presence of of auth_token' do |
|
| 41 |
+ @checker.options[:auth_token] = '' |
|
| 42 |
+ expect(@checker).not_to be_valid |
|
| 43 |
+ end |
|
| 44 |
+ |
|
| 45 |
+ it 'should validate presence of of chat_id' do |
|
| 46 |
+ @checker.options[:chat_id] = '' |
|
| 47 |
+ expect(@checker).not_to be_valid |
|
| 48 |
+ end |
|
| 49 |
+ end |
|
| 50 |
+ |
|
| 51 |
+ describe '#receive' do |
|
| 52 |
+ it 'processes multiple events properly' do |
|
| 53 |
+ event_0 = event_with_payload text: 'Looks like its going to rain' |
|
| 54 |
+ event_1 = event_with_payload text: 'Another text message' |
|
| 55 |
+ @checker.receive [event_0, event_1] |
|
| 56 |
+ |
|
| 57 |
+ expect(@sent_messages).to eq([ |
|
| 58 |
+ { send_message: { text: 'Looks like its going to rain' } },
|
|
| 59 |
+ { send_message: { text: 'Another text message' } }
|
|
| 60 |
+ ]) |
|
| 61 |
+ end |
|
| 62 |
+ |
|
| 63 |
+ it 'accepts photo key and uses :send_photo to send the file' do |
|
| 64 |
+ event = event_with_payload photo: 'https://example.com/image.png' |
|
| 65 |
+ @checker.receive [event] |
|
| 66 |
+ |
|
| 67 |
+ expect(@sent_messages).to eq([{ send_photo: { photo: :stubbed_file } }])
|
|
| 68 |
+ end |
|
| 69 |
+ |
|
| 70 |
+ it 'accepts audio key and uses :send_audio to send the file' do |
|
| 71 |
+ event = event_with_payload audio: 'https://example.com/sound.mp3' |
|
| 72 |
+ @checker.receive [event] |
|
| 73 |
+ |
|
| 74 |
+ expect(@sent_messages).to eq([{ send_audio: { audio: :stubbed_file } }])
|
|
| 75 |
+ end |
|
| 76 |
+ |
|
| 77 |
+ it 'accepts document key and uses :send_document to send the file' do |
|
| 78 |
+ event = event_with_payload document: 'https://example.com/document.pdf' |
|
| 79 |
+ @checker.receive [event] |
|
| 80 |
+ |
|
| 81 |
+ expect(@sent_messages).to eq([{ send_document: { document: :stubbed_file } }])
|
|
| 82 |
+ end |
|
| 83 |
+ |
|
| 84 |
+ it 'accepts video key and uses :send_video to send the file' do |
|
| 85 |
+ event = event_with_payload video: 'https://example.com/video.avi' |
|
| 86 |
+ @checker.receive [event] |
|
| 87 |
+ |
|
| 88 |
+ expect(@sent_messages).to eq([{ send_video: { video: :stubbed_file } }])
|
|
| 89 |
+ end |
|
| 90 |
+ end |
|
| 91 |
+ |
|
| 92 |
+ describe '#working?' do |
|
| 93 |
+ it 'is not working without having received an event' do |
|
| 94 |
+ expect(@checker).not_to be_working |
|
| 95 |
+ end |
|
| 96 |
+ |
|
| 97 |
+ it 'is working after receiving an event without error' do |
|
| 98 |
+ @checker.last_receive_at = Time.now |
|
| 99 |
+ expect(@checker).to be_working |
|
| 100 |
+ end |
|
| 101 |
+ end |
|
| 102 |
+end |