Merge pull request #1381 from tomknig/telegram_agent

add telegram agent

Andrew Cantino 9 years ago
parent
commit
85d901b403
4 changed files with 209 additions and 6 deletions
  1. 2 1
      Gemfile
  2. 9 5
      Gemfile.lock
  3. 96 0
      app/models/agents/telegram_agent.rb
  4. 102 0
      spec/models/agents/telegram_agent_spec.rb

+ 2 - 1
Gemfile

@@ -35,7 +35,7 @@ gem 'hipchat', '~> 1.2.0'         # HipchatAgent
35 35
 gem 'xmpp4r',  '~> 0.5.6'         # JabberAgent
36 36
 gem 'mqtt'                        # MQTTAgent
37 37
 gem 'slack-notifier', '~> 1.0.0'  # SlackAgent
38
-gem 'hypdf', '~> 1.0.7'           # PDFInfoAgent
38
+gem 'hypdf', '~> 1.0.10'          # PDFInfoAgent
39 39
 
40 40
 # Weibo Agents
41 41
 gem 'weibo_2', github: 'cantino/weibo_2', branch: 'master'
@@ -100,6 +100,7 @@ gem 'foreman', '~> 0.63.0'
100 100
 gem 'geokit', '~> 1.8.4'
101 101
 gem 'geokit-rails', '~> 2.0.1'
102 102
 gem 'httparty', '~> 0.13'
103
+gem 'httmultiparty', '~> 0.3.16'
103 104
 gem 'jquery-rails', '~> 3.1.3'
104 105
 gem 'json', '~> 1.8.1'
105 106
 gem 'jsonpath', '~> 0.5.6'

+ 9 - 5
Gemfile.lock

@@ -267,19 +267,21 @@ GEM
267 267
     hipchat (1.2.0)
268 268
       httparty
269 269
     hpricot (0.8.6)
270
-    httmultiparty (0.3.10)
270
+    httmultiparty (0.3.16)
271 271
       httparty (>= 0.7.3)
272
+      mimemagic
272 273
       multipart-post
273 274
     http (0.6.4)
274 275
       http_parser.rb (~> 0.6.0)
275 276
     http-cookie (1.0.2)
276 277
       domain_name (~> 0.5)
277 278
     http_parser.rb (0.6.0)
278
-    httparty (0.13.1)
279
+    httparty (0.13.7)
279 280
       json (~> 1.8)
280 281
       multi_xml (>= 0.5.2)
281
-    hypdf (1.0.7)
282
-      httmultiparty (= 0.3.10)
282
+    hypdf (1.0.10)
283
+      httmultiparty (~> 0.3)
284
+      httparty (~> 0.13)
283 285
     i18n (0.7.0)
284 286
     jmespath (1.1.3)
285 287
     jquery-rails (3.1.3)
@@ -318,6 +320,7 @@ GEM
318 320
       thread_safe (~> 0.3, >= 0.3.1)
319 321
     method_source (0.8.2)
320 322
     mime-types (2.99.1)
323
+    mimemagic (0.3.1)
321 324
     mini_magick (4.2.3)
322 325
     mini_portile2 (2.0.0)
323 326
     minitest (5.8.4)
@@ -616,8 +619,9 @@ DEPENDENCIES
616 619
   guard-rspec (~> 4.6.4)
617 620
   haversine
618 621
   hipchat (~> 1.2.0)
622
+  httmultiparty (~> 0.3.16)
619 623
   httparty (~> 0.13)
620
-  hypdf (~> 1.0.7)
624
+  hypdf (~> 1.0.10)
621 625
   jquery-rails (~> 3.1.3)
622 626
   json (~> 1.8.1)
623 627
   jsonpath (~> 0.5.6)

+ 96 - 0
app/models/agents/telegram_agent.rb

@@ -0,0 +1,96 @@
1
+require 'httmultiparty'
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
+    description <<-MD
12
+      The Telegram Agent receives and collects events and sends them via [Telegram](https://telegram.org/).
13
+
14
+      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.
15
+
16
+      The value of `text` key is sent as a plain text message.
17
+      The value of `photo`, `audio`, `document` and `video` keys should be a url whose contents will be sent to you.
18
+
19
+      **Setup**
20
+
21
+      1. Obtain an `auth_token` by [creating a new bot](https://telegram.me/botfather).
22
+      2. Send a private message to your bot by visiting https://telegram.me/YourHuginnBot
23
+      3. Obtain your private `chat_id` from the recently started conversation by visiting https://api.telegram.org/bot<auth_token>/getUpdates
24
+    MD
25
+
26
+    def default_options
27
+      {
28
+        auth_token: 'xxxxxxxxx:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
29
+        chat_id: 'xxxxxxxx'
30
+      }
31
+    end
32
+
33
+    def validate_options
34
+      errors.add(:base, 'auth_token is required') unless options['auth_token'].present?
35
+      errors.add(:base, 'chat_id is required') unless options['chat_id'].present?
36
+    end
37
+
38
+    def working?
39
+      received_event_without_error? && !recent_error_logs?
40
+    end
41
+
42
+    def receive(incoming_events)
43
+      incoming_events.each do |event|
44
+        receive_event event
45
+      end
46
+    end
47
+
48
+    private
49
+
50
+    TELEGRAM_ACTIONS = {
51
+      text:     :sendMessage,
52
+      photo:    :sendPhoto,
53
+      audio:    :sendAudio,
54
+      document: :sendDocument,
55
+      video:    :sendVideo
56
+    }.freeze
57
+
58
+    def telegram_bot_uri(method)
59
+      "https://api.telegram.org/bot#{interpolated['auth_token']}/#{method}"
60
+    end
61
+
62
+    def receive_event(event)
63
+      TELEGRAM_ACTIONS.each do |field, method|
64
+        payload = load_field event, field
65
+        next unless payload
66
+        send_telegram_message method, field => payload
67
+        unlink_file payload if payload.is_a? Tempfile
68
+      end
69
+    end
70
+
71
+    def send_telegram_message(method, params)
72
+      params[:chat_id] = interpolated['chat_id']
73
+      HTTMultiParty.post telegram_bot_uri(method), query: params
74
+    end
75
+
76
+    def load_field(event, field)
77
+      payload = event.payload[field]
78
+      return false unless payload.present?
79
+      return payload if field == :text
80
+      load_file payload
81
+    end
82
+
83
+    def load_file(url)
84
+      file = Tempfile.new [File.basename(url), File.extname(url)]
85
+      file.binmode
86
+      file.write open(url).read
87
+      file.rewind
88
+      file
89
+    end
90
+
91
+    def unlink_file(file)
92
+      file.close
93
+      file.unlink
94
+    end
95
+  end
96
+end

+ 102 - 0
spec/models/agents/telegram_agent_spec.rb

@@ -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
+        { sendMessage: { text: 'Looks like its going to rain' } },
59
+        { sendMessage: { 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([{ sendPhoto: { 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([{ sendAudio: { 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([{ sendDocument: { 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([{ sendVideo: { 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