_message_serializer.py 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613
  1. # -*-coding:utf-8-*-
  2. #
  3. # Copyright 2020. Huawei Technologies Co., Ltd. All rights reserved.
  4. #
  5. # Licensed under the Apache License, Version 2.0 (the "License");
  6. # you may not use this file except in compliance with the License.
  7. # You may obtain a copy of the License at
  8. #
  9. # http://www.apache.org/licenses/LICENSE-2.0
  10. #
  11. # Unless required by applicable law or agreed to in writing, software
  12. # distributed under the License is distributed on an "AS IS" BASIS,
  13. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. # See the License for the specific language governing permissions and
  15. # limitations under the License.
  16. import json
  17. from Service.HuaweiPushService.push_admin import _messages
  18. import six
  19. class MessageSerializer(json.JSONEncoder):
  20. """
  21. Use https://docs.python.org/3/library/json.html to do serialization
  22. The serializer should serialize the following messages:
  23. _messages.Message
  24. _messages.Notification
  25. _messages.ApnsConfig
  26. _messages.WebPushConfig
  27. _messages.WebPushNotification
  28. _messages.WebPushNotificationAction
  29. _messages.WebPushHMSOptions
  30. _messages.AndroidConfig
  31. _messages.AndroidNotification
  32. _messages.AndroidClickAction
  33. _messages.BadgeNotification
  34. """
  35. def default(self, message):
  36. """
  37. :param message: The push message
  38. :return: formatted push messages
  39. """
  40. result = {
  41. 'data': message.data,
  42. 'notification': MessageSerializer.encode_notification(message.notification),
  43. 'android': MessageSerializer.encode_android_config(message.android),
  44. 'apns': MessageSerializer.encode_apns_config(message.apns),
  45. 'webpush': MessageSerializer.encode_webpush_config(message.web_push),
  46. 'token': message.token,
  47. 'topic': message.topic,
  48. 'condition': message.condition
  49. }
  50. result = MessageSerializer.remove_null_values(result)
  51. return result
  52. @classmethod
  53. def remove_null_values(cls, dict_value):
  54. return {k: v for k, v in dict_value.items() if v not in [None, [], {}]}
  55. @classmethod
  56. def encode_notification(cls, notification):
  57. """
  58. An example:
  59. {
  60. "title":"Big News",
  61. "body":"This is a Big News!",
  62. "image":"https://res.vmallres.com/pimages//common/config/logo/SXppnESYv4K11DBxDFc2_0.png"
  63. }
  64. :param notification:
  65. :return:
  66. """
  67. if notification is None:
  68. return None
  69. if not isinstance(notification, _messages.Notification):
  70. raise ValueError('Message.notification must be an instance of Notification class.')
  71. result = {
  72. 'title': notification.title,
  73. 'body': notification.body,
  74. 'image': notification.image
  75. }
  76. return cls.remove_null_values(result)
  77. @classmethod
  78. def encode_android_config(cls, android_config):
  79. """
  80. An example:
  81. {
  82. "android":{
  83. "collapse_key":-1,
  84. "urgency":"HIGH",
  85. "ttl":"1448s",
  86. "bi_tag":"Trump",
  87. "fast_app_target":1,
  88. "notification": {}
  89. }
  90. :param android_config:
  91. :return:
  92. """
  93. if android_config is None:
  94. return None
  95. if not isinstance(android_config, _messages.AndroidConfig):
  96. raise ValueError('Message.android must be an instance of AndroidConfig class.')
  97. result = {
  98. 'collapse_key': android_config.collapse_key,
  99. 'urgency': android_config.urgency,
  100. 'ttl': android_config.ttl,
  101. 'bi_tag': android_config.bi_tag,
  102. 'fast_app_target': android_config.fast_app_target,
  103. 'data': android_config.data,
  104. 'notification': MessageSerializer.encode_android_notification(android_config.notification)
  105. }
  106. return cls.remove_null_values(result)
  107. @classmethod
  108. def encode_android_notification(cls, notification):
  109. """
  110. "notification":{
  111. "title":"Noti in Noti title",
  112. "body":"Noti in Noti body",
  113. "icon":"https://res.vmallres.com/pimages//common/config/logo/SXppnESYv4K11DBxDFc2.png",
  114. "color":"#AACCDD",
  115. "default_sound":true,
  116. "tag":"tagBoom",
  117. "importance":"PRIORITY_HIGH",
  118. "click_action":{
  119. "type":2,
  120. "url":"https://www.huawei.com"
  121. },
  122. "body_loc_key":"M.String.body",
  123. "body_loc_args":[
  124. "Boy",
  125. "Dog"
  126. ],
  127. "title_loc_key":"M.String.title",
  128. "title_loc_args":[
  129. "Girl",
  130. "Cat"
  131. ],
  132. "channel_id":"RingRing",
  133. "notify_summary":"Some Summary",
  134. "style":2,
  135. "big_title":"Big Boom Title",
  136. "big_body":"Big Boom Body",
  137. "notify_id":486,
  138. "group":"Espace",
  139. "badge":{
  140. "add_num":99,
  141. "set_num":99,
  142. "class":"Classic"
  143. },
  144. "ticker":"i am a ticker",
  145. "auto_cancel":false,
  146. "when":"2019-11-05",
  147. "use_default_vibrate":true,
  148. "use_default_light":false,
  149. "visibility":"PUBLIC",
  150. "vibrate_config":["1.5","2","3"],
  151. "light_settings":{
  152. "color":{
  153. "alpha":0,
  154. "red":0,
  155. "green":1,
  156. "blue":1
  157. },
  158. "light_on_duration":"3.5",
  159. "light_off_duration":"5S"
  160. },
  161. "foreground_show":true
  162. }
  163. :param notification:
  164. :return:
  165. """
  166. if notification is None:
  167. return None
  168. if not isinstance(notification, _messages.AndroidNotification):
  169. raise ValueError('Message.AndroidConfig.notification must be an instance of AndroidNotification class.')
  170. result = {
  171. "title": notification.title,
  172. "body": notification.body,
  173. "icon": notification.icon,
  174. "color": notification.color,
  175. "sound": notification.sound,
  176. "default_sound": notification.default_sound,
  177. "tag": notification.tag,
  178. "importance": notification.importance,
  179. "multi_lang_key": notification.multi_lang_key,
  180. "click_action": MessageSerializer.encode_android_click_action(notification.click_action),
  181. "body_loc_key": notification.body_loc_key,
  182. "body_loc_args": notification.body_loc_args,
  183. "title_loc_key": notification.title_loc_key,
  184. "title_loc_args": notification.title_loc_args,
  185. "channel_id": notification.channel_id,
  186. "notify_summary": notification.notify_summary,
  187. "image": notification.image,
  188. "style": notification.style,
  189. "big_title": notification.big_title,
  190. "big_body": notification.big_body,
  191. "notify_id": notification.notify_id,
  192. "group": notification.group,
  193. "badge": MessageSerializer.encode_android_badge(notification.badge),
  194. "ticker": notification.ticker,
  195. "auto_cancel": notification.auto_cancel,
  196. "when": notification.when,
  197. "use_default_vibrate": notification.use_default_vibrate,
  198. "use_default_light": notification.use_default_light,
  199. "visibility": notification.visibility,
  200. "vibrate_config": notification.vibrate_config,
  201. "light_settings": MessageSerializer.encode_android_light_settings(notification.light_settings),
  202. "foreground_show": notification.foreground_show
  203. }
  204. result = cls.remove_null_values(result)
  205. return result
  206. @classmethod
  207. def encode_android_click_action(cls, click_action):
  208. """
  209. "click_action":{
  210. "type":2,
  211. "url":"https://www.huawei.com"
  212. }
  213. "click_action":{
  214. "type":1,
  215. "intent":"https://www.huawei.com",
  216. "action":""
  217. }
  218. :param click_action: _messages.AndroidClickAction
  219. :return:
  220. """
  221. if click_action is None:
  222. return None
  223. if not isinstance(click_action, _messages.AndroidClickAction):
  224. raise ValueError('Message.AndroidConfig.AndroidNotification.click_action must be an instance\
  225. of AndroidClickAction class.')
  226. result = {
  227. "type": click_action.action_type,
  228. "intent": click_action.intent,
  229. "url": click_action.url,
  230. "action": click_action.action
  231. }
  232. result = cls.remove_null_values(result)
  233. return result
  234. @classmethod
  235. def encode_android_badge(cls, badge):
  236. """
  237. refer to: _messages.AndroidBadgeNotification
  238. "badge":{
  239. "add_num":99,
  240. "set_num":99,
  241. "class":"Classic"
  242. }
  243. :param badge:
  244. :return:
  245. """
  246. if badge is None:
  247. return None
  248. if not isinstance(badge, _messages.AndroidBadgeNotification):
  249. raise ValueError('Message.AndroidConfig.AndroidNotification.badge must be an instance\
  250. of AndroidBadgeNotification class.')
  251. result = {
  252. "add_num": badge.add_num,
  253. "set_num": badge.set_num,
  254. "class": badge.clazz
  255. }
  256. result = cls.remove_null_values(result)
  257. return result
  258. @classmethod
  259. def encode_android_light_settings(cls, android_light_settings):
  260. """
  261. refer to: _messages.AndroidLightSettings
  262. "light_settings":{
  263. "color":{
  264. "alpha":0,
  265. "red":0,
  266. "green":1,
  267. "blue":1
  268. },
  269. "light_on_duration":"3.5",
  270. "light_off_duration":"5S"
  271. }
  272. :param android_light_settings: _messages.AndroidLightSettings
  273. :return:
  274. """
  275. if android_light_settings is None:
  276. return None
  277. if not isinstance(android_light_settings, _messages.AndroidLightSettings):
  278. raise ValueError('Message.AndroidConfig.AndroidNotification.android_light_settings must be an instance\
  279. of AndroidLightSettings class.')
  280. result = {
  281. "color": MessageSerializer.encode_android_light_settings_color(android_light_settings.color),
  282. "light_on_duration": android_light_settings.light_on_duration,
  283. "light_off_duration": android_light_settings.light_off_duration
  284. }
  285. result = cls.remove_null_values(result)
  286. return result
  287. @classmethod
  288. def encode_android_light_settings_color(cls, color):
  289. """
  290. "color":{
  291. "alpha":0,
  292. "red":0,
  293. "green":1,
  294. "blue":1
  295. }
  296. :param color: _messages.AndroidLightSettingsColor
  297. :return:
  298. """
  299. if color is None:
  300. return None
  301. if not isinstance(color, _messages.AndroidLightSettingsColor):
  302. raise ValueError('Message.AndroidConfig.AndroidNotification.android_light_settings.color must be an instance\
  303. of AndroidLightSettingsColor class.')
  304. result = {
  305. "alpha": color.alpha,
  306. "red": color.red,
  307. "green": color.green,
  308. "blue": color.blue
  309. }
  310. result = cls.remove_null_values(result)
  311. return result
  312. @classmethod
  313. def encode_webpush_config(cls, webpush_config):
  314. """
  315. "webpush":{
  316. "headers":{
  317. ...
  318. },
  319. "notification":{
  320. ...
  321. },
  322. "hms_options":{
  323. ...
  324. }
  325. }
  326. :param webpush_config: refer to _messages.WebPushConfig
  327. :return:
  328. """
  329. if webpush_config is None:
  330. return None
  331. if not isinstance(webpush_config, _messages.WebPushConfig):
  332. raise ValueError('Message.webpush must be an instance of WebPushConfig class.')
  333. result = {
  334. "headers": MessageSerializer.encode_webpush_config_headers(webpush_config.headers),
  335. "notification": MessageSerializer.encode_webpush_config_notification(webpush_config.notification),
  336. "hms_options": MessageSerializer.encode_webpush_config_hms_options(webpush_config.hms_options),
  337. }
  338. result = cls.remove_null_values(result)
  339. return result
  340. @classmethod
  341. def encode_webpush_config_headers(cls, webpush_headers):
  342. """
  343. "headers":{
  344. "ttl":"990",
  345. "urgency":"very-low",
  346. "topic":"12313ceshi"
  347. }
  348. :param webpush_headers: _messages.WebPushHeader
  349. :return:
  350. """
  351. if webpush_headers is None:
  352. return None
  353. if not isinstance(webpush_headers, _messages.WebPushHeader):
  354. raise ValueError('Message.webpush.headers must be an instance of WebPushHeader class.')
  355. result = {
  356. "ttl": webpush_headers.ttl,
  357. "urgency": webpush_headers.urgency,
  358. "topic": webpush_headers.topic,
  359. }
  360. result = cls.remove_null_values(result)
  361. return result
  362. @classmethod
  363. def encode_webpush_config_notification(cls, webpush_notification):
  364. """
  365. "notification":{
  366. "title":"notication string",
  367. "body":"web push body",
  368. "actions":[
  369. {
  370. "action":"",
  371. "icon":"https://res.vmallres.com/pimages//common/config/logo/SXppnESYv4K11DBxDFc2.png",
  372. "title":"string"
  373. }
  374. ],
  375. "badge":"string",
  376. "dir":"auto",
  377. "icon":"https://res.vmallres.com/pimages//common/config/logo/SXppnESYv4K11DBxDFc2.png",
  378. "image":"string",
  379. "lang":"string",
  380. "renotify":true,
  381. "requireInteraction":true,
  382. "silent":true,
  383. "tag":"string",
  384. "timestamp":1545201266,
  385. "vibrate":[1,2,3]
  386. }
  387. :param webpush_notification: refer to _messages.WebPushNotification
  388. :return:
  389. """
  390. if webpush_notification is None:
  391. return None
  392. if not isinstance(webpush_notification, _messages.WebPushNotification):
  393. raise ValueError('Message.webpush.notification must be an instance of WebPushNotification class.')
  394. result = {
  395. "title": webpush_notification.title,
  396. "body": webpush_notification.body,
  397. "actions": [MessageSerializer.encode_webpush_notification_action(_)
  398. for _ in webpush_notification.actions],
  399. "badge": webpush_notification.badge,
  400. "dir": webpush_notification.dir,
  401. "icon": webpush_notification.icon,
  402. "image": webpush_notification.image,
  403. "lang": webpush_notification.lang,
  404. "renotify": webpush_notification.renotify,
  405. "require_interaction": webpush_notification.require_interaction,
  406. "silent": webpush_notification.silent,
  407. "tag": webpush_notification.tag,
  408. "timestamp": webpush_notification.timestamp,
  409. "vibrate": webpush_notification.vibrate
  410. }
  411. result = cls.remove_null_values(result)
  412. return result
  413. @classmethod
  414. def encode_webpush_notification_action(cls, webpush_notification_action):
  415. """
  416. "actions":[
  417. {
  418. "action":"",
  419. "icon":"https://res.vmallres.com/pimages//common/config/logo/SXppnESYv4K11DBxDFc2.png",
  420. "title":"string"
  421. }
  422. ],
  423. :param webpush_notification_action: refer to _messages.WebPushNotificationAction
  424. :return:
  425. """
  426. if webpush_notification_action is None:
  427. return None
  428. if not isinstance(webpush_notification_action, _messages.WebPushNotificationAction):
  429. raise ValueError('Message.webpush.notification.action must be an instance of \
  430. WebPushNotificationAction class.')
  431. result = {
  432. "action": webpush_notification_action.action,
  433. "icon": webpush_notification_action.icon,
  434. "title": webpush_notification_action.title
  435. }
  436. result = cls.remove_null_values(result)
  437. return result
  438. @classmethod
  439. def encode_webpush_config_hms_options(cls, webpush_hms_options):
  440. """
  441. "hms_options":{
  442. "link":"https://www.huawei.com/"
  443. }
  444. :param webpush_hms_options: refer to _messages.WebPushHMSOptions
  445. :return:
  446. """
  447. if webpush_hms_options is None:
  448. return None
  449. if not isinstance(webpush_hms_options, _messages.WebPushHMSOptions):
  450. raise ValueError('Message.webpush.hmsoptions must be an instance of \
  451. WebPushHMSOptions class.')
  452. result = {
  453. "link": webpush_hms_options.link
  454. }
  455. result = cls.remove_null_values(result)
  456. return result
  457. @classmethod
  458. def encode_apns_config(cls, apns_config):
  459. """
  460. Encode APNs config into JSON
  461. :param apns_config:
  462. :return:
  463. """
  464. if apns_config is None:
  465. return None
  466. if not isinstance(apns_config, _messages.APNsConfig):
  467. raise ValueError('Message.apns_config must be an instance of _messages.APNsConfig class.')
  468. result = {
  469. 'headers': apns_config.headers,
  470. 'payload': cls.encode_apns_payload(apns_config.payload),
  471. 'hms_options': cls.encode_apns_hms_options(apns_config.apns_hms_options)
  472. }
  473. return cls.remove_null_values(result)
  474. @classmethod
  475. def encode_apns_payload(cls, apns_payload):
  476. """Encodes an ``APNSPayload`` instance into JSON."""
  477. if apns_payload is None:
  478. return None
  479. if not isinstance(apns_payload, _messages.APNsPayload):
  480. raise ValueError('APNSConfig.payload must be an instance of _messages.APNsPayload class.')
  481. result = {
  482. 'aps': cls.encode_apns_payload_aps(apns_payload.aps)
  483. }
  484. for key, value in apns_payload.custom_data.items():
  485. result[key] = value
  486. return cls.remove_null_values(result)
  487. @classmethod
  488. def encode_apns_payload_aps(cls, apns_payload_aps):
  489. """Encodes an ``Aps`` instance into JSON."""
  490. if not isinstance(apns_payload_aps, _messages.APNsAps):
  491. raise ValueError('APNSPayload.aps must be an instance of _messages.APNsAps class.')
  492. result = {
  493. 'alert': cls.encode_apns_payload_alert(apns_payload_aps.alert),
  494. 'badge': apns_payload_aps.badge,
  495. 'sound': apns_payload_aps.sound,
  496. 'category': apns_payload_aps.category,
  497. 'thread-id': apns_payload_aps.thread_id
  498. }
  499. if apns_payload_aps.content_available is True:
  500. result['content-available'] = 1
  501. if apns_payload_aps.mutable_content is True:
  502. result['mutable-content'] = 1
  503. if apns_payload_aps.custom_data is not None:
  504. if not isinstance(apns_payload_aps.custom_data, dict):
  505. raise ValueError('Aps.custom_data must be a dict.')
  506. for key, val in apns_payload_aps.custom_data.items():
  507. if key in result:
  508. raise ValueError('Multiple specifications for {0} in Aps.'.format(key))
  509. result[key] = val
  510. return cls.remove_null_values(result)
  511. @classmethod
  512. def encode_apns_payload_alert(cls, apns_payload_alert):
  513. """Encodes an ``ApsAlert`` instance into JSON."""
  514. if apns_payload_alert is None:
  515. return None
  516. if isinstance(apns_payload_alert, six.string_types):
  517. return apns_payload_alert
  518. if not isinstance(apns_payload_alert, _messages.APNsAlert):
  519. raise ValueError('Aps.alert must be a string or an instance of _messages.APNsAlert class.')
  520. result = {
  521. 'title': apns_payload_alert.title,
  522. 'body': apns_payload_alert.body,
  523. 'title-loc-key': apns_payload_alert.title_loc_key,
  524. 'title-loc-args': apns_payload_alert.title_loc_args,
  525. 'loc-key': apns_payload_alert.loc_key,
  526. 'loc-args': apns_payload_alert.loc_args,
  527. 'action-loc-key': apns_payload_alert.action_loc_key,
  528. 'launch-image': apns_payload_alert.launch_image
  529. }
  530. if result.get('loc-args') and not result.get('loc-key'):
  531. raise ValueError(
  532. 'ApsAlert.loc_key is required when specifying loc_args.')
  533. if result.get('title-loc-args') and not result.get('title-loc-key'):
  534. raise ValueError(
  535. 'ApsAlert.title_loc_key is required when specifying title_loc_args.')
  536. if apns_payload_alert.custom_data is not None:
  537. if not isinstance(apns_payload_alert.custom_data, dict):
  538. raise ValueError('ApsAlert.custom_data must be a dict.')
  539. for key, val in apns_payload_alert.custom_data.items():
  540. result[key] = val
  541. return cls.remove_null_values(result)
  542. @classmethod
  543. def encode_apns_hms_options(cls, apns_hms_options):
  544. """
  545. :param apns_hms_options:
  546. """
  547. if apns_hms_options is None:
  548. return None
  549. if not isinstance(apns_hms_options, _messages.APNsHMSOptions):
  550. raise ValueError('Aps.alert must be a string or an instance of _messages.APNsHMSOptions class.')
  551. result = {
  552. 'target_user_type': apns_hms_options.target_user_type,
  553. }
  554. return cls.remove_null_values(result)