5
5
import random
6
6
from sys import version_info
7
7
import logging
8
+ import re
8
9
9
10
GCM_URL = 'https://gcm-http.googleapis.com/gcm/send'
10
11
@@ -32,6 +33,11 @@ class GCMTooManyRegIdsException(GCMException):
32
33
class GCMInvalidTtlException (GCMException ):
33
34
pass
34
35
36
+
37
+ class GCMTopicMessageException (GCMException ):
38
+ pass
39
+
40
+
35
41
# Exceptions from Google responses
36
42
37
43
@@ -59,6 +65,10 @@ class GCMUnavailableException(GCMException):
59
65
pass
60
66
61
67
68
+ class GCMInvalidInputException (GCMException ):
69
+ pass
70
+
71
+
62
72
# TODO: Refactor this to be more human-readable
63
73
# TODO: Use OrderedDict for the result type to be able to preserve the order of the messages returned by GCM server
64
74
def group_response (response , registration_ids , key ):
@@ -104,6 +114,8 @@ class Payload(object):
104
114
# TTL in seconds
105
115
GCM_TTL = 2419200
106
116
117
+ topicPattern = re .compile ('/topics/[a-zA-Z0-9-_.~%]+' )
118
+
107
119
def __init__ (self , ** kwargs ):
108
120
self .validate (kwargs )
109
121
self .__dict__ .update (** kwargs )
@@ -121,7 +133,16 @@ def validate(self, options):
121
133
def validate_time_to_live (self , value ):
122
134
if not (0 <= value <= self .GCM_TTL ):
123
135
raise GCMInvalidTtlException ("Invalid time to live value" )
124
-
136
+
137
+ def validate_registration_ids (self , registration_ids ):
138
+
139
+ if len (registration_ids ) > 1000 :
140
+ raise GCMTooManyRegIdsException ("Exceded number of registration_ids" )
141
+
142
+ def validate_to (self , value ):
143
+ if not re .match (Payload .topicPattern , value ):
144
+ raise GCMInvalidInputException ("Invalid topic name: {0}! Does not match the {1} pattern" .format (value , Payload .topicPattern ))
145
+
125
146
@property
126
147
def body (self ):
127
148
raise NotImplementedError
@@ -221,6 +242,16 @@ def construct_payload(self, **kwargs):
221
242
is_json = kwargs .pop ('is_json' , True )
222
243
223
244
if is_json :
245
+ if 'topic' not in kwargs and 'registration_ids' not in kwargs :
246
+ raise GCMMissingRegistrationException ("Missing registration_ids or topic" )
247
+ elif 'topic' in kwargs and 'registration_ids' in kwargs :
248
+ raise GCMInvalidInputException ("Invalid parameters! Can't have both 'registration_ids' and 'to' as input parameters" )
249
+
250
+ if 'topic' in kwargs :
251
+ kwargs ['to' ] = '/topics/{}' .format (kwargs .pop ('topic' ))
252
+ elif 'registration_ids' not in kwargs :
253
+ raise GCMMissingRegistrationException ("Missing registration_ids" )
254
+
224
255
payload = JsonPayload (** kwargs ).body
225
256
else :
226
257
payload = PlaintextPayload (** kwargs ).body
@@ -343,6 +374,12 @@ def handle_json_response(self, response, registration_ids):
343
374
344
375
return info
345
376
377
+ def handle_topic_response (self , response ):
378
+ error = response .get ('error' )
379
+ if error :
380
+ raise GCMTopicMessageException (error )
381
+ return response ['message_id' ]
382
+
346
383
def extract_unsent_reg_ids (self , info ):
347
384
if 'errors' in info and 'Unavailable' in info ['errors' ]:
348
385
return info ['errors' ]['Unavailable' ]
@@ -354,11 +391,14 @@ def plaintext_request(self, **kwargs):
354
391
355
392
:return dict of response body from Google including multicast_id, success, failure, canonical_ids, etc
356
393
"""
394
+ if 'registration_id' not in kwargs :
395
+ raise GCMMissingRegistrationException ("Missing registration_id" )
396
+ elif not kwargs ['registration_id' ]:
397
+ raise GCMMissingRegistrationException ("Empty registration_id" )
357
398
358
399
kwargs ['is_json' ] = False
359
400
retries = kwargs .pop ('retries' , 5 )
360
401
payload = self .construct_payload (** kwargs )
361
-
362
402
backoff = self .BACKOFF_INITIAL_DELAY
363
403
info = None
364
404
has_error = False
@@ -392,10 +432,14 @@ def json_request(self, **kwargs):
392
432
"""
393
433
Makes a JSON request to GCM servers
394
434
395
- :param registration_ids: list of the registration ids
396
- :param data: dict mapping of key-value pairs of messages
435
+ :param kwargs: dict mapping of key-value pairs of parameters
397
436
:return dict of response body from Google including multicast_id, success, failure, canonical_ids, etc
398
437
"""
438
+ if 'registration_ids' not in kwargs :
439
+ raise GCMMissingRegistrationException ("Missing registration_ids" )
440
+ elif not kwargs ['registration_ids' ]:
441
+ raise GCMMissingRegistrationException ("Empty registration_ids" )
442
+
399
443
args = dict (** kwargs )
400
444
401
445
retries = args .pop ('retries' , 5 )
@@ -440,3 +484,40 @@ def json_request(self, **kwargs):
440
484
raise IOError ("Could not make request after %d attempts" % retries )
441
485
442
486
return info
487
+
488
+ def send_topic_message (self , ** kwargs ):
489
+ """
490
+ Publish Topic Messaging to GCM servers
491
+ Ref: https://developers.google.com/cloud-messaging/topic-messaging
492
+
493
+ :param kwargs: dict mapping of key-value pairs of parameters
494
+ :return message_id
495
+ :raises GCMInvalidInputException: if the topic is empty
496
+ """
497
+
498
+ if 'topic' not in kwargs :
499
+ raise GCMInvalidInputException ("Topic name missing!" )
500
+ elif not kwargs ['topic' ]:
501
+ raise GCMInvalidInputException ("Topic name cannot be empty!" )
502
+
503
+ retries = kwargs .pop ('retries' , 5 )
504
+ payload = self .construct_payload (** kwargs )
505
+ backoff = self .BACKOFF_INITIAL_DELAY
506
+
507
+ for attempt in range (retries ):
508
+ try :
509
+ response = self .make_request (payload , is_json = True )
510
+ return self .handle_topic_response (response )
511
+ except GCMUnavailableException :
512
+ sleep_time = backoff / 2 + random .randrange (backoff )
513
+ time .sleep (float (sleep_time ) / 1000 )
514
+ if 2 * backoff < self .MAX_BACKOFF_DELAY :
515
+ backoff *= 2
516
+ else :
517
+ raise IOError ("Could not make request after %d attempts" % retries )
518
+
519
+ def send_device_group_message (self , ** kwargs ):
520
+ raise NotImplementedError
521
+
522
+ def send_downstream_message (self , ** kwargs ):
523
+ return self .json_request (** kwargs )
0 commit comments