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

Source Code for Module pushnotify.pushover

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