1
2
3
4 """Module for sending push notifications to iOS devices that have
5 Prowl installed. See http://www.prowlapp.com/ 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 logging
15 import urllib
16 import urllib2
17 try:
18 from xml.etree import cElementTree
19 ElementTree = cElementTree
20 except ImportError:
21 from xml.etree import ElementTree
22
23 from pushnotify import exceptions
24
25
26 PUBLIC_API_URL = u'https://api.prowlapp.com/publicapi'
27 VERIFY_URL = u'/'.join([PUBLIC_API_URL, 'verify'])
28 NOTIFY_URL = u'/'.join([PUBLIC_API_URL, 'add'])
29 RETRIEVE_TOKEN_URL = u'/'.join([PUBLIC_API_URL, 'retrieve', 'token'])
30 RETRIEVE_APIKEY_URL = u'/'.join([PUBLIC_API_URL, 'retrieve', 'apikey'])
31
32
34 """Client for sending push notificiations to iOS devices with
35 the Prowl application installed.
36
37 Member Vars:
38 apikeys: A list of strings, each containing a 40 character api
39 key.
40 providerkey: A string containing a 40 character provider key.
41
42 """
43
44 - def __init__(self, apikeys=None, providerkey=None):
45 """Initialize the Prowl client.
46
47 Args:
48 apikeys: A list of strings of 40 characters each, each
49 containing a valid api key.
50 providerkey: A string of 40 characters containing a valid
51 provider key.
52
53 """
54
55 self.logger = logging.getLogger('{0}.{1}'.format(
56 self.__module__, self.__class__.__name__))
57
58 self._browser = urllib2.build_opener(urllib2.HTTPSHandler())
59 self._last_type = None
60 self._last_code = None
61 self._last_message = None
62 self._last_remaining = None
63 self._last_resetdate = None
64 self._last_token = None
65 self._last_token_url = None
66 self._last_apikey = None
67
68 self.apikeys = [] if apikeys is None else apikeys
69 self.providerkey = providerkey
70
71 - def _get(self, url):
72
73 self.logger.debug('_get requesting url: {0}'.format(url))
74
75 request = urllib2.Request(url)
76 try:
77 response_stream = self._browser.open(request)
78 except urllib2.HTTPError, exc:
79 return exc
80 else:
81 return response_stream
82
84
85 xmlresp = response.read()
86 self.logger.info('received response: {0}'.format(xmlresp))
87
88 root = ElementTree.fromstring(xmlresp)
89
90 self._last_type = root[0].tag.lower()
91 self._last_code = root[0].attrib['code']
92
93 if self._last_type == 'success':
94 self._last_message = None
95 self._last_remaining = root[0].attrib['remaining']
96 self._last_resetdate = root[0].attrib['resetdate']
97 elif self._last_type == 'error':
98 self._last_message = root[0].text
99 self._last_remaining = None
100 self._last_resetdate = None
101
102 if (not verify or
103 (self._last_code != '400' and self._last_code != '401')):
104 self._raise_exception()
105 else:
106 raise exceptions.UnrecognizedResponseError(xmlresp, -1)
107
108 if len(root) > 1:
109 if root[1].tag.lower() == 'retrieve':
110 if 'token' in root[1].attrib:
111 self._last_token = root[1].attrib['token']
112 self._last_token_url = root[1].attrib['url']
113 self._last_apikey = None
114 elif 'apikey' in root[1].attrib:
115 self._last_token = None
116 self.last_token_url = None
117 self._last_apikey = root[1].attrib['apikey']
118 else:
119 raise exceptions.UnrecognizedResponseError(xmlresp, -1)
120 else:
121 raise exceptions.UnrecognizedResponseError(xmlresp, -1)
122
123 return root
124
125 - def _post(self, url, data):
126
127 self.logger.debug('_post sending data: {0}'.format(data))
128 self.logger.debug('_post sending to url: {0}'.format(url))
129
130 request = urllib2.Request(url, data)
131 try:
132 response_stream = self._browser.open(request)
133 except urllib2.HTTPError, exc:
134 return exc
135 else:
136 return response_stream
137
162
163 - def notify(self, app, event, desc, kwargs=None):
164 """Send a notification to each apikey in self.apikeys.
165
166 Args:
167 app: A string of up to 256 characters containing the name
168 of the application sending the notification.
169 event: A string of up to 1024 characters containing the
170 event that is being notified (i.e. subject or brief
171 description.)
172 desc: A string of up to 10000 characters containing the
173 notification text.
174 kwargs: A dictionary with any of the following strings as
175 keys:
176 priority: An integer between -2 and 2, indicating the
177 priority of the notification. -2 is the lowest, 2 is
178 the highest, and 0 is normal.
179 url: A string of up to 512 characters containing a URL
180 to attach to the notification.
181 (default: None)
182
183 Raises:
184 pushnotify.exceptions.FormatError
185 pushnotify.exceptions.ApiKeyError
186 pushnotify.exceptions.RateLimitExceeded
187 pushnotify.exceptions.ServerError
188 pushnotify.exceptions.UnknownError
189 pushnotify.exceptions.UnrecognizedResponseError
190
191 """
192
193 data = {'apikey': ','.join(self.apikeys),
194 'application': app,
195 'event': event,
196 'description': desc}
197
198 if self.providerkey:
199 data['providerkey'] = self.providerkey
200
201 if kwargs:
202 data.update(kwargs)
203
204 data = urllib.urlencode(data)
205
206 response = self._post(NOTIFY_URL, data)
207 self._parse_response(response)
208
210 """Get an API key for a given token.
211
212 Once a user has approved you sending them push notifications,
213 you can supply the returned token here and get an API key.
214
215 Args:
216 token: A string containing a registration token returned
217 from the retrieve_token method.
218
219 Raises:
220 pushnotify.exceptions.ProviderKeyError
221
222 Returns:
223 A string containing the API key.
224
225 """
226
227 data = {'providerkey': self.providerkey,
228 'token': token}
229
230 querystring = urllib.urlencode(data)
231 url = '?'.join([RETRIEVE_APIKEY_URL, querystring])
232
233 response = self._get(url)
234 self._parse_response(response)
235
236 return self._last_apikey
237
239 """Get a registration token and approval URL.
240
241 A user follows the URL and logs in to the Prowl website to
242 approve you sending them push notifications. If you've
243 associated a 'Retrieve success URL' with your provider key, they
244 will be redirected there.
245
246 Raises:
247 pushnotify.exceptions.ProviderKeyError
248
249 Returns:
250 A two-item tuple where the first item is a string containing
251 a registration token, and the second item is a string
252 containing the associated URL.
253 """
254
255 data = {'providerkey': self.providerkey}
256
257 querystring = urllib.urlencode(data)
258 url = '?'.join([RETRIEVE_TOKEN_URL, querystring])
259
260 response = self._get(url)
261 self._parse_response(response)
262
263 return self._last_token, self._last_token_url
264
266 """Verify an API key for a user.
267
268 Args:
269 apikey: A string of 40 characters containing an API key.
270
271 Raises:
272 pushnotify.exceptions.RateLimitExceeded
273 pushnotify.exceptions.ServerError
274 pushnotify.exceptions.UnknownError
275 pushnotify.exceptions.UnrecognizedResponseError
276
277 Returns:
278 A boolean containing True if the API key is valid, and False
279 if it is not.
280
281 """
282
283 data = {'apikey': apikey}
284
285 if self.providerkey:
286 data['providerkey'] = self.providerkey
287
288 querystring = urllib.urlencode(data)
289 url = '?'.join([VERIFY_URL, querystring])
290
291 response = self._get(url)
292 self._parse_response(response, True)
293
294 return self._last_code == '200'
295
296 if __name__ == '__main__':
297 pass
298