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 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
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
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
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
193
194
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
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
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