|
@@ -0,0 +1,917 @@
|
|
|
+# -*-coding:utf-8-*-
|
|
|
+#
|
|
|
+# Copyright 2020. Huawei Technologies Co., Ltd. All rights reserved.
|
|
|
+#
|
|
|
+# Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
+# you may not use this file except in compliance with the License.
|
|
|
+# You may obtain a copy of the License at
|
|
|
+#
|
|
|
+# http://www.apache.org/licenses/LICENSE-2.0
|
|
|
+#
|
|
|
+# Unless required by applicable law or agreed to in writing, software
|
|
|
+# distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
+# See the License for the specific language governing permissions and
|
|
|
+# limitations under the License.
|
|
|
+
|
|
|
+import numbers
|
|
|
+import re
|
|
|
+import six
|
|
|
+
|
|
|
+
|
|
|
+class Message(object):
|
|
|
+ """A message that can be sent Huawei Cloud Messaging.
|
|
|
+
|
|
|
+ Args:
|
|
|
+ data: A string value.
|
|
|
+ notification: An instance of ``messaging.Notification`` (optional).
|
|
|
+ android: An instance of ``messaging.Android`` (optional).
|
|
|
+ apns: APSN related message definition
|
|
|
+ web_push: Web Push related message definition
|
|
|
+ token: token list, must be tuple (optional).
|
|
|
+ topic: message topic, must be string (optional).
|
|
|
+ condition: message condition, must be string (optional).
|
|
|
+ """
|
|
|
+ def __init__(self, data=None, notification=None, android=None, apns=None, web_push=None, token=None,
|
|
|
+ topic=None, condition=None):
|
|
|
+ MessageValidator.check_message(data, notification, android, apns, web_push, token, topic, condition)
|
|
|
+ self.data = data
|
|
|
+ self.notification = notification
|
|
|
+ self.android = android
|
|
|
+ self.apns = apns
|
|
|
+ self.web_push = web_push
|
|
|
+ self.token = token
|
|
|
+ self.topic = topic
|
|
|
+ self.condition = condition
|
|
|
+
|
|
|
+
|
|
|
+class Notification(object):
|
|
|
+ """A notification that can be included in a message.
|
|
|
+
|
|
|
+ Args:
|
|
|
+ title: Title of the notification (optional).
|
|
|
+ body: Body of the notification (optional).
|
|
|
+ """
|
|
|
+ def __init__(self, title=None, body=None, image=None):
|
|
|
+ MessageValidator.check_notification(title, body, image)
|
|
|
+ self.title = title
|
|
|
+ self.body = body
|
|
|
+ self.image = image
|
|
|
+
|
|
|
+
|
|
|
+# ----------------------------------------------------------------------------------------------------------------------
|
|
|
+
|
|
|
+
|
|
|
+class APNsConfig(object):
|
|
|
+ """
|
|
|
+ Please refer to the Apple APNS API reference:
|
|
|
+ https://developer.apple.com/library/archive/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/\
|
|
|
+ CommunicatingwithAPNs.html
|
|
|
+ """
|
|
|
+ def __init__(self, headers=None, payload=None, apns_hms_options=None):
|
|
|
+ MessageValidator.check_apns_config(headers=headers, payload=payload, apns_hms_options=apns_hms_options)
|
|
|
+ self.headers = headers
|
|
|
+ self.payload = payload
|
|
|
+ self.apns_hms_options = apns_hms_options
|
|
|
+
|
|
|
+
|
|
|
+class APNsHeader(object):
|
|
|
+ """
|
|
|
+ authorization
|
|
|
+ apns-id
|
|
|
+ apns-expiration
|
|
|
+ apns-priority
|
|
|
+ apns-topic
|
|
|
+ apns-collapse-id
|
|
|
+ """
|
|
|
+ HEAD_AUTHORIZATION = "authorization"
|
|
|
+ HEAD_APNs_ID = "apns-id"
|
|
|
+ HEAD_APNs_EXPIRATION = "apns-expiration"
|
|
|
+ HEAD_APNs_PRIORITY = "apns-priority"
|
|
|
+ HEAD_APNs_TOPIC = "pns-topic"
|
|
|
+ HEAD_APNs_COLLAPSE_ID = "apns-collapse-id"
|
|
|
+
|
|
|
+
|
|
|
+class APNsPayload(object):
|
|
|
+ """
|
|
|
+ APNs payload definition
|
|
|
+ """
|
|
|
+ def __init__(self, aps, **kwargs):
|
|
|
+ MessageValidator.check_apns_payload(aps=aps)
|
|
|
+ self.aps = aps
|
|
|
+ self.custom_data = kwargs
|
|
|
+
|
|
|
+
|
|
|
+class APNsAps(object):
|
|
|
+ """
|
|
|
+ APNs aps definition: https://developer.apple.com/library/archive/documentation/NetworkingInternet/Conceptual\
|
|
|
+ /RemoteNotificationsPG/PayloadKeyReference.html#//apple_ref/doc/uid/TP40008194-CH17-SW1
|
|
|
+
|
|
|
+ one sample is as follows:
|
|
|
+
|
|
|
+ {
|
|
|
+ "aps" : {
|
|
|
+ "alert" : {
|
|
|
+ "title" : "Game Request",
|
|
|
+ "body" : "Bob wants to play poker",
|
|
|
+ "action-loc-key" : "PLAY"
|
|
|
+ "loc-key" : "GAME_PLAY_REQUEST_FORMAT",
|
|
|
+ "loc-args" : [ "Jenna", "Frank"],
|
|
|
+ "content-available" : 1
|
|
|
+ },
|
|
|
+ "badge" : 5,
|
|
|
+ "sound" : "bingbong.aiff",
|
|
|
+ },
|
|
|
+ "acme1" : "bar",
|
|
|
+ "acme2" : [ "bang", "whiz" ]
|
|
|
+ }
|
|
|
+ """
|
|
|
+ def __init__(self, alert=None, badge=None, sound=None, content_available=None, category=None,
|
|
|
+ thread_id=None, mutable_content=None, custom_data=None):
|
|
|
+ MessageValidator.check_apns_payload_aps(alert=alert, badge=badge, sound=sound,
|
|
|
+ content_available=content_available, category=category,
|
|
|
+ thread_id=thread_id, mutable_content=mutable_content,
|
|
|
+ custom_data=custom_data)
|
|
|
+ self.alert = alert
|
|
|
+ self.badge = badge
|
|
|
+ self.sound = sound
|
|
|
+ self.content_available = content_available
|
|
|
+ self.category = category
|
|
|
+ self.thread_id = thread_id
|
|
|
+ self.mutable_content = mutable_content
|
|
|
+ self.custom_data = custom_data
|
|
|
+
|
|
|
+
|
|
|
+class APNsAlert(object):
|
|
|
+ """An alert that can be included in ``messaging.Aps``.
|
|
|
+
|
|
|
+ Args:
|
|
|
+
|
|
|
+ """
|
|
|
+
|
|
|
+ def __init__(self, title=None, body=None, loc_key=None, loc_args=None,
|
|
|
+ title_loc_key=None, title_loc_args=None, action_loc_key=None, launch_image=None,
|
|
|
+ custom_data=None):
|
|
|
+ MessageValidator.check_apns_payload_aps_alert(title=title, body=body, loc_key=loc_key, loc_args=loc_args,
|
|
|
+ title_loc_key=title_loc_key, title_loc_args=title_loc_args,
|
|
|
+ action_loc_key=action_loc_key, launch_image=launch_image,
|
|
|
+ custom_data=custom_data)
|
|
|
+ self.title = title
|
|
|
+ self.body = body
|
|
|
+ self.loc_key = loc_key
|
|
|
+ self.loc_args = loc_args
|
|
|
+ self.title_loc_key = title_loc_key
|
|
|
+ self.title_loc_args = title_loc_args
|
|
|
+ self.action_loc_key = action_loc_key
|
|
|
+ self.launch_image = launch_image
|
|
|
+ self.custom_data = custom_data
|
|
|
+
|
|
|
+
|
|
|
+class APNsHMSOptions(object):
|
|
|
+ """Options for features provided by the FCM SDK for iOS.
|
|
|
+
|
|
|
+ Args:
|
|
|
+ target_user_type: Developer or Commercial enviroment
|
|
|
+ """
|
|
|
+ def __init__(self, target_user_type=None):
|
|
|
+ MessageValidator.check_apns_hms_options(target_user_type=target_user_type)
|
|
|
+ self.target_user_type = target_user_type
|
|
|
+
|
|
|
+
|
|
|
+# ----------------------------------------------------------------------------------------------------------------------
|
|
|
+
|
|
|
+
|
|
|
+class WebPushConfig(object):
|
|
|
+ """
|
|
|
+ Web push-specific options that can be included in a message.
|
|
|
+ For Web Push Specification Reference: https://tools.ietf.org/html/rfc8030#section-5
|
|
|
+ For mozilla implementation: https://developer.mozilla.org/en-US/docs/Web/API/notification
|
|
|
+ """
|
|
|
+ TTL_HEADER = "ttl"
|
|
|
+ URGENCY_HEADER = "urgency"
|
|
|
+ TOPIC_HEADER = "topic"
|
|
|
+
|
|
|
+ def __init__(self, headers=None, data=None, notification=None, hms_options=None):
|
|
|
+ """
|
|
|
+
|
|
|
+ :param headers: A dictionary of headers (optional). Refer `Web push Specification`_ for supported headers.
|
|
|
+ :param notification: A ``messaging.WebPushNotification`` to be included in the message (optional).
|
|
|
+ :param hms_options: A ``WebPushHMSOptions`` instance to be included in the message(optional).
|
|
|
+ """
|
|
|
+ MessageValidator.check_webpush_config(headers, data, notification, hms_options)
|
|
|
+ """ Refer to https://tools.ietf.org/html/rfc7240 """
|
|
|
+ self.headers = headers
|
|
|
+ """ message deliver to the end application directly """
|
|
|
+ self.data = data
|
|
|
+ """ Refer to WebPushNotification """
|
|
|
+ self.notification = notification
|
|
|
+ """ Refer to WebPushHMSOptions"""
|
|
|
+ self.hms_options = hms_options
|
|
|
+
|
|
|
+
|
|
|
+class WebPushHeader(object):
|
|
|
+ """
|
|
|
+ Web Push Header, refer to: https://tools.ietf.org/html/rfc7240
|
|
|
+ """
|
|
|
+ def __init__(self, ttl=None, urgency=None, topic=None):
|
|
|
+ MessageValidator.check_webpush_header(ttl, urgency, topic)
|
|
|
+ self.ttl = ttl
|
|
|
+ self.urgency = urgency
|
|
|
+ self.topic = topic
|
|
|
+
|
|
|
+
|
|
|
+class WebPushNotification(object):
|
|
|
+ """
|
|
|
+ Web Push Notification
|
|
|
+ """
|
|
|
+ def __init__(self, title=None, body=None, icon=None, actions=None, badge=None, data=None, dir=None,
|
|
|
+ image=None, lang=None, renotify=None, require_interaction=None, silent=None, tag=None,
|
|
|
+ timestamp=None, vibrate=None):
|
|
|
+ MessageValidator.check_webpush_notification(title=title, body=body, icon=icon, actions=actions, badge=badge,
|
|
|
+ data=data, dir=dir, image=image, lang=lang,
|
|
|
+ renotify=renotify, require_interaction=require_interaction,
|
|
|
+ silent=silent, tag=tag, timestamp=timestamp, vibrate=vibrate)
|
|
|
+ self.title = title
|
|
|
+ self.body = body
|
|
|
+ """ Refer to WebPushNotificationAction """
|
|
|
+ self.actions = actions
|
|
|
+ self.badge = badge
|
|
|
+ self.data = data
|
|
|
+ self.dir = dir
|
|
|
+ self.icon = icon
|
|
|
+ self.image = image
|
|
|
+ self.lang = lang
|
|
|
+ self.renotify = renotify
|
|
|
+ self.require_interaction = require_interaction
|
|
|
+ self.silent = silent
|
|
|
+ self.tag = tag
|
|
|
+ self.timestamp = timestamp
|
|
|
+ self.vibrate = vibrate
|
|
|
+
|
|
|
+
|
|
|
+class WebPushNotificationAction(object):
|
|
|
+ """
|
|
|
+ The action for web push notification
|
|
|
+ """
|
|
|
+ def __init__(self, action=None, title=None, icon=None):
|
|
|
+ """
|
|
|
+
|
|
|
+ :param action:
|
|
|
+ :param title:
|
|
|
+ :param icon:
|
|
|
+ """
|
|
|
+ MessageValidator.check_webpush_notification_action(action=action, title=title, icon=icon)
|
|
|
+ self.action = action
|
|
|
+ self.icon = icon
|
|
|
+ self.title = title
|
|
|
+
|
|
|
+
|
|
|
+class WebPushHMSOptions(object):
|
|
|
+ """
|
|
|
+ optional link option
|
|
|
+ """
|
|
|
+ def __init__(self, link=None):
|
|
|
+ MessageValidator.check_webpush_hms_options(link)
|
|
|
+ self.link = link
|
|
|
+
|
|
|
+
|
|
|
+# ----------------------------------------------------------------------------------------------------------------------
|
|
|
+
|
|
|
+
|
|
|
+class AndroidConfig(object):
|
|
|
+
|
|
|
+ HIGH_PRIORITY = "HIGH"
|
|
|
+ NORMAL_PRIORITY = "NORMAL"
|
|
|
+
|
|
|
+ """
|
|
|
+ Android-specific options that can be included in a message.
|
|
|
+ """
|
|
|
+ def __init__(self, collapse_key=None, urgency='NORMAL', ttl=None, bi_tag=None
|
|
|
+ , fast_app_target=None, notification=None, data=None, category=None):
|
|
|
+ MessageValidator.check_android_config(collapse_key, urgency, ttl, bi_tag, fast_app_target
|
|
|
+ , notification, data)
|
|
|
+ self.collapse_key = collapse_key
|
|
|
+ self.urgency = urgency
|
|
|
+ self.ttl = ttl
|
|
|
+ self.bi_tag = bi_tag
|
|
|
+ self.fast_app_target = fast_app_target
|
|
|
+ self.notification = notification
|
|
|
+ self.data = data
|
|
|
+ self.category = category
|
|
|
+
|
|
|
+
|
|
|
+class AndroidNotification(object):
|
|
|
+
|
|
|
+ PRIORITY_LOW = "LOW"
|
|
|
+ PRIORITY_DEFAULT = "NORMAL"
|
|
|
+ PRIORITY_HIGH = "HIGH"
|
|
|
+ VISIBILITY_UNSPECIFIED = "VISIBILITY_UNSPECIFIED"
|
|
|
+ PRIVATE = "PRIVATE"
|
|
|
+ PUBLIC = "PUBLIC"
|
|
|
+ SECRET = "SECRET"
|
|
|
+
|
|
|
+ """
|
|
|
+ Android-specific notification parameters.
|
|
|
+ """
|
|
|
+
|
|
|
+ def __init__(self, title=None, body=None, icon=None, color=None, sound=None, default_sound=None, tag=None,
|
|
|
+ click_action=None, body_loc_key=None, body_loc_args=None, title_loc_key=None,
|
|
|
+ title_loc_args=None, multi_lang_key=None, channel_id=None, notify_summary=None, image=None,
|
|
|
+ style=None, big_title=None, big_body=None, auto_clear=None, notify_id=None, group=None, badge=None,
|
|
|
+ ticker=None, auto_cancel=None, when=None, importance=None, use_default_vibrate=True,
|
|
|
+ use_default_light=True, vibrate_config=None, visibility=None, light_settings=None, foreground_show=False,
|
|
|
+ buttons=None):
|
|
|
+
|
|
|
+ MessageValidator.check_android(title=title, body=body, icon=icon, color=color, sound=sound,
|
|
|
+ default_sound=default_sound, tag=tag, click_action=click_action,
|
|
|
+ body_loc_key=body_loc_key, body_loc_args=body_loc_args,
|
|
|
+ title_loc_key=title_loc_key, title_loc_args=title_loc_args,
|
|
|
+ multi_lang_key=multi_lang_key, channel_id=channel_id,
|
|
|
+ notify_summary=notify_summary,
|
|
|
+ image=image, style=style, big_title=big_title, big_body=big_body,
|
|
|
+ auto_clear=auto_clear, notify_id=notify_id,
|
|
|
+ group=group, badge=badge, ticker=ticker, auto_cancel=auto_cancel, when=when,
|
|
|
+ importance=importance,
|
|
|
+ use_default_vibrate=use_default_vibrate,
|
|
|
+ use_default_light=use_default_light, vibrate_config=vibrate_config,
|
|
|
+ visibility=visibility, light_settings=light_settings,
|
|
|
+ foreground_show=foreground_show)
|
|
|
+ self.title = title
|
|
|
+ self.body = body
|
|
|
+ self.icon = icon
|
|
|
+ self.color = color
|
|
|
+ self.sound = sound
|
|
|
+ self.default_sound = default_sound
|
|
|
+ self.tag = tag
|
|
|
+ self.click_action = click_action
|
|
|
+ self.body_loc_key = body_loc_key
|
|
|
+ self.body_loc_args = body_loc_args
|
|
|
+ self.title_loc_key = title_loc_key
|
|
|
+ self.title_loc_args = title_loc_args
|
|
|
+ self.multi_lang_key = multi_lang_key
|
|
|
+ self.channel_id = channel_id
|
|
|
+ self.notify_summary = notify_summary
|
|
|
+ self.image = image
|
|
|
+ self.style = style
|
|
|
+ self.big_title = big_title
|
|
|
+ self.big_body = big_body
|
|
|
+ self.auto_clear = auto_clear
|
|
|
+ self.notify_id = notify_id
|
|
|
+ self.group = group
|
|
|
+ self.badge = badge
|
|
|
+ self.ticker = ticker
|
|
|
+ self.auto_cancel = auto_cancel
|
|
|
+ self.when = when
|
|
|
+ self.importance = importance
|
|
|
+ self.use_default_vibrate = use_default_vibrate
|
|
|
+ self.use_default_light = use_default_light
|
|
|
+ self.vibrate_config = vibrate_config
|
|
|
+ self.visibility = visibility
|
|
|
+ self.light_settings = light_settings
|
|
|
+ self.foreground_show = foreground_show
|
|
|
+ self.buttons = buttons
|
|
|
+
|
|
|
+
|
|
|
+class AndroidClickAction(object):
|
|
|
+ """A ClickAction that can be included in a message.android.notification.
|
|
|
+
|
|
|
+ Args:
|
|
|
+ action_type: type of the android.notification (optional).
|
|
|
+ intent: intent of the android.notification (optional).
|
|
|
+ url: url of the android.notification (optional).
|
|
|
+ action: action definition for push message
|
|
|
+ 1: to specific activity of application
|
|
|
+ 2: specific URL
|
|
|
+ 3: to specific application
|
|
|
+ """
|
|
|
+ def __init__(self, action_type=None, intent=None, action=None, url=None):
|
|
|
+ MessageValidator.check_click_action(action_type=action_type, intent=intent, action=action, url=url)
|
|
|
+ self.action_type = action_type
|
|
|
+ self.intent = intent
|
|
|
+ self.action = action
|
|
|
+ self.url = url
|
|
|
+
|
|
|
+class AndroidBadgeNotification(object):
|
|
|
+ """A BadgeNotification that can be included in a message.android.notification.
|
|
|
+
|
|
|
+ Args:
|
|
|
+ add_num: message number of badge notification in the android.notification (optional).
|
|
|
+ set_num: set the specific number of badge notification (optional).
|
|
|
+ clazz: message class of badge notification in the android.notification (optional).
|
|
|
+ """
|
|
|
+ def __init__(self, add_num=None, set_num=None, clazz=None):
|
|
|
+ MessageValidator.check_badge_notification(add_num=add_num, set_num=set_num, clazz=clazz)
|
|
|
+ self.add_num = add_num
|
|
|
+ self.set_num = set_num
|
|
|
+ self.clazz = clazz
|
|
|
+
|
|
|
+
|
|
|
+class AndroidLightSettings(object):
|
|
|
+ """
|
|
|
+ light_settings":{
|
|
|
+ "color":{
|
|
|
+ "alpha":0,
|
|
|
+ "red":0,
|
|
|
+ "green":1,
|
|
|
+ "blue":1
|
|
|
+ },
|
|
|
+ "light_on_duration":"3.5",
|
|
|
+ "light_off_duration":"5S"
|
|
|
+ }
|
|
|
+ """
|
|
|
+ def __init__(self, color=None, light_on_duration=None, light_off_duration=None):
|
|
|
+ MessageValidator.check_light_settings(color=color, light_on_duration=light_on_duration, light_off_duration=light_off_duration)
|
|
|
+ self.color = color
|
|
|
+ self.light_on_duration = light_on_duration
|
|
|
+ self.light_off_duration = light_off_duration
|
|
|
+
|
|
|
+
|
|
|
+class AndroidLightSettingsColor(object):
|
|
|
+ """
|
|
|
+ "color":{
|
|
|
+ "alpha":0,
|
|
|
+ "red":0,
|
|
|
+ "green":1,
|
|
|
+ "blue":1
|
|
|
+ }
|
|
|
+ """
|
|
|
+ def __init__(self, alpha=None, red=None, green=None, blue=None):
|
|
|
+ MessageValidator.check_light_settings_color(alpha=alpha, red=red, green=green, blue=blue)
|
|
|
+ self.alpha = alpha
|
|
|
+ self.red = red
|
|
|
+ self.green = green
|
|
|
+ self.blue = blue
|
|
|
+
|
|
|
+# --------------------------------------------------------------------------------------------------------------------
|
|
|
+
|
|
|
+
|
|
|
+class MessageValidator(object):
|
|
|
+ """
|
|
|
+ message validation utilities.
|
|
|
+ Methods provided in this class raise ValueErrors if any validations fail.
|
|
|
+ """
|
|
|
+ @classmethod
|
|
|
+ def check_https_url(cls, hint, value):
|
|
|
+ cls.check_string(hint, value)
|
|
|
+ if value is not None and not re.match(r"^https:/{2}\w.+$", value):
|
|
|
+ raise ValueError('{0} must be a valid https url.'.format(hint))
|
|
|
+
|
|
|
+ @classmethod
|
|
|
+ def check_string(cls, hint, value, non_empty=False):
|
|
|
+ """Checks if the given value is a string."""
|
|
|
+ if value is None:
|
|
|
+ return None
|
|
|
+ if not isinstance(value, six.string_types):
|
|
|
+ if non_empty:
|
|
|
+ raise ValueError('{0} must be a non-empty string.'.format(hint))
|
|
|
+ else:
|
|
|
+ raise ValueError('{0} must be a string.'.format(hint))
|
|
|
+ if non_empty and not value:
|
|
|
+ raise ValueError('{0} must be a non-empty string.'.format(hint))
|
|
|
+ return value
|
|
|
+
|
|
|
+ @classmethod
|
|
|
+ def assert_string_values(cls, hint, value, *args):
|
|
|
+ """
|
|
|
+ Check the class value should be an instance of string, and related values should be within *args
|
|
|
+ :param hint: prompt message
|
|
|
+ :param value: the real value
|
|
|
+ :param args: the specific value list
|
|
|
+ :return:
|
|
|
+ """
|
|
|
+ if value is None:
|
|
|
+ return None
|
|
|
+ if not isinstance(value, six.string_types):
|
|
|
+ raise ValueError('{0} must be a string.'.format(hint))
|
|
|
+ for v in args:
|
|
|
+ if value.__eq__(v):
|
|
|
+ return value
|
|
|
+
|
|
|
+ raise ValueError('{} must be a value within{}.'.format(hint, args))
|
|
|
+
|
|
|
+ @classmethod
|
|
|
+ def check_string_list(cls, label, value):
|
|
|
+ """Checks if the given value is a list comprised only of strings."""
|
|
|
+ if value is None or value == []:
|
|
|
+ return None
|
|
|
+ if not isinstance(value, list):
|
|
|
+ raise ValueError('{0} must be a list of strings.'.format(label))
|
|
|
+ non_str = [k for k in value if not isinstance(k, six.string_types)]
|
|
|
+ if non_str:
|
|
|
+ raise ValueError('{0} must not contain non-string values.'.format(label))
|
|
|
+ return value
|
|
|
+
|
|
|
+ @classmethod
|
|
|
+ def check_boolean(cls, hint, value):
|
|
|
+ """Checks if the given value is a string."""
|
|
|
+ if value is None:
|
|
|
+ return None
|
|
|
+ if not isinstance(value, bool):
|
|
|
+ raise ValueError('{0} must be a boolean.'.format(hint))
|
|
|
+ return value
|
|
|
+
|
|
|
+ @classmethod
|
|
|
+ def count_boolean(cls, *args):
|
|
|
+ count = 0
|
|
|
+ for v in args:
|
|
|
+ if v:
|
|
|
+ count += 1
|
|
|
+ return count
|
|
|
+
|
|
|
+ @classmethod
|
|
|
+ def check_not_all_none(cls, hint, *args):
|
|
|
+ total_size = len(args)
|
|
|
+ count = 0
|
|
|
+ for data in args:
|
|
|
+ if data is None:
|
|
|
+ count += 1
|
|
|
+ if total_size == count:
|
|
|
+ raise ValueError(hint)
|
|
|
+
|
|
|
+ @classmethod
|
|
|
+ def check_type(cls, class_obj, class_type, hint):
|
|
|
+ if (class_obj is not None) and (not isinstance(class_obj, class_type)):
|
|
|
+ raise ValueError(hint)
|
|
|
+
|
|
|
+ @classmethod
|
|
|
+ def check_type_list(cls, label, value, cls_type):
|
|
|
+ """Checks if the given value is a list comprised only of numbers."""
|
|
|
+ if value is None or value == []:
|
|
|
+ return None
|
|
|
+ if not isinstance(value, list):
|
|
|
+ raise ValueError('{0} must be a list of {1}.'.format(label, cls_type))
|
|
|
+ non_number = [k for k in value if not isinstance(k, cls_type)]
|
|
|
+ if non_number:
|
|
|
+ raise ValueError('{0} must not contain non-{1} values.'.format(label, cls_type))
|
|
|
+ return value
|
|
|
+
|
|
|
+ @classmethod
|
|
|
+ def check_number(cls, label, value):
|
|
|
+ if value is None:
|
|
|
+ return None
|
|
|
+ if not isinstance(value, numbers.Number):
|
|
|
+ raise ValueError('{0} must be a number.'.format(label))
|
|
|
+ return value
|
|
|
+
|
|
|
+ @classmethod
|
|
|
+ def check_number_span(cls, label, value, min, max):
|
|
|
+ if value is None:
|
|
|
+ return None
|
|
|
+ if not isinstance(value, numbers.Number):
|
|
|
+ raise ValueError('{0} must be a number.'.format(label))
|
|
|
+ if value < min or value > max:
|
|
|
+ raise ValueError('{0} must be within {1} to {2}.'.format(label, min, max))
|
|
|
+ return value
|
|
|
+
|
|
|
+ @classmethod
|
|
|
+ def assert_integer_values(cls, hint, value, *args):
|
|
|
+ """
|
|
|
+ Check the class value should be an instance of string, and related values should be within *args
|
|
|
+ :param hint: prompt message
|
|
|
+ :param value: the real value
|
|
|
+ :param args: the specific value list
|
|
|
+ :return:
|
|
|
+ """
|
|
|
+ if value is None:
|
|
|
+ return None
|
|
|
+ if not isinstance(value, six.integer_types):
|
|
|
+ raise ValueError('{0} must be a integer.'.format(hint))
|
|
|
+ for v in args:
|
|
|
+ if value == v:
|
|
|
+ return value
|
|
|
+
|
|
|
+ raise ValueError('{} must be a value within{}.'.format(hint, args))
|
|
|
+
|
|
|
+ @classmethod
|
|
|
+ def check_number_list(cls, label, value):
|
|
|
+ if value is None or value == []:
|
|
|
+ return None
|
|
|
+ if not isinstance(value, list):
|
|
|
+ raise ValueError('{0} must be a list of numbers.'.format(label))
|
|
|
+ non_number = [k for k in value if not isinstance(k, numbers.Number)]
|
|
|
+ if non_number:
|
|
|
+ raise ValueError('{0} must not contain non-number values.'.format(label))
|
|
|
+ return value
|
|
|
+
|
|
|
+ @classmethod
|
|
|
+ def check_string_dict(cls, label, value):
|
|
|
+ if value is None or value == {}:
|
|
|
+ return None
|
|
|
+ if not isinstance(value, dict):
|
|
|
+ raise ValueError('{0} must be a dictionary.'.format(label))
|
|
|
+ non_str = [k for k in value if not isinstance(k, six.string_types)]
|
|
|
+ if non_str:
|
|
|
+ raise ValueError('{0} must not contain non-string keys.'.format(label))
|
|
|
+ return value
|
|
|
+
|
|
|
+ # ------------------------------------------------------------------------------------------------------------------
|
|
|
+
|
|
|
+ @classmethod
|
|
|
+ def check_message(cls, data, notification, android, apns, web_push, token, topic, condition):
|
|
|
+ """
|
|
|
+ Check whether the message parameter is valid or not
|
|
|
+
|
|
|
+ :param data:
|
|
|
+ :param notification:
|
|
|
+ :param android:
|
|
|
+ :param apns:
|
|
|
+ :param web_push:
|
|
|
+ :param token:
|
|
|
+ :param topic:
|
|
|
+ :param condition:
|
|
|
+ :return:
|
|
|
+ """
|
|
|
+ # data must be string
|
|
|
+ cls.check_string(hint="Message.data", value=data)
|
|
|
+
|
|
|
+ # notification
|
|
|
+ if (notification is not None) and (not isinstance(notification, Notification)):
|
|
|
+ raise ValueError('notification must be an instance of Notification class')
|
|
|
+
|
|
|
+ # android / APNs / Web Push
|
|
|
+ # if notification message(data is None), one of android / APNs / Web Push must be present
|
|
|
+ if data is None:
|
|
|
+ cls.check_not_all_none('Message.data is None, one of Message.android/Message.apns/Message.webpush \
|
|
|
+ must be present', android, apns, web_push)
|
|
|
+
|
|
|
+ cls.check_type(android, AndroidConfig, 'android must be an instance of AndroidConfig class')
|
|
|
+ cls.check_type(apns, APNsConfig, 'apns must be an instance of APNsConfig class')
|
|
|
+ cls.check_type(web_push, WebPushConfig, 'web_push must be an instance of WebPushConfig class')
|
|
|
+
|
|
|
+ """token, topic, condition"""
|
|
|
+ # [token, topic, condition] only one not None
|
|
|
+ target_count = cls.count_boolean(token is not None, topic is not None, condition is not None)
|
|
|
+ if target_count != 1:
|
|
|
+ raise ValueError('Exactly one of token, topic or condition must be specified.')
|
|
|
+
|
|
|
+ # token must be tuple or list
|
|
|
+ if token is not None:
|
|
|
+ if not isinstance(token, tuple) and not isinstance(token, list):
|
|
|
+ raise ValueError('token must be a tuple or a list')
|
|
|
+ if len(token) > 1000:
|
|
|
+ raise ValueError('token must not contain more than 1000 tokens')
|
|
|
+
|
|
|
+ cls.check_string(hint="Message.topic", value=topic)
|
|
|
+ cls.check_string(hint="Message.condition", value=condition)
|
|
|
+
|
|
|
+ @classmethod
|
|
|
+ def check_notification(cls, title, body, image):
|
|
|
+ cls.check_string(hint="Notification.title", value=title)
|
|
|
+ cls.check_string(hint="Notification.body", value=body)
|
|
|
+ cls.check_https_url(hint="Notification.image", value=image)
|
|
|
+
|
|
|
+ @classmethod
|
|
|
+ def check_android_config(cls, collapse_key, urgency, ttl, bi_tag, fast_app_target, notification, data):
|
|
|
+ # collapse_key
|
|
|
+ cls.check_number('AndroidConfig.collapse_key', collapse_key)
|
|
|
+ # urgency
|
|
|
+ cls.assert_string_values("AndroidConfig.urgency", urgency, AndroidConfig.HIGH_PRIORITY,
|
|
|
+ AndroidConfig.NORMAL_PRIORITY)
|
|
|
+ # ttl
|
|
|
+ cls.check_string(hint="AndroidConfig.ttl", value=ttl)
|
|
|
+ # bi_tag
|
|
|
+ cls.check_string(hint="AndroidConfig.bi_tag", value=bi_tag)
|
|
|
+ # fast_app_target
|
|
|
+ cls.check_number_span("AndroidConfig.fast_app_target", fast_app_target, 1, 2)
|
|
|
+ # notification
|
|
|
+ cls.check_type(notification, AndroidNotification,
|
|
|
+ hint='notification must be an instance of AndroidNotification')
|
|
|
+ # data
|
|
|
+ cls.check_string(hint="AndroidConfig.data", value=data)
|
|
|
+
|
|
|
+ @classmethod
|
|
|
+ def check_android(cls, title, body, icon, color, sound, default_sound, tag, click_action, body_loc_key, body_loc_args,
|
|
|
+ title_loc_key, title_loc_args, multi_lang_key, channel_id, notify_summary, image,
|
|
|
+ style, big_title, big_body, auto_clear, notify_id, group, badge,
|
|
|
+ ticker, auto_cancel, when, importance,
|
|
|
+ use_default_vibrate, use_default_light, vibrate_config, visibility, light_settings,
|
|
|
+ foreground_show):
|
|
|
+ # title
|
|
|
+ cls.check_string(hint="AndroidNotification.title", value=title)
|
|
|
+ # body
|
|
|
+ cls.check_string(hint="AndroidNotification.body", value=body)
|
|
|
+ # icon
|
|
|
+ cls.check_string(hint="AndroidNotification.icon", value=icon)
|
|
|
+ # color
|
|
|
+ cls.check_string(hint="AndroidNotification.color", value=color)
|
|
|
+ # sound
|
|
|
+ cls.check_string(hint="AndroidNotification.sound", value=sound)
|
|
|
+ # default_sound
|
|
|
+ cls.check_boolean(hint="AndroidNotification.default_sound", value=default_sound)
|
|
|
+ # tag
|
|
|
+ cls.check_string(hint="AndroidNotification.tag", value=tag)
|
|
|
+ # click_action
|
|
|
+ cls.check_type(click_action, AndroidClickAction,
|
|
|
+ hint='click_action must be an instance of AndroidClickAction')
|
|
|
+ # body_loc_key
|
|
|
+ cls.check_string(hint="AndroidNotification.body_loc_key", value=body_loc_key)
|
|
|
+ # body_loc_args
|
|
|
+ if (body_loc_args is not None) and (not isinstance(body_loc_args, tuple)) and (not isinstance(body_loc_args, list)):
|
|
|
+ raise ValueError('AndroidNotification.body_loc_args must be an instance of tuple or list')
|
|
|
+ # title_loc_key
|
|
|
+ cls.check_string(hint="AndroidNotification.title_loc_key", value=title_loc_key)
|
|
|
+ # title_loc_args
|
|
|
+ if (title_loc_args is not None) and (not isinstance(title_loc_args, tuple) and not isinstance(title_loc_args, list)):
|
|
|
+ raise ValueError('AndroidNotification.title_loc_args must be an instance of tuple or list')
|
|
|
+ # multi_lang_key
|
|
|
+ if multi_lang_key is not None:
|
|
|
+ if not isinstance(multi_lang_key, dict):
|
|
|
+ raise ValueError('AndroidNotification.multi_lang_key must be a dict.')
|
|
|
+ # channel_id
|
|
|
+ cls.check_string(hint="AndroidNotification.channel_id", value=channel_id)
|
|
|
+ # notify_summary
|
|
|
+ cls.check_string(hint="AndroidNotification.notify_summary", value=notify_summary)
|
|
|
+ #
|
|
|
+ # image
|
|
|
+ cls.check_https_url(hint="AndroidNotification.image", value=image)
|
|
|
+ # style
|
|
|
+ if style is not None:
|
|
|
+ if style not in [0, 1, 2]:
|
|
|
+ raise ValueError('AndroidNotification.style must in [0, 1, 2]')
|
|
|
+ # big_title, big_body
|
|
|
+ if style == 1:
|
|
|
+ if (big_title is None) or (not isinstance(big_title, str)):
|
|
|
+ raise ValueError('AndroidNotification.big_title must be valid string when style is 1')
|
|
|
+ if (big_body is None) and (not isinstance(big_body, str)):
|
|
|
+ raise ValueError('AndroidNotification.big_body must be valid string when style is 1')
|
|
|
+ # auto_clear
|
|
|
+ cls.check_number(label='AndroidNotification.auto_clear ', value=auto_clear)
|
|
|
+ # notify_id
|
|
|
+ cls.check_number(label='AndroidNotification.notify_id ', value=notify_id)
|
|
|
+ # group
|
|
|
+ cls.check_string(hint="AndroidNotification.group", value=group)
|
|
|
+ # badge
|
|
|
+ cls.check_type(badge, AndroidBadgeNotification, "badge should be an instance of AndroidBadgeNotification")
|
|
|
+ # ticker
|
|
|
+ cls.check_string(hint="AndroidNotification.ticker", value=ticker)
|
|
|
+ # auto_cancel
|
|
|
+ cls.check_boolean(hint="AndroidNotification.auto_cancel", value=auto_cancel)
|
|
|
+ # when
|
|
|
+ cls.check_string(hint="AndroidNotification.when", value=when)
|
|
|
+ # importance
|
|
|
+ cls.assert_string_values("AndroidNotification.importance", importance,
|
|
|
+ AndroidNotification.PRIORITY_DEFAULT,
|
|
|
+ AndroidNotification.PRIORITY_HIGH, AndroidNotification.PRIORITY_LOW)
|
|
|
+ # use_default_vibrate
|
|
|
+ cls.check_boolean(hint="AndroidNotification.use_default_vibrate", value=use_default_vibrate)
|
|
|
+ # use_default_light
|
|
|
+ cls.check_boolean(hint="AndroidNotification.use_default_light", value=use_default_light)
|
|
|
+ # vibrate_config
|
|
|
+ cls.check_string_list(label="AndroidNotification.vibrate_config", value=vibrate_config)
|
|
|
+ # visibility
|
|
|
+ cls.assert_string_values("AndroidNotification.visibility", visibility,
|
|
|
+ AndroidNotification.PRIVATE,
|
|
|
+ AndroidNotification.PUBLIC, AndroidNotification.SECRET,
|
|
|
+ AndroidNotification.VISIBILITY_UNSPECIFIED)
|
|
|
+ # light_settings
|
|
|
+ cls.check_type(light_settings, AndroidLightSettings, "light_settings should be an instance of AndroidLightSettings")
|
|
|
+ # foreground_show
|
|
|
+ cls.check_boolean(hint="AndroidNotification.foreground_show", value=foreground_show)
|
|
|
+
|
|
|
+ @classmethod
|
|
|
+ def check_badge_notification(cls, add_num, set_num, clazz):
|
|
|
+ # add_num must be int
|
|
|
+ cls.check_number_span(label="AndroidBadgeNotification.add_num", value=add_num, min=0, max=100)
|
|
|
+ # set_num must be int
|
|
|
+ cls.check_number_span(label="AndroidBadgeNotification.set_num", value=set_num, min=0, max=100)
|
|
|
+ # clazz
|
|
|
+ cls.check_string(hint="AndroidBadgeNotification.clazz", value=clazz)
|
|
|
+
|
|
|
+ @classmethod
|
|
|
+ def check_click_action(cls, action_type, intent, action, url):
|
|
|
+ # type must be in [1, 4]
|
|
|
+ if (action_type is None) or (action_type not in [1, 2, 3, 4]):
|
|
|
+ raise ValueError('ClickAction.type must be in [1, 2, 3, 4]')
|
|
|
+
|
|
|
+ # intent, if type is 1, intent or action must be present or both
|
|
|
+ if action_type == 1:
|
|
|
+ count = cls.count_boolean(isinstance(intent, str), isinstance(action, str))
|
|
|
+ if count <= 0:
|
|
|
+ raise ValueError('ClickAction.intent or ClickAction.action must be present or both when click_type is 1')
|
|
|
+
|
|
|
+ # url, if type is 2, url must
|
|
|
+ if action_type == 2:
|
|
|
+ if not isinstance(url, str):
|
|
|
+ raise ValueError('ClickAction.url must when ClickAction.type is 2')
|
|
|
+ if not url.upper().startswith('HTTPS'):
|
|
|
+ raise ValueError('ClickAction.url must be https prefix when ClickAction.type is 2')
|
|
|
+
|
|
|
+ @classmethod
|
|
|
+ def check_light_settings(cls, color, light_on_duration, light_off_duration):
|
|
|
+ cls.check_type(color, AndroidLightSettingsColor, "color must be an instance of AndroidLightSettingsColor")
|
|
|
+ cls.check_string(hint="AndroidLightSettings.light_on_duration", value=light_on_duration)
|
|
|
+ cls.check_string(hint="AndroidLightSettings.light_off_duration", value=light_off_duration)
|
|
|
+
|
|
|
+ @classmethod
|
|
|
+ def check_light_settings_color(cls, alpha, red, green, blue):
|
|
|
+ cls.check_number("AndroidLightSettingsColor.alpha", alpha)
|
|
|
+ cls.check_number("AndroidLightSettingsColor.red", red)
|
|
|
+ cls.check_number("AndroidLightSettingsColor.green", green)
|
|
|
+ cls.check_number("AndroidLightSettingsColor.blue", blue)
|
|
|
+
|
|
|
+ @classmethod
|
|
|
+ def check_webpush_config(cls, headers, data, notification, hms_options):
|
|
|
+ # headers
|
|
|
+ cls.check_type(headers, WebPushHeader, "headers must be an instance of WebPushHeader")
|
|
|
+ cls.check_string(hint="WebPushConfig.headers", value=data)
|
|
|
+ cls.check_type(notification, WebPushNotification, "notification must be an instance of WebPushNotification")
|
|
|
+ cls.check_type(hms_options, WebPushHMSOptions, "hms_options must be an instance of WebPushHMSOptions")
|
|
|
+
|
|
|
+ @classmethod
|
|
|
+ def check_webpush_header(cls, ttl=None, urgency=None, topic=None):
|
|
|
+ cls.check_string(hint="WebPushHeader.ttl", value=ttl)
|
|
|
+ cls.check_string(hint="WebPushHeader.urgency", value=urgency)
|
|
|
+ cls.check_string(hint="WebPushHeader.topic", value=topic)
|
|
|
+
|
|
|
+ @classmethod
|
|
|
+ def check_webpush_notification(cls, title=None, body=None, icon=None, actions=None, badge=None,
|
|
|
+ data=None, dir=None, image=None, lang=None, renotify=None,
|
|
|
+ require_interaction=None, silent=None, tag=None, timestamp=None, vibrate=None):
|
|
|
+ cls.check_string(hint="WebPushNotification.title", value=title)
|
|
|
+ cls.check_string(hint="WebPushNotification.body", value=body)
|
|
|
+ cls.check_string(hint="WebPushNotification.icon", value=icon)
|
|
|
+ cls.check_string(hint="WebPushNotification.data", value=data)
|
|
|
+ cls.check_type_list("WebPushNotificationAction.actions", actions, WebPushNotificationAction)
|
|
|
+ cls.check_string(hint="WebPushNotification.image", value=image)
|
|
|
+ cls.check_string(hint="WebPushNotification.lang", value=lang)
|
|
|
+ cls.check_string(hint="WebPushNotification.tag", value=tag)
|
|
|
+ cls.check_string(hint="WebPushNotification.badge", value=badge)
|
|
|
+ cls.assert_string_values("WebPushNotification.dir", dir, "auto", "ltr", "rtl")
|
|
|
+ cls.check_number_list("WebPushNotification.vibrate", vibrate)
|
|
|
+ cls.check_boolean("WebPushNotification.renotify", renotify)
|
|
|
+ cls.check_boolean("WebPushNotification.require_interaction", require_interaction)
|
|
|
+ cls.check_boolean("WebPushNotification.silent", silent)
|
|
|
+ cls.check_number("WebPushNotification.timestamp", timestamp)
|
|
|
+
|
|
|
+ @classmethod
|
|
|
+ def check_webpush_notification_action(cls, action=None, title=None, icon=None):
|
|
|
+ cls.check_string(hint="WebPushNotificationAction.action", value=action)
|
|
|
+ cls.check_string(hint="WebPushNotificationAction.title", value=title)
|
|
|
+ cls.check_string(hint="WebPushNotificationAction.icon", value=icon)
|
|
|
+
|
|
|
+ @classmethod
|
|
|
+ def check_webpush_hms_options(cls, link):
|
|
|
+ cls.check_string(hint="WebPushHMSOptions.link", value=link)
|
|
|
+
|
|
|
+ @classmethod
|
|
|
+ def check_apns_config(cls, headers=None, payload=None, apns_hms_options=None):
|
|
|
+ cls.check_string_dict("APNsConfig.headers", headers)
|
|
|
+ cls.check_type(payload, APNsPayload, "payload must be an instance of APNsPayload")
|
|
|
+ cls.check_type(apns_hms_options, APNsHMSOptions, "apns_hms_options must be an instance of APNsHMSOptions")
|
|
|
+
|
|
|
+ @classmethod
|
|
|
+ def check_apns_payload(cls, aps=None):
|
|
|
+ cls.check_type(aps, APNsAps, "aps must be an instance of APNsAps")
|
|
|
+ pass
|
|
|
+
|
|
|
+ @classmethod
|
|
|
+ def check_apns_payload_aps(cls, alert, badge, sound, content_available, category, thread_id, mutable_content,
|
|
|
+ custom_data):
|
|
|
+ # alert: Dictionary or String
|
|
|
+ if alert is not None:
|
|
|
+ if not isinstance(alert, six.string_types):
|
|
|
+ cls.check_type(alert, APNsAlert, "alert must be an instance of String or APNsAlert class")
|
|
|
+ # badge: Number
|
|
|
+ cls.check_number("APNsAps.badge", badge)
|
|
|
+ # sound: String
|
|
|
+ cls.check_string("APNsAps.sound", sound)
|
|
|
+ # content_available: number
|
|
|
+ cls.check_number("APNsAps.content_available", content_available)
|
|
|
+ # category: String
|
|
|
+ cls.check_string("APNsAps.category", category)
|
|
|
+ # thread_id: String
|
|
|
+ cls.check_string("APNsAps.thread_id", thread_id)
|
|
|
+ # mutable_content
|
|
|
+ cls.check_boolean("APNsAps.mutable_content", mutable_content)
|
|
|
+ # custom_data
|
|
|
+ if custom_data is not None:
|
|
|
+ if not isinstance(custom_data, dict):
|
|
|
+ raise ValueError('APNsAps.custom_data must be a dict.')
|
|
|
+
|
|
|
+ @classmethod
|
|
|
+ def check_apns_payload_aps_alert(cls, title, body, loc_key, loc_args, title_loc_key, title_loc_args, action_loc_key,
|
|
|
+ launch_image, custom_data):
|
|
|
+ # title: String
|
|
|
+ cls.check_string("APNsAlert.title", title)
|
|
|
+ # body: String
|
|
|
+ cls.check_string("APNsAlert.body", body)
|
|
|
+ # loc_key: String
|
|
|
+ cls.check_string("APNsAlert.loc_key", loc_key)
|
|
|
+ # loc_args: Array of strings
|
|
|
+ cls.check_string_list("APNsAlert.loc_args", loc_args)
|
|
|
+ # title_loc_key: String or null
|
|
|
+ cls.check_string("APNsAlert.title_loc_key", title_loc_key)
|
|
|
+ # title_loc_args: Array of strings or null
|
|
|
+ cls.check_string_list("APNsAlert.title_loc_args", title_loc_args)
|
|
|
+ # action_loc_key: String or null
|
|
|
+ cls.check_string("APNsAlert.action_loc_key", action_loc_key)
|
|
|
+ # launch_image: String
|
|
|
+ cls.check_string("APNsAlert.launch_image", launch_image)
|
|
|
+ # custom_data
|
|
|
+ if custom_data is not None:
|
|
|
+ if not isinstance(custom_data, dict):
|
|
|
+ raise ValueError('APNsAlert.custom_data must be a dict.')
|
|
|
+
|
|
|
+ @classmethod
|
|
|
+ def check_apns_hms_options(cls, target_user_type):
|
|
|
+ cls.assert_integer_values("APNsHMSOptions.target_user_type", target_user_type, 1, 2, 3)
|