1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 """Module for sending push notifications to iOS devices that have
22 Prowl installed. See http://www.prowlapp.com/ for more
23 information.
24
25 """
26
27
28 try:
29 from xml.etree import cElementTree
30 ElementTree = cElementTree
31 except ImportError:
32 from xml.etree import ElementTree
33
34 from pushnotify import abstract
35 from pushnotify import exceptions
36
37
38 PUBLIC_API_URL = u'https://api.prowlapp.com/publicapi'
39 VERIFY_URL = u'/'.join([PUBLIC_API_URL, 'verify'])
40 NOTIFY_URL = u'/'.join([PUBLIC_API_URL, 'add'])
41 RETRIEVE_TOKEN_URL = u'/'.join([PUBLIC_API_URL, 'retrieve', 'token'])
42 RETRIEVE_APIKEY_URL = u'/'.join([PUBLIC_API_URL, 'retrieve', 'apikey'])
43
44 DESC_LIMIT = 10000
45
46
47 -class Client(abstract.AbstractClient):
48 """Client for sending push notificiations to iOS devices with
49 the Prowl application installed.
50
51 Member Vars:
52 developerkey: A string containing a valid provider key for the
53 Prowl application.
54 application: A string containing the name of the application on
55 behalf of whom the Prowl client will be sending messages.
56 apikeys: A dictionary where the keys are strings containing
57 valid user API keys, and the values are lists of strings,
58 each containing a valid user device key. Device keys are not
59 used by this client.
60
61 """
62
63 - def __init__(self, developerkey='', application=''):
64 """Initialize the Prowl client.
65
66 Args:
67 developerkey: A string containing a valid provider key for
68 the Prowl application.
69 application: A string containing the name of the application
70 on behalf of whom the Prowl client will be sending
71 messages.
72
73 """
74
75 super(self.__class__, self).__init__(developerkey, application)
76
77 self._type = 'prowl'
78 self._urls = {'notify': NOTIFY_URL, 'verify': VERIFY_URL,
79 'retrieve_token': RETRIEVE_TOKEN_URL,
80 'retrieve_apikey': RETRIEVE_APIKEY_URL}
81
83
84 xmlresp = response.text
85 self.logger.info('received response: {0}'.format(xmlresp))
86
87 root = ElementTree.fromstring(xmlresp)
88
89 self._last['type'] = root[0].tag.lower()
90 self._last['code'] = root[0].attrib['code']
91
92 if self._last['type'] == 'success':
93 self._last['message'] = None
94 self._last['remaining'] = root[0].attrib['remaining']
95 self._last['resetdate'] = root[0].attrib['resetdate']
96 elif self._last['type'] == 'error':
97 self._last['message'] = root[0].text
98 self._last['remaining'] = None
99 self._last['resetdate'] = None
100
101 if (not verify or
102 (self._last['code'] != '400' and
103 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
126
127 if self._last['code'] == '400':
128 raise exceptions.FormatError(self._last['message'],
129 int(self._last['code']))
130 elif self._last['code'] == '401':
131 if 'provider' not in self._last['message'].lower():
132 raise exceptions.ApiKeyError(self._last['message'],
133 int(self._last['code']))
134 else:
135 raise exceptions.ProviderKeyError(self._last['message'],
136 int(self._last['code']))
137 elif self._last['code'] == '406':
138 raise exceptions.RateLimitExceeded(self._last['message'],
139 int(self._last['code']))
140 elif self._last['code'] == '409':
141 raise exceptions.PermissionDenied(self._last['message'],
142 int(self._last['code']))
143 elif self._last['code'] == '500':
144 raise exceptions.ServerError(self._last['message'],
145 int(self._last['code']))
146 else:
147 raise exceptions.UnknownError(self._last['message'],
148 int(self._last['code']))
149
150 - def notify(self, description, event, split=True, kwargs=None):
151 """Send a notification to each user's apikey in self.apikeys.
152
153 Args:
154 description: A string of up to DESC_LIMIT characters
155 containing the notification text.
156 event: A string of up to 1024 characters containing a
157 subject or brief description of the event.
158 split: A boolean indicating whether to split long
159 descriptions among multiple notifications (True) or to
160 possibly raise an exception (False). (default True)
161 kwargs: A dictionary with any of the following strings as
162 keys:
163 priority: An integer between -2 and 2, indicating the
164 priority of the notification. -2 is the lowest, 2 is
165 the highest, and 0 is normal.
166 url: A string of up to 512 characters containing a URL
167 to attach to the notification.
168 (default: None)
169
170 Raises:
171 pushnotify.exceptions.ApiKeyError
172 pushnotify.exceptions.FormatError
173 pushnotify.exceptions.RateLimitExceeded
174 pushnotify.exceptions.ServerError
175 pushnotify.exceptions.UnknownError
176 pushnotify.exceptions.UnrecognizedResponseError
177
178 """
179
180 def send_notify(description, event, kwargs):
181 data = {'apikey': ','.join(self.apikeys),
182 'application': self.application,
183 'event': event,
184 'description': description}
185
186 if self.developerkey:
187 data['providerkey'] = self.developerkey
188
189 if kwargs:
190 data.update(kwargs)
191
192 response = self._post(self._urls['notify'], data)
193 self._parse_response(response)
194
195 if not self.apikeys:
196 self.logger.warn('notify called with no users set')
197 return
198
199 if split:
200 while description:
201 this_desc = description[0:DESC_LIMIT]
202 description = description[DESC_LIMIT:]
203 send_notify(this_desc, event, kwargs)
204 else:
205 send_notify(description, event, kwargs)
206
208 """Get a user's API key for a given registration token.
209
210 Once a user has approved you sending them push notifications,
211 you can supply the returned token here and get an API key.
212
213 Args:
214 reg_token: A string containing a registration token returned
215 from the retrieve_token method.
216
217 Raises:
218 pushnotify.exceptions.ProviderKeyError
219
220 Returns:
221 A string containing the API key.
222
223 """
224
225 data = {'providerkey': self.developerkey,
226 'token': reg_token}
227
228 response = self._get(self._urls['retrieve_apikey'], data)
229 self._parse_response(response)
230
231 return self._last['apikey']
232
234 """Get a registration token and approval URL.
235
236 A user follows the approval URL and logs in to the Prowl website
237 to approve you sending them push notifications. If you have
238 associated a 'Retrieve success URL' with your provider key, they
239 will be redirected there.
240
241 Raises:
242 pushnotify.exceptions.ProviderKeyError
243
244 Returns:
245 A two-item tuple where the first item is a string containing
246 a registration token, and the second item is a string
247 containing the associated URL.
248 """
249
250 data = {'providerkey': self.developerkey}
251
252 response = self._get(self._urls['retrieve_token'], data)
253 self._parse_response(response)
254
255 return self._last['token'], self._last['token_url']
256
258 """Verify a user's API key.
259
260 Args:
261 apikey: A string of 40 characters containing a user's API
262 key.
263
264 Raises:
265 pushnotify.exceptions.RateLimitExceeded
266 pushnotify.exceptions.ServerError
267 pushnotify.exceptions.UnknownError
268 pushnotify.exceptions.UnrecognizedResponseError
269
270 Returns:
271 A boolean containing True if the user's API key is valid,
272 and False if it is not.
273
274 """
275
276 data = {'apikey': apikey}
277
278 if self.developerkey:
279 data['providerkey'] = self.developerkey
280
281 response = self._get(self._urls['verify'], data)
282 self._parse_response(response, True)
283
284 return self._last['code'] == '200'
285
286 if __name__ == '__main__':
287 pass
288