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 logging 
 16  import time 
 17  import urllib 
 18  import urllib2 
 19   
 20  from pushnotify import exceptions 
 21   
 22   
 23  PUBLIC_API_URL = u'https://api.pushover.net/1' 
 24  VERIFY_URL = u'/'.join([PUBLIC_API_URL, u'users/validate.json']) 
 25  NOTIFY_URL = u'/'.join([PUBLIC_API_URL, u'messages.json']) 
 26   
 27   
28 -class Client(object):
29 """Client for sending push notifications to Android and iOS devices 30 with the Pushover application installed. 31 32 Member Vars: 33 token: A string containing a valid API token. 34 35 """ 36
37 - def __init__(self, token, users=None):
38 """Initialize the Pushover client. 39 40 Args: 41 token: A string of 30 characters containing a valid API 42 token. 43 users: A list containing 1 or 2-item tuples, where the first 44 item is a string of 30 characters containing a user 45 token, and the second is an optional string of up to 25 46 characters containing a device name for the given user. 47 (default: None) 48 49 """ 50 51 self.logger = logging.getLogger('{0}.{1}'.format( 52 self.__module__, self.__class__.__name__)) 53 54 self._browser = urllib2.build_opener(urllib2.HTTPSHandler()) 55 self._last_code = None 56 self._last_device = None 57 self._last_errors = None 58 self._last_status = None 59 self._last_token = None 60 self._last_user = None 61 62 self.token = token 63 self.users = [] if users is None else users
64
65 - def _parse_response(self, stream, verify=False):
66 67 response = stream.read() 68 self.logger.info('received response: {0}'.format(response)) 69 70 response = json.loads(response) 71 72 self._last_code = stream.code 73 if 'device' in response.keys(): 74 self._last_device = response['device'] 75 else: 76 self._last_device = None 77 if 'errors' in response.keys(): 78 self._last_errors = response['errors'] 79 else: 80 self._last_errors = None 81 if 'status' in response.keys(): 82 self._last_status = response['status'] 83 else: 84 self._last_status = None 85 if 'token' in response.keys(): 86 self._last_token = response['token'] 87 if 'user' in response.keys(): 88 self._last_user = response['user'] 89 else: 90 self._last_user = None 91 92 return self._last_status
93
94 - def _post(self, url, data):
95 96 self.logger.debug('_post sending data: {0}'.format(data)) 97 self.logger.debug('_post sending to url: {0}'.format(url)) 98 99 request = urllib2.Request(url, data) 100 try: 101 response_stream = self._browser.open(request) 102 except urllib2.HTTPError, exc: 103 return exc 104 else: 105 return response_stream
106
107 - def _raise_exception(self):
108 109 msg = '' 110 if self._last_errors: 111 messages = [] 112 for key, value in self._last_errors.items(): 113 messages.append('{0} {1}'.format(key, value[0])) 114 msg = '; '.join(messages) 115 116 if self._last_device and 'invalid' in self._last_device: 117 raise exceptions.ApiKeyError('device invalid', self._last_code) 118 119 elif self._last_token and 'invalid' in self._last_token: 120 raise exceptions.ApiKeyError('token invalid', self._last_code) 121 122 elif self._last_user and 'invalid' in self._last_user: 123 raise exceptions.ApiKeyError('user invalid', self._last_code) 124 125 elif self._last_code == 429: 126 # TODO: what is actually returned when the rate limit is hit? 127 128 msg = 'too many messages sent this month' if not msg else msg 129 raise exceptions.RateLimitExceeded(msg, self._last_code) 130 131 elif self._last_code >= 500 and self._last_code <= 599: 132 raise exceptions.ServerError(msg, self._last_code) 133 134 elif self._last_errors: 135 raise exceptions.FormatError(msg, self._last_code) 136 137 else: 138 raise exceptions.UnrecognizedResponseError(msg, self._last_code)
139
140 - def notify(self, title, message, kwargs=None):
141 """Send a notification to each user/device in self.users. 142 143 As of 2012-09-18, this is not returning a 4xx status code as 144 per the Pushover API docs, but instead chopping the delivered 145 messages off at 512 characters. 146 147 Args: 148 title: A string of up to 100 characters containing the 149 title of the message (i.e. subject or brief description) 150 message: A string of up to 512 characters containing the 151 notification text. 152 kwargs: A dictionary with any of the following strings as 153 keys: 154 priority: The integer 1, which will make the 155 notification display in red and override any set 156 quiet hours. 157 url: A string of up to 500 characters containing a URL 158 to attach to the notification. 159 url_title: A string of up to 50 characters containing a 160 title to give the attached URL. 161 (default: None) 162 163 Raises: 164 pushnotify.exceptions.ApiKeyError 165 pushnotify.exceptions.FormatError 166 pushnotify.exceptions.RateLimitExceeded 167 pushnotify.exceptions.ServerError 168 pushnotify.exceptions.UnrecognizedResponseError 169 170 """ 171 172 """Here we match the behavior of Notify My Android and Prowl: 173 raise a single exception if and only if every notification 174 fails""" 175 176 raise_exception = False 177 178 if not self.users: 179 self.logger.warn('notify called with no users set') 180 181 for user in self.users: 182 data = {'token': self.token, 183 'user': user[0], 184 'title': title, 185 'message': message, 186 'timestamp': int(time.time())} 187 188 if user[1]: 189 data['device'] = user[1] 190 191 if kwargs: 192 data.update(kwargs) 193 194 data = urllib.urlencode(data) 195 196 response = self._post(NOTIFY_URL, data) 197 status = self._parse_response(response) 198 if not status: 199 raise_exception = not status 200 201 if raise_exception: 202 self._raise_exception()
203
204 - def verify_user(self, user):
205 """Verify a user token. 206 207 Args: 208 user: A string containing a valid user token. 209 210 Returns: 211 A boolean containing True if the user token is valid, and 212 False if it is not. 213 214 """ 215 216 data = {'token': self.token, 'user': user} 217 218 data = urllib.urlencode(data) 219 response_stream = self._post(VERIFY_URL, data) 220 221 self._parse_response(response_stream, True) 222 223 return self._last_status
224
225 - def verify_device(self, user, device):
226 """Verify a device for a user. 227 228 Args: 229 user: A string containing a valid user token. 230 device: A string containing a device name. 231 232 Raises: 233 pushnotify.exceptions.ApiKeyError 234 235 Returns: 236 A boolean containing True if the device is valid, and 237 False if it is not. 238 239 """ 240 241 data = {'token': self.token, 'user': user, 'device': device} 242 243 data = urllib.urlencode(data) 244 response_stream = self._post(VERIFY_URL, data) 245 246 self._parse_response(response_stream, True) 247 248 if self._last_user and 'invalid' in self._last_user.lower(): 249 self._raise_exception() 250 251 return self._last_status
252 253 254 if __name__ == '__main__': 255 pass 256