Package pushnotify :: Module pushover
[hide private]
[frames] | no frames]

Source Code for Module pushnotify.pushover

  1  #!/usr/bin/env python 
  2  # vim: set fileencoding=utf-8 
  3   
  4  """Module for sending push notificiations to Android and iOS devices 
  5  that have Pushover installed. See https://pushover.net/ for more 
  6  information. 
  7   
  8  copyright: Copyright (c) Jeffrey Goettsch and other contributors. 
  9  license: BSD, see LICENSE for details. 
 10   
 11  """ 
 12   
 13   
 14  import json 
 15  import time 
 16   
 17  from pushnotify import abstract 
 18  from pushnotify import exceptions 
 19   
 20   
 21  PUBLIC_API_URL = u'https://api.pushover.net/1' 
 22  VERIFY_URL = u'/'.join([PUBLIC_API_URL, u'users/validate.json']) 
 23  NOTIFY_URL = u'/'.join([PUBLIC_API_URL, u'messages.json']) 
 24   
 25  DESC_LIMIT = 512 
 26   
 27   
28 -class Client(abstract.AbstractClient):
29 """Client for sending push notifications to Android and iOS devices 30 with the Pushover application installed. 31 32 Member Vars: 33 developerkey: A string containing a valid token for the Pushover 34 application. 35 application: A string containing the name of the application on 36 behalf of whom the Pushover client will be sending messages. 37 Not used by this client. 38 apikeys: A dictionary where the keys are strings containing 39 valid user identifier, and the values are lists of strings, 40 each containing a valid device identifier. 41 42 """ 43
44 - def __init__(self, developerkey, application=''):
45 """Initialize the Pushover client. 46 47 Args: 48 developerkey: A string containing a valid token for the 49 Pushover application. 50 application: A string containing the name of the application 51 on behalf of whom the Pushover client will be sending 52 messages. Not used by this client. (default: '') 53 54 """ 55 56 super(self.__class__, self).__init__(developerkey, application) 57 58 self._type = 'pushover' 59 self._urls = {'notify': NOTIFY_URL, 'verify': VERIFY_URL}
60
61 - def _parse_response_stream(self, stream, verify=False):
62 63 response = stream.read() 64 self.logger.info('received response: {0}'.format(response)) 65 66 response = json.loads(response) 67 68 self._last['code'] = stream.code 69 if 'device' in response.keys(): 70 self._last['device'] = response['device'] 71 else: 72 self._last['device'] = None 73 if 'errors' in response.keys(): 74 self._last['errors'] = response['errors'] 75 else: 76 self._last['errors'] = None 77 if 'status' in response.keys(): 78 self._last['status'] = response['status'] 79 else: 80 self._last['status'] = None 81 if 'token' in response.keys(): 82 self._last['token'] = response['token'] 83 else: 84 self._last['token'] = None 85 if 'user' in response.keys(): 86 self._last['user'] = response['user'] 87 else: 88 self._last['user'] = None 89 90 return self._last['status']
91
92 - def _raise_exception(self):
93 94 msg = '' 95 if self._last['errors']: 96 messages = [] 97 for key, value in self._last['errors'].items(): 98 messages.append('{0} {1}'.format(key, value[0])) 99 msg = '; '.join(messages) 100 101 if self._last['device'] and 'invalid' in self._last['device']: 102 raise exceptions.ApiKeyError('device invalid', self._last['code']) 103 104 elif self._last['token'] and 'invalid' in self._last['token']: 105 raise exceptions.ApiKeyError('token invalid', self._last['code']) 106 107 elif self._last['user'] and 'invalid' in self._last['user']: 108 raise exceptions.ApiKeyError('user invalid', self._last['code']) 109 110 elif self._last['code'] == 429: 111 # TODO: what is actually returned when the rate limit is hit? 112 113 msg = 'too many messages sent this month' if not msg else msg 114 raise exceptions.RateLimitExceeded(msg, self._last['code']) 115 116 elif self._last['code'] >= 500 and self._last['code'] <= 599: 117 raise exceptions.ServerError(msg, self._last['code']) 118 119 elif self._last['errors']: 120 raise exceptions.FormatError(msg, self._last['code']) 121 122 else: 123 raise exceptions.UnrecognizedResponseError(msg, self._last['code'])
124
125 - def notify(self, description, event, split=True, kwargs=None):
126 """Send a notification to each user/device combintation in 127 self.apikeys. 128 129 As of 2012-09-18, this is not returning a 4xx status code as 130 per the Pushover API docs, but instead chopping the delivered 131 messages off at 512 characters. 132 133 Args: 134 description: A string of up to DESC_LIMIT characters 135 containing the notification text. 136 event: A string of up to 100 characters containing a 137 subject or brief description of the event. 138 split: A boolean indicating whether to split long 139 descriptions among multiple notifications (True) or to 140 possibly raise an exception (False). (default True) 141 kwargs: A dictionary with any of the following strings as 142 keys: 143 priority: The integer 1, which will make the 144 notification display in red and override any set 145 quiet hours. 146 url: A string of up to 500 characters containing a URL 147 to attach to the notification. 148 url_title: A string of up to 50 characters containing a 149 title to give the attached URL. 150 (default: None) 151 152 Raises: 153 pushnotify.exceptions.ApiKeyError 154 pushnotify.exceptions.FormatError 155 pushnotify.exceptions.RateLimitExceeded 156 pushnotify.exceptions.ServerError 157 pushnotify.exceptions.UnrecognizedResponseError 158 159 """ 160 161 def send_notify(desc_list, event, kwargs, apikey, device_key=''): 162 all_successful = True 163 164 for description in desc_list: 165 data = {'token': self.developerkey, 166 'user': apikey, 167 'title': event, 168 'message': description, 169 'timestamp': int(time.time())} 170 171 if device_key: 172 data['device'] = device_key 173 174 if kwargs: 175 data.update(kwargs) 176 177 response_stream = self._post(self._urls['notify'], data) 178 this_successful = self._parse_response_stream(response_stream) 179 180 all_successful = all_successful and this_successful 181 182 return all_successful
183 184 if not self.apikeys: 185 self.logger.warn('notify called with no users set') 186 return 187 188 desc_list = [] 189 if split: 190 while description: 191 desc_list.append(description[0:DESC_LIMIT]) 192 description = description[DESC_LIMIT:] 193 else: 194 desc_list = [description] 195 196 # Here we match the behavior of Notify My Android and Prowl: 197 # raise a single exception if and only if every notification 198 # fails 199 200 all_ok = True 201 202 for apikey, device_keys in self.apikeys.items(): 203 if not device_keys: 204 this_ok = send_notify(desc_list, event, kwargs, 205 apikey) 206 else: 207 for device_key in device_keys: 208 this_ok = send_notify( 209 desc_list, event, kwargs, apikey, device_key) 210 211 all_ok = all_ok and this_ok 212 213 if not all_ok: 214 self._raise_exception()
215
216 - def verify_user(self, apikey):
217 """Verify a user identifier. 218 219 Args: 220 apikey: A string containing a user identifer. 221 222 Returns: 223 A boolean containing True if apikey is valid, and 224 False if it is not. 225 226 """ 227 228 data = {'token': self.developerkey, 'user': apikey} 229 230 response_stream = self._post(self._urls['verify'], data) 231 232 self._parse_response_stream(response_stream, True) 233 234 return self._last['status']
235
236 - def verify_device(self, apikey, device_key):
237 """Verify a device identifier for the user given by apikey. 238 239 Args: 240 apikey: A string containing a user identifer. 241 device_key: A string containing a device identifier. 242 243 Raises: 244 pushnotify.exceptions.ApiKeyError 245 246 Returns: 247 A boolean containing True if device_key is valid for 248 apikey, and False if it is not. 249 250 """ 251 252 data = {'token': self.developerkey, 'user': apikey, 253 'device': device_key} 254 255 response_stream = self._post(self._urls['verify'], data) 256 257 self._parse_response_stream(response_stream, True) 258 259 if self._last['user'] and 'invalid' in self._last['user'].lower(): 260 self._raise_exception() 261 262 return self._last['status']
263 264 265 if __name__ == '__main__': 266 pass 267