| @@ -0,0 +1,107 @@ | ||
| 1 | +module Agents | |
| 2 | + class PushoverAgent < Agent | |
| 3 | + cannot_be_scheduled! | |
| 4 | + cannot_create_events! | |
| 5 | + | |
| 6 | + API_URL = 'https://api.pushover.net/1/messages.json' | |
| 7 | + | |
| 8 | + description <<-MD | |
| 9 | + The PushoverAgent receives and collects events and sends them via push notification to a user/group. | |
| 10 | + | |
| 11 | + **You need a Pushover API Token:** [https://pushover.net/apps/build](https://pushover.net/apps/build) | |
| 12 | + | |
| 13 | + **You must provide** a `message` or `text` key that will contain the body of the notification. This can come from an event or be set as a default. Pushover API has a `512` Character Limit including `title`. `message` will be truncated. | |
| 14 | + | |
| 15 | + * `token`: your application's API token | |
| 16 | + * `user`: the user or group key (not e-mail address). | |
| 17 | + * `expected_receive_period_in_days`: is maximum number of days that you would expect to pass between events being received by this agent. | |
| 18 | + | |
| 19 | + Your event can provide any of the following optional parameters or you can provide defaults: | |
| 20 | + | |
| 21 | + * `device` - your user's device name to send the message directly to that device, rather than all of the user's devices | |
| 22 | + * `title` or `subject` - your notifications's title | |
| 23 | + * `url` - a supplementary URL to show with your message - `512` Character Limit | |
| 24 | + * `url_title` - a title for your supplementary URL, otherwise just the URL is shown - `100` Character Limit | |
| 25 | + * `priority` - send as `-1` to always send as a quiet notification, `0` is default, `1` to display as high-priority and bypass the user's quiet hours, or `2` for emergency priority: [Please read Pushover Docs on Emergency Priority](https://pushover.net/api#priority) | |
| 26 | + * `sound` - the name of one of the sounds supported by device clients to override the user's default sound choice. [See PushOver docs for sound options.](https://pushover.net/api#sounds) | |
| 27 | + * `retry` - Requred for emergency priority - Specifies how often (in seconds) the Pushover servers will send the same notification to the user. Minimum value: `30` | |
| 28 | + * `expire` - Requred for emergency priority - Specifies how many seconds your notification will continue to be retried for (every retry seconds). Maximum value: `86400` | |
| 29 | + | |
| 30 | + Your event can also pass along a timestamp parameter: | |
| 31 | + | |
| 32 | + * `timestamp` - a [Unix timestamp](https://en.wikipedia.org/wiki/Unix_time) of your message's date and time to display to the user, rather than the time your message is received by the Pushover API. | |
| 33 | + | |
| 34 | + MD | |
| 35 | + | |
| 36 | + def default_options | |
| 37 | +      { | |
| 38 | + 'token' => '', | |
| 39 | + 'user' => '', | |
| 40 | + 'message' => 'a default message', | |
| 41 | + 'device' => '', | |
| 42 | + 'title' => '', | |
| 43 | + 'url' => '', | |
| 44 | + 'url_title' => '', | |
| 45 | + 'priority' => 0, | |
| 46 | + 'sound' => 'pushover', | |
| 47 | + 'retry' => 0, | |
| 48 | + 'expire' => 0, | |
| 49 | + 'expected_receive_period_in_days' => '1' | |
| 50 | + } | |
| 51 | + end | |
| 52 | + | |
| 53 | + def validate_options | |
| 54 | + unless options['token'].present? && options['user'].present? && options['expected_receive_period_in_days'].present? | |
| 55 | + errors.add(:base, 'token, user, and expected_receive_period_in_days are all required.') | |
| 56 | + end | |
| 57 | + end | |
| 58 | + | |
| 59 | + def receive(incoming_events) | |
| 60 | + incoming_events.each do |event| | |
| 61 | + message = (event.payload['message'].presence || event.payload['text'].presence || options['message']).to_s | |
| 62 | + if message.present? | |
| 63 | +            post_params = { | |
| 64 | + 'token' => options['token'], | |
| 65 | + 'user' => options['user'], | |
| 66 | + 'message' => message | |
| 67 | + } | |
| 68 | + | |
| 69 | + post_params['device'] = event.payload['device'].presence || options['device'] | |
| 70 | + post_params['title'] = event.payload['title'].presence || event.payload['subject'].presence || options['title'] | |
| 71 | + | |
| 72 | + url = (event.payload['url'].presence || options['url'] || '').to_s | |
| 73 | + url = url.slice 0..512 | |
| 74 | + post_params['url'] = url | |
| 75 | + | |
| 76 | + url_title = (event.payload['url_title'].presence || options['url_title']).to_s | |
| 77 | + url_title = url_title.slice 0..100 | |
| 78 | + post_params['url_title'] = url_title | |
| 79 | + | |
| 80 | + post_params['priority'] = (event.payload['priority'].presence || options['priority']).to_i | |
| 81 | + | |
| 82 | + if event.payload.has_key? 'timestamp' | |
| 83 | + post_params['timestamp'] = (event.payload['timestamp']).to_s | |
| 84 | + end | |
| 85 | + | |
| 86 | + post_params['sound'] = (event.payload['sound'].presence || options['sound']).to_s | |
| 87 | + | |
| 88 | + post_params['retry'] = (event.payload['retry'].presence || options['retry']).to_i | |
| 89 | + | |
| 90 | + post_params['expire'] = (event.payload['expire'].presence || options['expire']).to_i | |
| 91 | + | |
| 92 | + send_notification(post_params) | |
| 93 | + end | |
| 94 | + end | |
| 95 | + end | |
| 96 | + | |
| 97 | + def working? | |
| 98 | + last_receive_at && last_receive_at > options['expected_receive_period_in_days'].to_i.days.ago && !recent_error_logs? | |
| 99 | + end | |
| 100 | + | |
| 101 | + def send_notification(post_params) | |
| 102 | + response = HTTParty.post(API_URL, :query => post_params) | |
| 103 | + puts response | |
| 104 | + end | |
| 105 | + | |
| 106 | + end | |
| 107 | +end | 
| @@ -0,0 +1,222 @@ | ||
| 1 | +require 'spec_helper' | |
| 2 | + | |
| 3 | +describe Agents::PushoverAgent do | |
| 4 | + before do | |
| 5 | + @checker = Agents::PushoverAgent.new(:name => 'Some Name', | |
| 6 | +                                       :options => { :token => 'x', | |
| 7 | + :user => 'x', | |
| 8 | + :message => 'Some Message', | |
| 9 | + :device => 'Some Device', | |
| 10 | + :title => 'Some Message Title', | |
| 11 | + :url => 'http://someurl.com', | |
| 12 | + :url_title => 'Some Url Title', | |
| 13 | + :priority => 0, | |
| 14 | + :timestamp => 'false', | |
| 15 | + :sound => 'pushover', | |
| 16 | + :retry => 0, | |
| 17 | + :expire => 0, | |
| 18 | + :expected_receive_period_in_days => '1'}) | |
| 19 | + | |
| 20 | + @checker.user = users(:bob) | |
| 21 | + @checker.save! | |
| 22 | + | |
| 23 | + @event = Event.new | |
| 24 | + @event.agent = agents(:bob_weather_agent) | |
| 25 | +    @event.payload = { :message => 'Looks like its going to rain' } | |
| 26 | + @event.save! | |
| 27 | + | |
| 28 | + @sent_notifications = [] | |
| 29 | +    stub.any_instance_of(Agents::PushoverAgent).send_notification  { |notification| @sent_notifications << notification} | |
| 30 | + end | |
| 31 | + | |
| 32 | + describe '#receive' do | |
| 33 | + it 'should make sure multiple events are being received' do | |
| 34 | + event1 = Event.new | |
| 35 | + event1.agent = agents(:bob_rain_notifier_agent) | |
| 36 | +      event1.payload = { :message => 'Some message' } | |
| 37 | + event1.save! | |
| 38 | + | |
| 39 | + event2 = Event.new | |
| 40 | + event2.agent = agents(:bob_weather_agent) | |
| 41 | +      event2.payload = { :message => 'Some other message' } | |
| 42 | + event2.save! | |
| 43 | + | |
| 44 | + @checker.receive([@event,event1,event2]) | |
| 45 | + @sent_notifications[0]['message'].should == 'Looks like its going to rain' | |
| 46 | + @sent_notifications[1]['message'].should == 'Some message' | |
| 47 | + @sent_notifications[2]['message'].should == 'Some other message' | |
| 48 | + end | |
| 49 | + | |
| 50 | + it 'should make sure event message overrides default message' do | |
| 51 | + event = Event.new | |
| 52 | + event.agent = agents(:bob_rain_notifier_agent) | |
| 53 | +      event.payload = { :message => 'Some new message'} | |
| 54 | + event.save! | |
| 55 | + | |
| 56 | + @checker.receive([event]) | |
| 57 | + @sent_notifications[0]['message'].should == 'Some new message' | |
| 58 | + end | |
| 59 | + | |
| 60 | + it 'should make sure event text overrides default message' do | |
| 61 | + event = Event.new | |
| 62 | + event.agent = agents(:bob_rain_notifier_agent) | |
| 63 | +      event.payload = { :text => 'Some new text'} | |
| 64 | + event.save! | |
| 65 | + | |
| 66 | + @checker.receive([event]) | |
| 67 | + @sent_notifications[0]['message'].should == 'Some new text' | |
| 68 | + end | |
| 69 | + | |
| 70 | + it 'should make sure event title overrides default title' do | |
| 71 | + event = Event.new | |
| 72 | + event.agent = agents(:bob_rain_notifier_agent) | |
| 73 | +      event.payload = { :message => 'Some message', :title => 'Some new title' } | |
| 74 | + event.save! | |
| 75 | + | |
| 76 | + @checker.receive([event]) | |
| 77 | + @sent_notifications[0]['title'].should == 'Some new title' | |
| 78 | + end | |
| 79 | + | |
| 80 | + it 'should make sure event url overrides default url' do | |
| 81 | + event = Event.new | |
| 82 | + event.agent = agents(:bob_rain_notifier_agent) | |
| 83 | +      event.payload = { :message => 'Some message', :url => 'Some new url' } | |
| 84 | + event.save! | |
| 85 | + | |
| 86 | + @checker.receive([event]) | |
| 87 | + @sent_notifications[0]['url'].should == 'Some new url' | |
| 88 | + end | |
| 89 | + | |
| 90 | + it 'should make sure event url_title overrides default url_title' do | |
| 91 | + event = Event.new | |
| 92 | + event.agent = agents(:bob_rain_notifier_agent) | |
| 93 | +      event.payload = { :message => 'Some message', :url_title => 'Some new url_title' } | |
| 94 | + event.save! | |
| 95 | + | |
| 96 | + @checker.receive([event]) | |
| 97 | + @sent_notifications[0]['url_title'].should == 'Some new url_title' | |
| 98 | + end | |
| 99 | + | |
| 100 | + it 'should make sure event priority overrides default priority' do | |
| 101 | + event = Event.new | |
| 102 | + event.agent = agents(:bob_rain_notifier_agent) | |
| 103 | +      event.payload = { :message => 'Some message', :priority => 1 } | |
| 104 | + event.save! | |
| 105 | + | |
| 106 | + @checker.receive([event]) | |
| 107 | + @sent_notifications[0]['priority'].should == 1 | |
| 108 | + end | |
| 109 | + | |
| 110 | + it 'should make sure event timestamp overrides default timestamp' do | |
| 111 | + event = Event.new | |
| 112 | + event.agent = agents(:bob_rain_notifier_agent) | |
| 113 | +      event.payload = { :message => 'Some message', :timestamp => 'false' } | |
| 114 | + event.save! | |
| 115 | + | |
| 116 | + @checker.receive([event]) | |
| 117 | + @sent_notifications[0]['timestamp'].should == 'false' | |
| 118 | + end | |
| 119 | + | |
| 120 | + it 'should make sure event sound overrides default sound' do | |
| 121 | + event = Event.new | |
| 122 | + event.agent = agents(:bob_rain_notifier_agent) | |
| 123 | +      event.payload = { :message => 'Some message', :sound => 'Some new sound' } | |
| 124 | + event.save! | |
| 125 | + | |
| 126 | + @checker.receive([event]) | |
| 127 | + @sent_notifications[0]['sound'].should == 'Some new sound' | |
| 128 | + end | |
| 129 | + | |
| 130 | + it 'should make sure event retry overrides default retry' do | |
| 131 | + event = Event.new | |
| 132 | + event.agent = agents(:bob_rain_notifier_agent) | |
| 133 | +      event.payload = { :message => 'Some message', :retry => 1 } | |
| 134 | + event.save! | |
| 135 | + | |
| 136 | + @checker.receive([event]) | |
| 137 | + @sent_notifications[0]['retry'].should == 1 | |
| 138 | + end | |
| 139 | + | |
| 140 | + it 'should make sure event expire overrides default expire' do | |
| 141 | + event = Event.new | |
| 142 | + event.agent = agents(:bob_rain_notifier_agent) | |
| 143 | +      event.payload = { :message => 'Some message', :expire => 60 } | |
| 144 | + event.save! | |
| 145 | + | |
| 146 | + @checker.receive([event]) | |
| 147 | + @sent_notifications[0]['expire'].should == 60 | |
| 148 | + end | |
| 149 | + end | |
| 150 | + | |
| 151 | + describe '#working?' do | |
| 152 | + it 'checks if events have been received within the expected receive period' do | |
| 153 | + # No events received | |
| 154 | + @checker.should_not be_working | |
| 155 | + Agents::PushoverAgent.async_receive @checker.id, [@event.id] | |
| 156 | + | |
| 157 | + # Just received events | |
| 158 | + @checker.reload.should be_working | |
| 159 | + two_days_from_now = 2.days.from_now | |
| 160 | +      stub(Time).now { two_days_from_now } | |
| 161 | + | |
| 162 | + # More time has passed than the expected receive period without any new events | |
| 163 | + @checker.reload.should_not be_working | |
| 164 | + end | |
| 165 | + end | |
| 166 | + | |
| 167 | + describe "validation" do | |
| 168 | + before do | |
| 169 | + @checker.should be_valid | |
| 170 | + end | |
| 171 | + | |
| 172 | + it "should validate presence of token" do | |
| 173 | + @checker.options[:token] = "" | |
| 174 | + @checker.should_not be_valid | |
| 175 | + end | |
| 176 | + | |
| 177 | + it "should validate presence of user" do | |
| 178 | + @checker.options[:user] = "" | |
| 179 | + @checker.should_not be_valid | |
| 180 | + end | |
| 181 | + | |
| 182 | + it "should validate presence of expected_receive_period_in_days" do | |
| 183 | + @checker.options[:expected_receive_period_in_days] = "" | |
| 184 | + @checker.should_not be_valid | |
| 185 | + end | |
| 186 | + | |
| 187 | + it "should make sure device is optional" do | |
| 188 | + @checker.options[:device] = "" | |
| 189 | + @checker.should be_valid | |
| 190 | + end | |
| 191 | + | |
| 192 | + it "should make sure title is optional" do | |
| 193 | + @checker.options[:title] = "" | |
| 194 | + @checker.should be_valid | |
| 195 | + end | |
| 196 | + | |
| 197 | + it "should make sure url is optional" do | |
| 198 | + @checker.options[:url] = "" | |
| 199 | + @checker.should be_valid | |
| 200 | + end | |
| 201 | + | |
| 202 | + it "should make sure url_title is optional" do | |
| 203 | + @checker.options[:url_title] = "" | |
| 204 | + @checker.should be_valid | |
| 205 | + end | |
| 206 | + | |
| 207 | + it "should make sure priority is optional" do | |
| 208 | + @checker.options[:priority] = "" | |
| 209 | + @checker.should be_valid | |
| 210 | + end | |
| 211 | + | |
| 212 | + it "should make sure timestamp is optional" do | |
| 213 | + @checker.options[:timestamp] = "" | |
| 214 | + @checker.should be_valid | |
| 215 | + end | |
| 216 | + | |
| 217 | + it "should make sure sound is optional" do | |
| 218 | + @checker.options[:sound] = "" | |
| 219 | + @checker.should be_valid | |
| 220 | + end | |
| 221 | + end | |
| 222 | +end |