_message_serializer.py 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614
  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. 'category': android_config.category
  106. }
  107. return cls.remove_null_values(result)
  108. @classmethod
  109. def encode_android_notification(cls, notification):
  110. """
  111. "notification":{
  112. "title":"Noti in Noti title",
  113. "body":"Noti in Noti body",
  114. "icon":"https://res.vmallres.com/pimages//common/config/logo/SXppnESYv4K11DBxDFc2.png",
  115. "color":"#AACCDD",
  116. "default_sound":true,
  117. "tag":"tagBoom",
  118. "importance":"PRIORITY_HIGH",
  119. "click_action":{
  120. "type":2,
  121. "url":"https://www.huawei.com"
  122. },
  123. "body_loc_key":"M.String.body",
  124. "body_loc_args":[
  125. "Boy",
  126. "Dog"
  127. ],
  128. "title_loc_key":"M.String.title",
  129. "title_loc_args":[
  130. "Girl",
  131. "Cat"
  132. ],
  133. "channel_id":"RingRing",
  134. "notify_summary":"Some Summary",
  135. "style":2,
  136. "big_title":"Big Boom Title",
  137. "big_body":"Big Boom Body",
  138. "notify_id":486,
  139. "group":"Espace",
  140. "badge":{
  141. "add_num":99,
  142. "set_num":99,
  143. "class":"Classic"
  144. },
  145. "ticker":"i am a ticker",
  146. "auto_cancel":false,
  147. "when":"2019-11-05",
  148. "use_default_vibrate":true,
  149. "use_default_light":false,
  150. "visibility":"PUBLIC",
  151. "vibrate_config":["1.5","2","3"],
  152. "light_settings":{
  153. "color":{
  154. "alpha":0,
  155. "red":0,
  156. "green":1,
  157. "blue":1
  158. },
  159. "light_on_duration":"3.5",
  160. "light_off_duration":"5S"
  161. },
  162. "foreground_show":true
  163. }
  164. :param notification:
  165. :return:
  166. """
  167. if notification is None:
  168. return None
  169. if not isinstance(notification, _messages.AndroidNotification):
  170. raise ValueError('Message.AndroidConfig.notification must be an instance of AndroidNotification class.')
  171. result = {
  172. "title": notification.title,
  173. "body": notification.body,
  174. "icon": notification.icon,
  175. "color": notification.color,
  176. "sound": notification.sound,
  177. "default_sound": notification.default_sound,
  178. "tag": notification.tag,
  179. "importance": notification.importance,
  180. "multi_lang_key": notification.multi_lang_key,
  181. "click_action": MessageSerializer.encode_android_click_action(notification.click_action),
  182. "body_loc_key": notification.body_loc_key,
  183. "body_loc_args": notification.body_loc_args,
  184. "title_loc_key": notification.title_loc_key,
  185. "title_loc_args": notification.title_loc_args,
  186. "channel_id": notification.channel_id,
  187. "notify_summary": notification.notify_summary,
  188. "image": notification.image,
  189. "style": notification.style,
  190. "big_title": notification.big_title,
  191. "big_body": notification.big_body,
  192. "notify_id": notification.notify_id,
  193. "group": notification.group,
  194. "badge": MessageSerializer.encode_android_badge(notification.badge),
  195. "ticker": notification.ticker,
  196. "auto_cancel": notification.auto_cancel,
  197. "when": notification.when,
  198. "use_default_vibrate": notification.use_default_vibrate,
  199. "use_default_light": notification.use_default_light,
  200. "visibility": notification.visibility,
  201. "vibrate_config": notification.vibrate_config,
  202. "light_settings": MessageSerializer.encode_android_light_settings(notification.light_settings),
  203. "foreground_show": notification.foreground_show
  204. }
  205. result = cls.remove_null_values(result)
  206. return result
  207. @classmethod
  208. def encode_android_click_action(cls, click_action):
  209. """
  210. "click_action":{
  211. "type":2,
  212. "url":"https://www.huawei.com"
  213. }
  214. "click_action":{
  215. "type":1,
  216. "intent":"https://www.huawei.com",
  217. "action":""
  218. }
  219. :param click_action: _messages.AndroidClickAction
  220. :return:
  221. """
  222. if click_action is None:
  223. return None
  224. if not isinstance(click_action, _messages.AndroidClickAction):
  225. raise ValueError('Message.AndroidConfig.AndroidNotification.click_action must be an instance\
  226. of AndroidClickAction class.')
  227. result = {
  228. "type": click_action.action_type,
  229. "intent": click_action.intent,
  230. "url": click_action.url,
  231. "action": click_action.action
  232. }
  233. result = cls.remove_null_values(result)
  234. return result
  235. @classmethod
  236. def encode_android_badge(cls, badge):
  237. """
  238. refer to: _messages.AndroidBadgeNotification
  239. "badge":{
  240. "add_num":99,
  241. "set_num":99,
  242. "class":"Classic"
  243. }
  244. :param badge:
  245. :return:
  246. """
  247. if badge is None:
  248. return None
  249. if not isinstance(badge, _messages.AndroidBadgeNotification):
  250. raise ValueError('Message.AndroidConfig.AndroidNotification.badge must be an instance\
  251. of AndroidBadgeNotification class.')
  252. result = {
  253. "add_num": badge.add_num,
  254. "set_num": badge.set_num,
  255. "class": badge.clazz
  256. }
  257. result = cls.remove_null_values(result)
  258. return result
  259. @classmethod
  260. def encode_android_light_settings(cls, android_light_settings):
  261. """
  262. refer to: _messages.AndroidLightSettings
  263. "light_settings":{
  264. "color":{
  265. "alpha":0,
  266. "red":0,
  267. "green":1,
  268. "blue":1
  269. },
  270. "light_on_duration":"3.5",
  271. "light_off_duration":"5S"
  272. }
  273. :param android_light_settings: _messages.AndroidLightSettings
  274. :return:
  275. """
  276. if android_light_settings is None:
  277. return None
  278. if not isinstance(android_light_settings, _messages.AndroidLightSettings):
  279. raise ValueError('Message.AndroidConfig.AndroidNotification.android_light_settings must be an instance\
  280. of AndroidLightSettings class.')
  281. result = {
  282. "color": MessageSerializer.encode_android_light_settings_color(android_light_settings.color),
  283. "light_on_duration": android_light_settings.light_on_duration,
  284. "light_off_duration": android_light_settings.light_off_duration
  285. }
  286. result = cls.remove_null_values(result)
  287. return result
  288. @classmethod
  289. def encode_android_light_settings_color(cls, color):
  290. """
  291. "color":{
  292. "alpha":0,
  293. "red":0,
  294. "green":1,
  295. "blue":1
  296. }
  297. :param color: _messages.AndroidLightSettingsColor
  298. :return:
  299. """
  300. if color is None:
  301. return None
  302. if not isinstance(color, _messages.AndroidLightSettingsColor):
  303. raise ValueError('Message.AndroidConfig.AndroidNotification.android_light_settings.color must be an instance\
  304. of AndroidLightSettingsColor class.')
  305. result = {
  306. "alpha": color.alpha,
  307. "red": color.red,
  308. "green": color.green,
  309. "blue": color.blue
  310. }
  311. result = cls.remove_null_values(result)
  312. return result
  313. @classmethod
  314. def encode_webpush_config(cls, webpush_config):
  315. """
  316. "webpush":{
  317. "headers":{
  318. ...
  319. },
  320. "notification":{
  321. ...
  322. },
  323. "hms_options":{
  324. ...
  325. }
  326. }
  327. :param webpush_config: refer to _messages.WebPushConfig
  328. :return:
  329. """
  330. if webpush_config is None:
  331. return None
  332. if not isinstance(webpush_config, _messages.WebPushConfig):
  333. raise ValueError('Message.webpush must be an instance of WebPushConfig class.')
  334. result = {
  335. "headers": MessageSerializer.encode_webpush_config_headers(webpush_config.headers),
  336. "notification": MessageSerializer.encode_webpush_config_notification(webpush_config.notification),
  337. "hms_options": MessageSerializer.encode_webpush_config_hms_options(webpush_config.hms_options),
  338. }
  339. result = cls.remove_null_values(result)
  340. return result
  341. @classmethod
  342. def encode_webpush_config_headers(cls, webpush_headers):
  343. """
  344. "headers":{
  345. "ttl":"990",
  346. "urgency":"very-low",
  347. "topic":"12313ceshi"
  348. }
  349. :param webpush_headers: _messages.WebPushHeader
  350. :return:
  351. """
  352. if webpush_headers is None:
  353. return None
  354. if not isinstance(webpush_headers, _messages.WebPushHeader):
  355. raise ValueError('Message.webpush.headers must be an instance of WebPushHeader class.')
  356. result = {
  357. "ttl": webpush_headers.ttl,
  358. "urgency": webpush_headers.urgency,
  359. "topic": webpush_headers.topic,
  360. }
  361. result = cls.remove_null_values(result)
  362. return result
  363. @classmethod
  364. def encode_webpush_config_notification(cls, webpush_notification):
  365. """
  366. "notification":{
  367. "title":"notication string",
  368. "body":"web push body",
  369. "actions":[
  370. {
  371. "action":"",
  372. "icon":"https://res.vmallres.com/pimages//common/config/logo/SXppnESYv4K11DBxDFc2.png",
  373. "title":"string"
  374. }
  375. ],
  376. "badge":"string",
  377. "dir":"auto",
  378. "icon":"https://res.vmallres.com/pimages//common/config/logo/SXppnESYv4K11DBxDFc2.png",
  379. "image":"string",
  380. "lang":"string",
  381. "renotify":true,
  382. "requireInteraction":true,
  383. "silent":true,
  384. "tag":"string",
  385. "timestamp":1545201266,
  386. "vibrate":[1,2,3]
  387. }
  388. :param webpush_notification: refer to _messages.WebPushNotification
  389. :return:
  390. """
  391. if webpush_notification is None:
  392. return None
  393. if not isinstance(webpush_notification, _messages.WebPushNotification):
  394. raise ValueError('Message.webpush.notification must be an instance of WebPushNotification class.')
  395. result = {
  396. "title": webpush_notification.title,
  397. "body": webpush_notification.body,
  398. "actions": [MessageSerializer.encode_webpush_notification_action(_)
  399. for _ in webpush_notification.actions],
  400. "badge": webpush_notification.badge,
  401. "dir": webpush_notification.dir,
  402. "icon": webpush_notification.icon,
  403. "image": webpush_notification.image,
  404. "lang": webpush_notification.lang,
  405. "renotify": webpush_notification.renotify,
  406. "require_interaction": webpush_notification.require_interaction,
  407. "silent": webpush_notification.silent,
  408. "tag": webpush_notification.tag,
  409. "timestamp": webpush_notification.timestamp,
  410. "vibrate": webpush_notification.vibrate
  411. }
  412. result = cls.remove_null_values(result)
  413. return result
  414. @classmethod
  415. def encode_webpush_notification_action(cls, webpush_notification_action):
  416. """
  417. "actions":[
  418. {
  419. "action":"",
  420. "icon":"https://res.vmallres.com/pimages//common/config/logo/SXppnESYv4K11DBxDFc2.png",
  421. "title":"string"
  422. }
  423. ],
  424. :param webpush_notification_action: refer to _messages.WebPushNotificationAction
  425. :return:
  426. """
  427. if webpush_notification_action is None:
  428. return None
  429. if not isinstance(webpush_notification_action, _messages.WebPushNotificationAction):
  430. raise ValueError('Message.webpush.notification.action must be an instance of \
  431. WebPushNotificationAction class.')
  432. result = {
  433. "action": webpush_notification_action.action,
  434. "icon": webpush_notification_action.icon,
  435. "title": webpush_notification_action.title
  436. }
  437. result = cls.remove_null_values(result)
  438. return result
  439. @classmethod
  440. def encode_webpush_config_hms_options(cls, webpush_hms_options):
  441. """
  442. "hms_options":{
  443. "link":"https://www.huawei.com/"
  444. }
  445. :param webpush_hms_options: refer to _messages.WebPushHMSOptions
  446. :return:
  447. """
  448. if webpush_hms_options is None:
  449. return None
  450. if not isinstance(webpush_hms_options, _messages.WebPushHMSOptions):
  451. raise ValueError('Message.webpush.hmsoptions must be an instance of \
  452. WebPushHMSOptions class.')
  453. result = {
  454. "link": webpush_hms_options.link
  455. }
  456. result = cls.remove_null_values(result)
  457. return result
  458. @classmethod
  459. def encode_apns_config(cls, apns_config):
  460. """
  461. Encode APNs config into JSON
  462. :param apns_config:
  463. :return:
  464. """
  465. if apns_config is None:
  466. return None
  467. if not isinstance(apns_config, _messages.APNsConfig):
  468. raise ValueError('Message.apns_config must be an instance of _messages.APNsConfig class.')
  469. result = {
  470. 'headers': apns_config.headers,
  471. 'payload': cls.encode_apns_payload(apns_config.payload),
  472. 'hms_options': cls.encode_apns_hms_options(apns_config.apns_hms_options)
  473. }
  474. return cls.remove_null_values(result)
  475. @classmethod
  476. def encode_apns_payload(cls, apns_payload):
  477. """Encodes an ``APNSPayload`` instance into JSON."""
  478. if apns_payload is None:
  479. return None
  480. if not isinstance(apns_payload, _messages.APNsPayload):
  481. raise ValueError('APNSConfig.payload must be an instance of _messages.APNsPayload class.')
  482. result = {
  483. 'aps': cls.encode_apns_payload_aps(apns_payload.aps)
  484. }
  485. for key, value in apns_payload.custom_data.items():
  486. result[key] = value
  487. return cls.remove_null_values(result)
  488. @classmethod
  489. def encode_apns_payload_aps(cls, apns_payload_aps):
  490. """Encodes an ``Aps`` instance into JSON."""
  491. if not isinstance(apns_payload_aps, _messages.APNsAps):
  492. raise ValueError('APNSPayload.aps must be an instance of _messages.APNsAps class.')
  493. result = {
  494. 'alert': cls.encode_apns_payload_alert(apns_payload_aps.alert),
  495. 'badge': apns_payload_aps.badge,
  496. 'sound': apns_payload_aps.sound,
  497. 'category': apns_payload_aps.category,
  498. 'thread-id': apns_payload_aps.thread_id
  499. }
  500. if apns_payload_aps.content_available is True:
  501. result['content-available'] = 1
  502. if apns_payload_aps.mutable_content is True:
  503. result['mutable-content'] = 1
  504. if apns_payload_aps.custom_data is not None:
  505. if not isinstance(apns_payload_aps.custom_data, dict):
  506. raise ValueError('Aps.custom_data must be a dict.')
  507. for key, val in apns_payload_aps.custom_data.items():
  508. if key in result:
  509. raise ValueError('Multiple specifications for {0} in Aps.'.format(key))
  510. result[key] = val
  511. return cls.remove_null_values(result)
  512. @classmethod
  513. def encode_apns_payload_alert(cls, apns_payload_alert):
  514. """Encodes an ``ApsAlert`` instance into JSON."""
  515. if apns_payload_alert is None:
  516. return None
  517. if isinstance(apns_payload_alert, six.string_types):
  518. return apns_payload_alert
  519. if not isinstance(apns_payload_alert, _messages.APNsAlert):
  520. raise ValueError('Aps.alert must be a string or an instance of _messages.APNsAlert class.')
  521. result = {
  522. 'title': apns_payload_alert.title,
  523. 'body': apns_payload_alert.body,
  524. 'title-loc-key': apns_payload_alert.title_loc_key,
  525. 'title-loc-args': apns_payload_alert.title_loc_args,
  526. 'loc-key': apns_payload_alert.loc_key,
  527. 'loc-args': apns_payload_alert.loc_args,
  528. 'action-loc-key': apns_payload_alert.action_loc_key,
  529. 'launch-image': apns_payload_alert.launch_image
  530. }
  531. if result.get('loc-args') and not result.get('loc-key'):
  532. raise ValueError(
  533. 'ApsAlert.loc_key is required when specifying loc_args.')
  534. if result.get('title-loc-args') and not result.get('title-loc-key'):
  535. raise ValueError(
  536. 'ApsAlert.title_loc_key is required when specifying title_loc_args.')
  537. if apns_payload_alert.custom_data is not None:
  538. if not isinstance(apns_payload_alert.custom_data, dict):
  539. raise ValueError('ApsAlert.custom_data must be a dict.')
  540. for key, val in apns_payload_alert.custom_data.items():
  541. result[key] = val
  542. return cls.remove_null_values(result)
  543. @classmethod
  544. def encode_apns_hms_options(cls, apns_hms_options):
  545. """
  546. :param apns_hms_options:
  547. """
  548. if apns_hms_options is None:
  549. return None
  550. if not isinstance(apns_hms_options, _messages.APNsHMSOptions):
  551. raise ValueError('Aps.alert must be a string or an instance of _messages.APNsHMSOptions class.')
  552. result = {
  553. 'target_user_type': apns_hms_options.target_user_type,
  554. }
  555. return cls.remove_null_values(result)