PushbulletAgent validate and autocomplete options

It will also register a new device if the device_id option is not set

Dominik Sander 9 years ago
parent
commit
c7b4c4260f
2 changed files with 100 additions and 24 deletions
  1. 53 20
      app/models/agents/pushbullet_agent.rb
  2. 47 4
      spec/models/agents/pushbullet_agent_spec.rb

+ 53 - 20
app/models/agents/pushbullet_agent.rb

@@ -5,29 +5,22 @@ module Agents
5 5
     cannot_be_scheduled!
6 6
     cannot_create_events!
7 7
 
8
-    API_URL = 'https://api.pushbullet.com/v2/pushes'
8
+    API_BASE = 'https://api.pushbullet.com/v2/'
9 9
     TYPE_TO_ATTRIBUTES = {
10 10
             'note'    => [:title, :body],
11 11
             'link'    => [:title, :body, :url],
12 12
             'address' => [:name, :address]
13 13
     }
14
+    class Unauthorized < StandardError; end
14 15
 
15 16
     description <<-MD
16 17
       The Pushbullet agent sends pushes to a pushbullet device
17 18
 
18
-      To authenticate you need to set the `api_key`, you can find yours at your account page:
19
+      To authenticate you need to either the `api_key` or create a `pushbullet_api_key` credential, you can find yours at your account page:
19 20
 
20 21
       `https://www.pushbullet.com/account`
21 22
 
22
-      Currently you need to get a the device identification manually:
23
-
24
-      `curl -u <your api key here>: https://api.pushbullet.com/v2/devices`
25
-
26
-      To register a new device run the following command:
27
-
28
-      `curl -u <your api key here>: -X POST https://api.pushbullet.com/v2/devices -d nickname=huginn -d type=stream`
29
-
30
-      Put one of the retured `iden` strings into the `device_id` field.
23
+      If you do not select an existing device, Huginn will create a new one with the name 'Huginn'.
31 24
 
32 25
       You have to provide a message `type` which has to be `note`, `link`, or `address`. The message types `checklist`, and `file` are not supported at the moment.
33 26
 
@@ -50,8 +43,8 @@ module Agents
50 43
       }
51 44
     end
52 45
 
53
-    form_configurable :api_key
54
-    form_configurable :device_id
46
+    form_configurable :api_key, roles: :validatable
47
+    form_configurable :device_id, roles: :completable
55 48
     form_configurable :type, type: :array, values: ['note', 'link', 'address']
56 49
     form_configurable :title
57 50
     form_configurable :body, type: :text
@@ -61,31 +54,71 @@ module Agents
61 54
 
62 55
     def validate_options
63 56
       errors.add(:base, "you need to specify a pushbullet api_key") if options['api_key'].blank?
64
-      errors.add(:base, "you need to specify a device_id") if options['device_id'].blank?
57
+      create_device if options['device_id'].blank?
65 58
       errors.add(:base, "you need to specify a valid message type") if options['type'].blank? or not ['note', 'link', 'address'].include?(options['type'])
66 59
       TYPE_TO_ATTRIBUTES[options['type']].each do |attr|
67 60
         errors.add(:base, "you need to specify '#{attr.to_s}' for the type '#{options['type']}'") if options[attr].blank?
68 61
       end
69 62
     end
70 63
 
64
+    def validate_api_key
65
+      devices
66
+      true
67
+    rescue Unauthorized
68
+      false
69
+    end
70
+
71
+    def complete_device_id
72
+      devices.map { |d| {text: d['nickname'], id: d['iden']} }
73
+    end
74
+
71 75
     def working?
72 76
       received_event_without_error?
73 77
     end
74 78
 
75 79
     def receive(incoming_events)
76 80
       incoming_events.each do |event|
77
-        response = HTTParty.post API_URL, query_options(event)
78
-        error(response.body) if response.body.include? 'error'
81
+        safely do
82
+          response = request(:post, 'pushes', query_options(event))
83
+        end
79 84
       end
80 85
     end
81 86
 
82 87
     private
88
+    def safely
89
+      yield
90
+    rescue Unauthorized => e
91
+      error(e.message)
92
+    end
93
+
94
+    def request(http_method, method, options)
95
+      response = JSON.parse(HTTParty.send(http_method, API_BASE + method, options).body)
96
+      raise Unauthorized, response['error']['message'] if response['error'].present?
97
+      response
98
+    end
99
+
100
+    def devices
101
+      response = request(:get, 'devices', basic_auth)
102
+      response['devices'].select { |d| d['pushable'] == true }
103
+    rescue Unauthorized
104
+      []
105
+    end
106
+
107
+    def create_device
108
+      safely do
109
+        response = request(:post, 'devices', basic_auth.merge(body: {nickname: 'Huginn', type: 'stream'}))
110
+        self.options[:device_id] = response['iden']
111
+      end
112
+    end
113
+
114
+
115
+    def basic_auth
116
+      {basic_auth: {username: interpolated[:api_key].presence || credential('pushbullet_api_key'), password: ''}}
117
+    end
118
+
83 119
     def query_options(event)
84 120
       mo = interpolated(event)
85
-      {
86
-        basic_auth: {username: mo[:api_key], password: ''},
87
-        body: {device_iden: mo[:device_id], type: mo[:type]}.merge(payload(mo))
88
-      }
121
+      basic_auth.merge(body: {device_iden: mo[:device_id], type: mo[:type]}.merge(payload(mo)))
89 122
     end
90 123
 
91 124
     def payload(mo)

+ 47 - 4
spec/models/agents/pushbullet_agent_spec.rb

@@ -33,9 +33,10 @@ describe Agents::PushbulletAgent do
33 33
       expect(@checker).not_to be_valid
34 34
     end
35 35
 
36
-    it "should require the device_id" do
36
+    it "should try do create a device_id" do
37 37
       @checker.options['device_id'] = nil
38
-      expect(@checker).not_to be_valid
38
+      mock(@checker).create_device
39
+      expect(@checker).to be_valid
39 40
     end
40 41
 
41 42
     it "should require fields based on the type" do
@@ -78,11 +79,30 @@ describe Agents::PushbulletAgent do
78 79
     end
79 80
   end
80 81
 
82
+  describe '#validate_api_key' do
83
+    it "should return true when working" do
84
+      mock(@checker).devices
85
+      expect(@checker.validate_api_key).to be_truthy
86
+    end
87
+
88
+    it "should return true when working" do
89
+      mock(@checker).devices { raise Agents::PushbulletAgent::Unauthorized }
90
+      expect(@checker.validate_api_key).to be_falsy
91
+    end
92
+  end
93
+
94
+  describe '#complete_device_id' do
95
+    it "should return an array" do
96
+      mock(@checker).devices { [{'iden' => '12345', 'nickname' => 'huginn'}] }
97
+      expect(@checker.complete_device_id).to eq([{:text=>"huginn", :id=>"12345"}])
98
+    end
99
+  end
100
+
81 101
   describe "#receive" do
82 102
     it "send a note" do
83 103
       stub_request(:post, "https://token:@api.pushbullet.com/v2/pushes").
84 104
         with(:body => "device_iden=124&type=note&title=hello%20from%20huginn&body=One%20two%20test").
85
-        to_return(:status => 200, :body => "ok", :headers => {})
105
+        to_return(:status => 200, :body => "{}", :headers => {})
86 106
       dont_allow(@checker).error
87 107
       @checker.receive([@event])
88 108
     end
@@ -90,7 +110,7 @@ describe Agents::PushbulletAgent do
90 110
     it "should log resquests which return an error" do
91 111
       stub_request(:post, "https://token:@api.pushbullet.com/v2/pushes").
92 112
         with(:body => "device_iden=124&type=note&title=hello%20from%20huginn&body=One%20two%20test").
93
-        to_return(:status => 200, :body => "error", :headers => {})
113
+        to_return(:status => 200, :body => '{"error": {"message": "error"}}', :headers => {})
94 114
       mock(@checker).error("error")
95 115
       @checker.receive([@event])
96 116
     end
@@ -103,4 +123,27 @@ describe Agents::PushbulletAgent do
103 123
       expect(@checker).to be_working
104 124
     end
105 125
   end
126
+
127
+  describe '#devices' do
128
+    it "should return an array of devices" do
129
+      stub_request(:get, "https://token:@api.pushbullet.com/v2/devices").
130
+         to_return(:status => 200, :body => '{"devices": [{"pushable": false}, {"nickname": "test", "iden": "iden", "pushable": true}]}', :headers => {})
131
+      expect(@checker.send(:devices)).to eq([{"nickname"=>"test", "iden"=>"iden", "pushable"=>true}])
132
+    end
133
+
134
+    it "should return an empty array on error" do
135
+      stub(@checker).request { raise Agents::PushbulletAgent::Unauthorized }
136
+      expect(@checker.send(:devices)).to eq([])
137
+    end
138
+  end
139
+
140
+  describe '#create_device' do
141
+    it "should create a new device and assign it to the options" do
142
+      stub_request(:post, "https://token:@api.pushbullet.com/v2/devices").
143
+         with(:body => "nickname=Huginn&type=stream").
144
+         to_return(:status => 200, :body => '{"iden": "udm0Tdjz5A7bL4NM"}', :headers => {})
145
+      @checker.send(:create_device)
146
+      expect(@checker.options[:device_id]).to eq('udm0Tdjz5A7bL4NM')
147
+    end
148
+  end
106 149
 end