jpush 0.4.0

集成极光App推送
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
use serde::de::{SeqAccess, Visitor};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use std::collections::HashMap;
use std::fmt;
use std::str::FromStr;

#[derive(Debug, Serialize, Deserialize)]
pub struct JPushMessage {
    /// ###### 推送平台设置
    pub platform: Platform,
    /// ###### 推送设备指定
    pub audience: Audience,
    /// ###### 推送内容
    /// - 通知内容体,是被推送到客户端的内容。
    /// - 与 message 一起二者必须有其一,可以二者并存。
    #[serde(skip_serializing_if = "Option::is_none")]
    pub notification: Option<Notification>,
    /// ###### 消息内容
    /// - 消息内容体,是被推送到客户端的内容。
    /// - 与 notification 一起二者必须有其一,可以二者并存。
    #[serde(skip_serializing_if = "Option::is_none")]
    pub message: Option<Message>,
    /// ###### 推送参数
    #[serde(skip_serializing_if = "Option::is_none")]
    pub options: Option<MessageOptions>,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct Intent {
    pub url: String,
}

impl Default for Intent {
    fn default() -> Self {
        Intent {
            url: "intent:#Intent;action=android.intent.action.MAIN;end".to_string(),
        }
    }
}

#[derive(Debug)]
pub enum Audience {
    All,
    Custom(AudienceCustom),
}

impl Audience {
    pub fn default() -> Self {
        Audience::All
    }
    pub fn from_alias(alias: Vec<u64>) -> Self {
        Audience::Custom(AudienceCustom::from_alias(alias))
    }
}

impl Serialize for Audience {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        match self {
            Audience::All => "all".serialize(serializer),
            Audience::Custom(c) => c.serialize(serializer)
        }
    }
}

impl<'de> Deserialize<'de> for Audience {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        // 使用Visitor模式(推荐)
        struct AudienceVisitor;

        impl<'de> Visitor<'de> for AudienceVisitor {
            type Value = Audience;

            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
                formatter.write_str("string or map")
            }

            fn visit_str<E>(self, _: &str) -> Result<Self::Value, E> {
                Ok(Audience::All)
            }

            fn visit_map<A>(self, map: A) -> Result<Self::Value, A::Error>
            where
                A: serde::de::MapAccess<'de>,
            {
                Ok(Audience::Custom(
                    AudienceCustom::deserialize(
                        serde::de::value::MapAccessDeserializer::new(map)
                    )?
                ))
            }
        }

        deserializer.deserialize_any(AudienceVisitor)
    }
}

#[derive(Debug)]
pub enum Platform {
    String(PlatformType),
    VecString(Vec<PlatformType>),
}

impl Default for Platform {
    fn default() -> Self {
        Platform::String(PlatformType::All)
    }
}

impl Serialize for Platform {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        match self {
            Platform::String(s) => s.serialize(serializer),
            Platform::VecString(v) => v.serialize(serializer)
        }
    }
}

impl<'de> Deserialize<'de> for Platform {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        struct PlatformVisitor;

        impl<'de> Visitor<'de> for PlatformVisitor {
            type Value = Platform;

            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
                formatter.write_str("a string or a list of strings")
            }

            fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
            where
                E: serde::de::Error,
            {
                let value = value.parse::<PlatformType>().map_err(|_| E::custom(format!("Invalid platform type: {}", value)))?;
                Ok(Platform::String(value))
            }

            fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
            where
                A: SeqAccess<'de>,
            {
                let mut strings = Vec::new();
                while let Some(value) = seq.next_element()? {
                    strings.push(value);
                }
                Ok(Platform::VecString(strings))
            }
        }

        deserializer.deserialize_any(PlatformVisitor)
    }
}

#[derive(Debug)]
pub enum PlatformType {
    All,
    Android,
    Ios,
    HarmonyOS,
}

impl FromStr for PlatformType {
    type Err = ();

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s.to_lowercase().as_str() {
            "all" => Ok(PlatformType::All),
            "android" => Ok(PlatformType::Android),
            "ios" => Ok(PlatformType::Ios),
            "hmos" => Ok(PlatformType::HarmonyOS),
            _ => Err(()),
        }
    }
}

impl Serialize for PlatformType {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        match self {
            PlatformType::All => serializer.serialize_str("all"),
            PlatformType::Android => serializer.serialize_str("android"),
            PlatformType::Ios => serializer.serialize_str("ios"),
            PlatformType::HarmonyOS => serializer.serialize_str("hmos"),
        }
    }
}

impl<'de> Deserialize<'de> for PlatformType {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        struct PlatformTypeVisitor;

        impl<'de> Visitor<'de> for PlatformTypeVisitor {
            type Value = PlatformType;

            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
                formatter.write_str("a string representing the platform type (all, android, ios)")
            }

            fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
            where
                E: serde::de::Error,
            {
                value.parse().map_err(|_| E::custom(format!("Invalid platform type: {}", value)))
            }
        }

        deserializer.deserialize_str(PlatformTypeVisitor)
    }
}

#[derive(Debug, Default, Serialize, Deserialize)]
pub struct AudienceCustom {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub alias: Option<Vec<String>>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub tag: Option<Vec<String>>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub tag_and: Option<Vec<String>>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub tag_not: Option<Vec<String>>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub registration_id: Option<Vec<String>>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub segment: Option<Vec<String>>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub abtest: Option<Vec<String>>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub live_activity_id: Option<String>,
}

impl AudienceCustom {
    pub fn from_alias(alias: Vec<u64>) -> Self {
        AudienceCustom {
            alias: Some(alias.iter().map(|x| x.to_string()).collect()),
            ..Default::default()
        }
    }
}

#[derive(Debug, Default, Serialize, Deserialize)]
pub struct Notification {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub alert: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub android: Option<AndroidNotification>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub ios: Option<IosNotification>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub hmos: Option<HarmonyOSNotification>,
}

impl Notification {
    pub fn from_alert(title: &str, content: &str) -> Self {
        let mut notification = Notification::default();
        let android_notification = AndroidNotification::from_alert(title, content);
        let ios_notification = IosNotification::from_alert(content);
        let hmos_notification = HarmonyOSNotification::from_alert(content);
        notification.alert = Some(content.to_string());
        notification.android = Some(android_notification);
        notification.ios = Some(ios_notification);
        notification.hmos = Some(hmos_notification);
        notification
    }
}


#[derive(Debug, Default, Serialize, Deserialize)]
pub struct AndroidNotification {
    /// ###### 通知内容
    pub alert: String,
    /// ###### 通知标题
    #[serde(skip_serializing_if = "Option::is_none")]
    pub title: Option<String>,
    /// ###### 通知栏样式 ID
    #[serde(skip_serializing_if = "Option::is_none")]
    pub builder_id: Option<i64>,
    /// - android 通知 channel_id 根据 channel ID 来指定通知栏展示效果,不超过 1000 字节
    /// - options.third_party_channel 下的蔚来、小米、OPPO 和华为厂商参数也有 channel_id 字段,若有填充,则优先使用,若无填充则以本字段定义为准。
    #[serde(skip_serializing_if = "Option::is_none")]
    pub channel_id: Option<String>,
    /// ###### 通知栏消息分类条目: 完全依赖 rom 厂商对 category 的处理策略。
    /// - 说明1:华为从 2023.09.15 开始基于《华为消息分类标准》 对其本地通知进行管控推送,参考:《华为本地通知频次及分类管控通知》 ,此字段值对应华为「本地通知」category取值,开发者通过极光服务发起推送时如果传递了此字段值,请务必按照华为官方要求传递,极光会自动适配华为本地通知importance取值,无需开发者额外处理。
    /// - 说明2:考虑到一次推送包含多个厂商用户的情况,建议此处传递的字段值要和您APP开发代码中创建的channel效果对应(category值一致),最好创建新的channelId,避免曾经已经创建了无法修改。
    /// - 官方category分类取值规则也可参考华为消息分类对应表
    #[serde(skip_serializing_if = "Option::is_none")]
    pub category: Option<String>,
    /// ###### 通知栏展示优先级
    /// - 默认为 0,范围为 -2~2。
    /// - 说明1:华为从 2023.09.15 开始基于[《华为消息分类标准》](https://docs.jiguang.cn/jpush/client/Android/android_channel_id#%E5%8D%8E%E4%B8%BA%E6%B6%88%E6%81%AF%E5%88%86%E7%B1%BB%E8%AF%B4%E6%98%8E) 对其本地通知进行管控推送,参考:[《华为本地通知频次及分类管控通知》](https://developer.huawei.com/consumer/cn/doc/development/hmscore-common-Guides/push_notice_local-0000001615143510) ,开发者通过极光服务发起推送时,如果有传递此字段值,请注意此字段要和 category 同时使用;反之,如果传了category,没传递此值时极光会自动帮您适配处理优先级。
    /// - priority = -2 时,对应华为本地通知 importance 级别为 IMPORTANCE_MIN;priority = 0 时,对应华为本地通知 importance 级别为 IMPORTANCE_DEFAULT。
    /// - 官方消息优先级取值规则也可参考[华为消息分类对应表](https://docs.jiguang.cn/jpush/client/Android/android_channel_id#%E5%8D%8E%E4%B8%BA%E6%B6%88%E6%81%AF%E5%88%86%E7%B1%BB%E8%AF%B4%E6%98%8E)
    /// - 极光取值 -2 ~-1 对应 FCM 取值 normal,极光取值 0~2 对应 FCM 取值 high。
    #[serde(skip_serializing_if = "Option::is_none")]
    pub priority: Option<i64>,
    /// ###### 通知栏样式类型
    /// 用来指定通知栏样式类型,默认为 0。
    /// - 1:bigText
    /// - 2:Inbox
    /// - 3:bigPicture
    #[serde(skip_serializing_if = "Option::is_none")]
    pub style: Option<i64>,
    /// ###### 通知提醒方式
    /// 可选范围为 -1~7 ,默认按照 -1 处理。即0111二进制,左数第二位代表 light,第三位代表 vibrate,第四位代表 sound。0:不生效,1:生效。如:
    /// - Notification.DEFAULT_ALL = -1 ,
    /// - Notification.DEFAULT_SOUND = 1,
    /// - Notification.DEFAULT_VIBRATE = 2,
    /// - Notification.DEFAULT_LIGHTS = 4 的任意 “or” 组合。
    #[serde(skip_serializing_if = "Option::is_none")]
    pub alert_type: Option<i64>,
    /// ###### 大文本通知栏样式
    /// - 当 style = 1 时可用,内容会被通知栏以大文本的形式展示出来。
    /// - 若没有填充 厂商 big_text, 则也默认使用该 big_text 字段展示。
    /// - 支持 api 16 以上的 rom。
    #[serde(skip_serializing_if = "Option::is_none")]
    pub big_text: Option<String>,
    /// ###### 设置角标数字累加值,在原角标的基础上进行累加
    /// - 此属性目前仅针对华为 EMUI 8.0 及以上、小米 MIUI 6 及以上、vivo、荣耀设备生效。
    /// - 此字段如果不填,表示不改变角标数字(小米设备由于系统控制,不论推送走极光通道下发还是厂商通道下发,即使不传递依旧是默认 +1 的效果)。
    /// - 取值范围为:1-99,若设置了取值范围内的数字,下一条通知栏消息配置的 badge_add_num 数据会和原角标数量进行相加,建议取值为 1。
    /// - 举例:badge_add_num 取值为 1,原角标数为 2,发送此角标消息后,应用角标数显示为 3。
    /// - 针对华为 / 荣耀通道,若 badge_set_num 与 badge_add_num 同时存在,则以 badge_set_num 为准;若“badge_add_num”和“badge_set_num”都设置为空,则应用角标数字默认加1。
    #[serde(skip_serializing_if = "Option::is_none")]
    pub badge_add_num: Option<u8>,
    /// ###### 设置角标数字固定值
    /// - 此属性目前仅针对华为 EMUI 8.0 及以上、荣耀设备走厂商通道时生效,若 badge_set_num 与 badge_add_num 同时存在,则以 badge_set_num 为准;若“badge_add_num”和“badge_set_num”都设置为空,则应用角标数字默认加1。
    /// - 取值范围为:0-99,若设置了取值范围内的数字,对应下一条通知栏消息配置的 badge_set_num 数字则为角标数值,举例:badge_set_num 取值为 1,无论应用之前角标数为多少,发送此角标消息后,应用角标数均显示为 1。
    #[serde(skip_serializing_if = "Option::is_none")]
    pub badge_set_num: Option<u8>,
    /// ###### 铃声
    /// - 填写 Android 工程中 /res/raw/ 路径下铃声文件名称,无需文件名后缀.
    /// - 注意:针对 Android 8.0 以上,当传递了 channel_id 时,此属性不生效。
    #[serde(skip_serializing_if = "Option::is_none")]
    pub sound: Option<String>,
    /// ###### APP 在前台,通知是否展示
    /// - 值为 "1" 时,APP 在前台会弹出/展示通知栏消息。
    /// - 值为 "0" 时,APP 在前台不会弹出/展示通知栏消息。
    /// - 注:默认情况下 APP 在前台会弹出/展示通知栏消息,JPush Android SDK v3.5.8 版本开始支持。
    /// - 前台展示功能目前适配的通道有:极光、华为、小米、vivo。
    #[serde(skip_serializing_if = "Option::is_none")]
    pub display_foreground: Option<String>,
    /// ###### 扩展字段
    /// - 这里自定义 JSON 格式的 Key / Value 信息,以供业务使用。
    /// - 针对部分厂商跳转地址异常,可通过 third_url_encode 兼容处理,详情参考 厂商通道无法跳转问题分析。
    /// - 当通知内容超过厂商的限制时,厂商通道会推送失败,可以在 extras 中配置 xx_content_forshort 参数传入对应厂商的通知内容,详情可展开表格查看。
    /// - mipns_content_forshort 由于小米官方的通知内容长度限制为128个字符以内(中英文都算一个),当通知内容(极光的“alert”字段的值)长度超过128时,小米通道会推送失败。此时调用极光api推送通知时,可使用此字段传入不超过128字符的通知内容作为小米通道通知内容。
    /// - oppns_content_forshort 由于OPPO官方的通知内容长度限制为50个字符以内(中英文都算一个),当通知内容(极光的“alert”字段的值)长度超过50时,OPPO通道会推送失败。此时调用极光api推送通知时,可使用此字段传入不超过50字符的通知内容作为OPPO通道通知内容。
    /// - vpns_content_forshort 由于vivo官方的通知内容长度限制为100个字符以内(1个汉字等于2个英文字符),当通知内容(极光的“alert”字段的值)长度超过100时,vivo通道会推送失败。此时调用极光api推送通知时,可使用此字段传入不超过100字符的通知内容作为vivo通道通知内容。
    /// - mzpns_content_forshort 由于魅族官方的通知内容长度限制为100个字符以内(中英文都算一个),当通知内容(极光的“alert”字段的值)长度超过100时,魅族通道会推送失败。此时调用极光api推送通知时,可使用此字段传入不超过100字符的通知内容作为魅族通道通知内容。
    #[serde(skip_serializing_if = "Option::is_none")]
    pub extras: Option<HashMap<String, String>>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub intent: Option<Intent>,
}

impl AndroidNotification {
    pub fn from_alert(title: &str, content: &str) -> Self {
        let mut android_notification = AndroidNotification::default();
        android_notification.alert = content.to_string();
        android_notification.badge_set_num = Some(1);
        android_notification.title = Some(title.to_string());
        android_notification.intent = Some(Intent::default());
        android_notification
    }
}

#[derive(Debug)]
pub enum IosNotificationAlert {
    /// 文本通知
    String(String),
    /// 自定义通知内容
    /// - title [String]
    /// - body [String]
    /// - title-loc-key Option<[String]>
    /// - title-loc-args Option<Vec<[String]>>
    /// - action-loc-key Option<[String]>
    /// - loc-key Option<[String]>
    /// - loc-args Option<Vec<[String]>>
    /// - launch-image [String]
    Custom(HashMap<String, String>),
}

#[derive(Debug, Default, Serialize, Deserialize)]
pub struct IosNotification {
    /// ###### 通知内容
    /// string 或 JSON Object
    /// - 这里指定内容将会覆盖上级统一指定的 alert 信息。
    /// - 内容为空则不展示到通知栏。
    /// - 支持字符串形式也支持官方定义的 [alert payload](https://developer.apple.com/library/content/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/PayloadKeyReference.html) 结构,在该结构中包含 title 和 subtitle 等官方支持的 key。
    pub alert: String,
    /// ###### 通知提示声音或警告通知
    /// string 或 JSON Object
    /// - 普通通知: string 类型,如果无此字段,则此消息无声音提示;有此字段,如果找到了指定的声音就播放该声音,否则播放默认声音,如果此字段为空字符串,iOS 7 为默认声音,iOS 8 及以上系统为无声音。说明:JPush 官方 SDK 会默认填充声音字段,提供另外的方法关闭声音,详情查看各 SDK 的源码。
    /// - 告警通知: JSON Object , 支持官方定义的 [payload](https://developer.apple.com/documentation/usernotifications/setting_up_a_remote_notification_server/generating_a_remote_notification#2990112) 结构,在该结构中包含 critical 、name 和 volume 等官方支持的 key 。
    /// - 自定义铃声说明:格式必须是 Linear PCM、MA4(IMA/ADPCM)、alaw,μLaw 的一种,将声频文件放到项目 bundle 目录中,且时长要求 30s 以下,否则就是系统默认的铃声,详见 [自定义铃声](https://docs.jiguang.cn/jpush/practice/custom_ringtone#apns-%E9%80%9A%E9%81%93%E9%80%9A%E7%9F%A5%E5%AE%9E%E7%8E%B0)。
    #[serde(skip_serializing_if = "Option::is_none")]
    pub sound: Option<String>,
    /// ###### 应用角标
    /// - 可设置为 N、+N、-N,N 的取值范围为 \[0,99]。若上传的角标值 value 为 10,表示角标会设置为 N、10+N、10-N(值小于 0 时默认清除角标)。
    /// - 为 0 或空字符串,则表示清除角标。
    /// - 如果不填,表示不改变角标数字。
    /// - JPush 官方服务端 SDK 会默认填充 badge 值为 "+1",详情参考:[badge +1](https://community.jiguang.cn/article/464967)。
    #[serde(skip_serializing_if = "Option::is_none")]
    pub badge: Option<String>,
    /// ###### 推送唤醒
    /// 推送的时候携带 "content-available":true,说明是 Background Remote Notification,如果不携带此字段则是普通的 Remote Notification,详情参考:[Background Remote Notification](https://docs.jiguang.cn/jpush/client/iOS/ios_new_fetures#ios-7-background-remote-notification)。
    #[serde(rename = "content-available", skip_serializing_if = "Option::is_none")]
    pub content_available: Option<bool>,
    /// ###### 通知扩展
    /// iOS 10 新增的 Notification Service Extension 功能,用于上报每条 APNs 信息的送达状态,使用该功能需要客户端实现 [Service Extension](https://docs.jiguang.cn/jpush/client/iOS/ios_guide_new#%E9%80%9A%E7%9F%A5%E5%B1%95%E7%A4%BA%E7%BB%9F%E8%AE%A1) 接口 ,并在服务端使用 mutable-content 字段完成设置。
    /// - true:说明支持 iOS 10 的 [UNNotificationServiceExtension](https://docs.jiguang.cn/jpush/client/iOS/ios_new_fetures#ios-10-service-extension) 功能。
    /// - 如果不携带此字段则是普通的 Remote Notification,无法统计抵达数据。
    #[serde(rename = "mutable-content", skip_serializing_if = "Option::is_none")]
    pub mutable_content: Option<bool>,
    /// ###### iOS 分类
    /// iOS 8 开始支持,设置 APNs payload 中的 "category" 字段值。
    #[serde(skip_serializing_if = "Option::is_none")]
    pub category: Option<String>,
    /// ###### 通知分组
    /// ios 的远程通知通过该属性来对通知进行分组,同一个 thread-id 的通知归为一组。
    #[serde(rename = "thread-id", skip_serializing_if = "Option::is_none")]
    pub thread_id: Option<String>,
    /// ###### 通知优先级和交付时间的中断级别
    /// ios 15 的通知级别,取值只能是 active,critical,passive,time-sensitive 中的一个,详情参考:[UNNotificationInterruptionLevel](https://developer.apple.com/documentation/usernotifications/unnotificationinterruptionlevel)。
    #[serde(rename = "interruption-level", skip_serializing_if = "Option::is_none")]
    pub interruption_level: Option<String>,
    /// ###### 扩展字段
    /// 这里自定义 Key / value 信息,以供业务使用,详情参考 [如何设置右侧图标/大图片](https://docs.jiguang.cn/jpush/practice/set_icon#%E5%8F%B3%E4%BE%A7%E5%9B%BE%E6%A0%87--%E5%A4%A7%E5%9B%BE%E7%89%87) 和 [iOS 通知点击跳转](https://docs.jiguang.cn/jpush/practice/intent_ios)。
    #[serde(skip_serializing_if = "Option::is_none")]
    pub extras: Option<HashMap<String, String>>,
}
impl IosNotification {
    pub fn from_alert(alert: &str) -> Self {
        let mut notification = IosNotification::default();
        notification.alert = alert.to_string();
        notification.sound = Some("sound.caf".to_string());
        notification.badge = Some("+1".to_string());
        notification
    }


    pub fn new(alert: String, extras: Option<HashMap<String, String>>) -> Self {
        IosNotification {
            sound: Some("sound.caf".to_string()),
            badge: Some("+1".to_string()),
            alert,
            extras,
            ..Default::default()
        }
    }
}

#[derive(Debug, Default, Serialize, Deserialize)]
pub struct HarmonyOSNotification {
    /// ###### 通知内容
    /// - 这里指定后会覆盖上级统一指定的 alert 信息。
    /// - 内容不可以是空字符串,否则推送厂商会返回失败。
    pub alert: String,
    /// ###### 通知标题
    /// - 如果指定了,则通知里原来展示 App 名称的地方,将展示 title。否则使用WebPortal配置的默认title。
    pub title: Option<String>,
    /// ###### 通知栏消息分类条目
    /// - 此字段由于厂商为必填字段,效果也完全依赖 rom 厂商对 category 的处理策略,请开发者务必填写。极光内部对此字段实际未进行必填校验,请开发者按照必填处理。
    /// - 此字段值对应官方「云端category」取值,开发者通过极光服务发起推送时如果传递了此字段值,请务必按照官方要求传递,官方category分类取值规则也可参考[鸿蒙消息分类标准](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/push-apply-right-V5)
    pub category: String,
    /// ###### 通知栏大图标
    /// - 要求传递网络地址,使用HTTPS协议,取值样例:https://example.com/image.png。
    /// - 图标大小不超过 30 k,图片长*宽<12800像素。
    pub large_icon: Option<String>,
    /// ###### 指定跳转页面
    /// 支持跳转到应用首页、deeplink 地址和Action跳转三种类型:
    /// - 1.跳转应用首页:固定 action.system.home
    /// - 2.跳转到 deeplink 地址: scheme://test?key1=val1&key2=val2
    /// - 3.跳转到 action 地址: com.test.action
    ///
    /// 说明:此字段由于厂商为必填字段,请开发者务必填写。极光内部对此字段实际未进行必填校验,请开发者按照必填处理。
    pub intent: Intent,
    /// ###### 设置角标数字累加值
    /// - 此字段如果不填,表示不改变角标数字
    /// - 取值范围为:1-99,若设置了取值范围内的数字,下一条通知栏消息配置的 badge_add_num 数据会和原角标数量进行相加,建议取值为 1。
    ///
    /// 举例:badge_add_num 取值为 1,原角标数为 2,发送此角标消息后,应用角标数显示为 3。
    pub badge_add_num: Option<u8>,
    /// ###### 设置角标数字为固定值
    /// - 此字段如果不填,表示不改变角标数字
    /// - 取值范围为:0-99,若设置了取值范围内的数字,对应下一条通知栏消息配置的 badge_set_num 数字则为角标数值,举例:badge_set_num 取值为 1,无论应用之前角标数为多少,发送此角标消息后,应用角标数均显示为 1。
    pub badge_set_num: Option<u8>,
    /// ###### 测试消息标识
    /// - false:正常消息(默认值)
    /// - true:测试消息
    pub test_message: Option<bool>,
    /// ###### 华为回执 ID
    /// 输入一个唯一的回执 ID 指定本次下行消息的回执地址及配置,该回执 ID 可以在[鸿蒙回执参数配置](https://docs.jiguang.cn/jpush/client/HarmonyOS/hmos_3rd_param#%E9%B8%BF%E8%92%99%E9%80%9A%E9%81%93%E5%9B%9E%E6%89%A7%E9%85%8D%E7%BD%AE%E6%8C%87%E5%8D%97)中查看。
    pub receipt_id: Option<String>,
    /// ###### 扩展字段
    /// 这里自定义 JSON 格式的 Key / Value 信息,以供业务使用。
    #[serde(skip_serializing_if = "Option::is_none")]
    pub extras: Option<HashMap<String, String>>,
    /// ###### 通知栏样式类型
    /// 默认为0
    /// - 0:普通样式
    /// - 2:多文本样式
    pub style: Option<i64>,
    /// ###### 多行文本样式
    /// 对应 style 的取值类型 2
    #[serde(skip_serializing_if = "Option::is_none")]
    pub inbox: Option<HashMap<String, String>>,
    /// ###### 推送类型
    /// 默认值 0,目前仅支持:
    /// - 0-通知消息
    /// - 2-通知拓展消息
    /// - 10-VoIP呼叫消息
    ///
    /// 其它值报错
    pub push_type: Option<i64>,
    /// ###### 推送类型
    /// - 对应华为 extraData 字段,当 push_type=2 或 push_type=10 时生效,此时是必填的,push_type=0时忽略此字段。
    pub extra_data: Option<String>,
    /// ###### APP 在前台,通知是否展示
    /// - 值为 "1" 时,APP 在前台会弹出/展示通知栏消息。
    /// - 值为 "0" 时,APP 在前台不会弹出/展示通知栏消息。
    pub display_foreground: Option<String>,

}

impl HarmonyOSNotification {
    pub fn from_alert(alert: &str) -> Self {
        let mut notification = HarmonyOSNotification::default();
        notification.alert = alert.to_string();
        notification.title = Some(alert.to_string());
        notification.intent = Intent::default();
        notification.badge_add_num = Some(1);
        notification
    }
}

#[derive(Debug, Serialize, Deserialize)]
pub struct Message {
    pub title: String,
    pub msg_content: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub extras: Option<HashMap<String, String>>,
}

impl Message {
    pub fn new(title: String, msg_content: String, extras: Option<HashMap<String, String>>) -> Self {
        Message {
            title,
            msg_content,
            extras,
        }
    }
}

#[derive(Debug, Default, Serialize, Deserialize)]
pub struct MessageOptions {
    /// ###### 推送序号
    /// - 纯粹用来作为 API 调用标识,API 返回时被原样返回,以方便 API 调用方匹配请求与返回。
    /// - 值为 0 表示该 messageid 无 sendno,所以字段取值范围为非 0 的 int。
    #[serde(skip_serializing_if = "Option::is_none")]
    pub sendno: Option<u32>,
    /// ###### 离线消息保留时长 (秒)
    /// - 推送当前用户不在线时,为该用户保留多长时间的离线消息,以便其上线时再次推送。
    /// - 默认 86400 (1 天),普通用户最长 3 天, VIP 用户最长 10 天。设置为 0 表示不保留离线消息,只有推送当前在线的用户可以收到。
    /// - 该字段对 iOS 的 Notification 消息无效。
    #[serde(skip_serializing_if = "Option::is_none")]
    pub time_to_live: Option<u32>,
    /// ###### 要覆盖的消息 ID
    /// 如果当前的推送要覆盖之前的一条推送,这里填写前一条推送的 msg_id 就会产生覆盖效果,即:
    /// - 该 msg_id 离线收到的消息是覆盖后的内容,即使该 msg_id Android 端用户已经收到,如果通知栏还未清除,则新的消息内容会覆盖之前这条通知。
    /// - 覆盖功能起作用的时限是:1 天,如果在覆盖指定时限内该 msg_id 不存在,则返回 1003 错误,提示不是一次有效的消息覆盖操作,当前的消息不会被推送。
    /// - 该字段对 Android 有效,仅支持极光通道、小米通道、OPPO 通道、vivo 通道、FCM 通道、荣耀通道、华为通道(EMUI10 及以上的设备)和鸿蒙通道。
    #[serde(skip_serializing_if = "Option::is_none")]
    pub override_msg_id: Option<u64>,
    /// ###### APNs 是否生产环境
    /// 该字段仅对 iOS 的 Notification 有效,如果不指定则为推送生产环境。注意:JPush 服务端 SDK 默认设置为推送 “开发环境”。
    /// - true:表示推送生产环境。
    /// - false:表示推送开发环境。
    #[serde(skip_serializing_if = "Option::is_none")]
    pub apns_production: Option<bool>,
    /// ###### 更新 iOS 通知的标识符
    /// - APNs 新通知如果匹配到当前通知中心有相同 apns-collapse-id 字段的通知,则会用新通知内容来更新它,并使其置于通知中心首位。
    /// - collapse id 长度不可超过 64 bytes。
    #[serde(skip_serializing_if = "Option::is_none")]
    pub apns_collapse_id: Option<String>,
    /// ###### 定速推送时长 (分钟)
    /// - 又名缓慢推送,把原本尽可能快的推送速度,降低下来,给定的 n 分钟内,均匀地向这次推送的目标用户推送;最大值为 1400。
    /// - 最多能同时存在 20 条定速推送。
    /// - 未设置则不是定速推送。
    #[serde(skip_serializing_if = "Option::is_none")]
    pub big_push_duration: Option<u32>,
    /// ###### 推送请求下发通道
    /// 仅针对配置了厂商用户使用有效,详情参考 [third_party_channel 说明](https://docs.jiguang.cn/jpush/server/push/rest_api_v3_push#third_party_channel-%E8%AF%B4%E6%98%8E) 。
    #[serde(skip_serializing_if = "Option::is_none")]
    pub third_party_channel: Option<ThirdPartyChannel>,
    /// ###### 消息类型分类
    /// 极光不对指定的消息类型进行判断或校准,会以开发者自行指定的消息类型适配 Android 厂商通道。不填默认为 0。
    /// - 0:代表运营消息。
    /// - 1:代表系统消息。
    ///
    /// 此字段优先级最高,会覆盖 options.third_party_channel.vivo.classification 设置的值。

    #[serde(skip_serializing_if = "Option::is_none")]
    pub classification: Option<u8>,
}

#[derive(Debug, Default, Serialize, Deserialize)]
pub struct ThirdPartyChannel {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub xiaomi: Option<ThirdPartyChannelContent>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub huawei: Option<ThirdPartyChannelContent>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub honor: Option<ThirdPartyChannelContent>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub meizu: Option<ThirdPartyChannelContent>,
    // #[serde(skip_serializing_if = "Option::is_none")]
    // pub fcm: Option<ThirdPartyChannelContent>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub oppo: Option<ThirdPartyChannelContent>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub vivo: Option<ThirdPartyChannelContent>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub nio: Option<ThirdPartyChannelContent>,
}

impl ThirdPartyChannel {
    pub fn from_category(category: &str) -> Self {
        Self {
            xiaomi: Some(ThirdPartyChannelContent::from_category(category)),
            huawei: Some(ThirdPartyChannelContent::from_category(category)),
            honor: Some(ThirdPartyChannelContent::from_category(category)),
            meizu: Some(ThirdPartyChannelContent::from_category(category)),
            // fcm: None,
            oppo: Some(ThirdPartyChannelContent::from_category(category)),
            vivo: Some(ThirdPartyChannelContent::from_category(category)),
            nio: Some(ThirdPartyChannelContent::from_category(category)),
        }
    }
}

#[derive(Debug, Default, Serialize, Deserialize)]
pub struct ThirdPartyChannelContent {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub distribution: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub distribution_fcm: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub category: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub channel_id: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub importance: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub receipt_id: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub skip_quota: Option<bool>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub classification: Option<i8>,
}

impl ThirdPartyChannelContent {
    pub fn from_category(category: &str) -> Self {
        Self {
            distribution: Some("secondary_push".to_string()),
            distribution_fcm: Some("secondary_fcm_push".to_string()),
            importance: Some("NORMAL".to_string()),
            category: Some(category.to_string()),
            skip_quota: Some(true),
            ..Default::default()
        }
    }
}


impl MessageOptions {
    pub fn new(time_to_live: u32, apns_production: bool, sendno: u32) -> Self {
        MessageOptions {
            time_to_live: Some(time_to_live),
            override_msg_id: None,
            apns_production: Some(apns_production),
            apns_collapse_id: None,
            big_push_duration: None,
            third_party_channel: None,
            sendno: Some(sendno),
            classification: None,
        }
    }
    pub fn from_category(category: &str) -> Self {
        let mut options = MessageOptions::default();
        options.time_to_live = Some(86400);
        options.apns_production = Some(true);
        options.sendno = Some(1);
        options.third_party_channel = Some(ThirdPartyChannel::from_category(category));
        options
    }
}

impl JPushMessage {
    pub fn default() -> Self {
        JPushMessage {
            platform: Platform::default(),
            audience: Audience::default(),
            notification: None,
            message: None,
            options: None,
        }
    }

    pub fn simple_normal_message(title: &str, content: &str, category: &str) -> Self {
        let mut msg = Self::default();
        let notification = Notification::from_alert(title, content);
        let options = MessageOptions::from_category(category);
        msg.platform = Platform::default();
        msg.audience = Audience::default();
        msg.message = Some(Message::new(title.to_string(), content.to_string(), None));
        msg.notification = Some(notification);
        msg.options = Some(options);
        msg
    }

    pub fn set_platform(&mut self, platform: Platform) -> &mut Self {
        self.platform = platform;
        self
    }
    pub fn add_alias(&mut self, alias: Vec<String>) -> &mut Self {
        if let Audience::Custom(ref mut audience) = &mut self.audience {
            match &mut audience.alias {
                None => {
                    audience.alias = Some(alias);
                }
                Some(ref mut audience_alias) => {
                    audience_alias.extend(alias);
                    audience.alias = Some(audience_alias.clone());
                }
            }
        } else {
            self.audience = Audience::Custom(AudienceCustom {
                alias: Some(alias),
                ..Default::default()
            });
        }
        self
    }
    pub fn add_tag(&mut self, tags: Vec<String>) -> &mut Self {
        if let Audience::Custom(ref mut audience) = &mut self.audience {
            match &mut audience.tag {
                None => {
                    audience.tag = Some(tags);
                }
                Some(ref mut audience_tag) => {
                    audience_tag.extend(tags);
                    audience.tag = Some(audience_tag.clone());
                }
            }
        } else {
            self.audience = Audience::Custom(AudienceCustom {
                tag: Some(tags),
                ..Default::default()
            });
        }
        self
    }
    pub fn add_tag_and(&mut self, tags: Vec<String>) -> &mut Self {
        if let Audience::Custom(ref mut audience) = &mut self.audience {
            match &mut audience.tag_and {
                None => {
                    audience.tag_and = Some(tags);
                }
                Some(ref mut audience_tag) => {
                    audience_tag.extend(tags);
                    audience.tag_and = Some(audience_tag.clone());
                }
            }
        } else {
            self.audience = Audience::Custom(AudienceCustom {
                tag_and: Some(tags),
                ..Default::default()
            });
        }
        self
    }
    pub fn add_tag_not(&mut self, tags: Vec<String>) -> &mut Self {
        if let Audience::Custom(ref mut audience) = &mut self.audience {
            match &mut audience.tag_not {
                None => {
                    audience.tag_not = Some(tags);
                }
                Some(ref mut audience_tag) => {
                    audience_tag.extend(tags);
                    audience.tag_not = Some(audience_tag.clone());
                }
            }
        } else {
            self.audience = Audience::Custom(AudienceCustom {
                tag_not: Some(tags),
                ..Default::default()
            });
        }
        self
    }
    pub fn add_registration_id(&mut self, registration_ids: Vec<String>) -> &mut Self {
        if let Audience::Custom(ref mut audience) = &mut self.audience {
            match &mut audience.registration_id {
                None => {
                    audience.registration_id = Some(registration_ids);
                }
                Some(ref mut audience_registration_ids) => {
                    audience_registration_ids.extend(registration_ids);
                    audience.registration_id = Some(audience_registration_ids.clone());
                }
            }
        } else {
            self.audience = Audience::Custom(AudienceCustom {
                registration_id: Some(registration_ids),
                ..Default::default()
            });
        }
        self
    }
    pub fn add_segment(&mut self, segments: Vec<String>) -> &mut Self {
        if let Audience::Custom(ref mut audience) = &mut self.audience {
            match &mut audience.segment {
                None => {
                    audience.segment = Some(segments);
                }
                Some(ref mut audience_segments) => {
                    audience_segments.extend(segments);
                    audience.segment = Some(audience_segments.clone());
                }
            }
        } else {
            self.audience = Audience::Custom(AudienceCustom {
                segment: Some(segments),
                ..Default::default()
            });
        }
        self
    }
    pub fn add_abtest(&mut self, abtests: Vec<String>) -> &mut Self {
        if let Audience::Custom(ref mut audience) = &mut self.audience {
            match &mut audience.abtest {
                None => {
                    audience.abtest = Some(abtests);
                }
                Some(ref mut audience_abtests) => {
                    audience_abtests.extend(abtests);
                    audience.abtest = Some(audience_abtests.clone());
                }
            }
        } else {
            self.audience = Audience::Custom(AudienceCustom {
                abtest: Some(abtests),
                ..Default::default()
            });
        }
        self
    }
    pub fn live_activity_id(&mut self, live_activity_id: String) -> &mut Self {
        if let Audience::Custom(ref mut audience) = &mut self.audience {
            audience.live_activity_id = Some(live_activity_id);
        }
        self
    }
    pub fn set_notification_alert(&mut self, alert: String) -> &mut Self {
        if let Some(ref mut notification) = &mut self.notification {
            notification.alert = Some(alert);
        } else {
            self.notification = Some(Notification {
                alert: Some(alert),
                android: None,
                ios: None,
                hmos: None,
            });
        }
        self
    }
    pub fn set_ios_notification(&mut self, ios_notification: IosNotification) -> &mut Self {
        if let Some(ref mut notification) = &mut self.notification {
            notification.ios = Some(ios_notification);
        } else {
            self.notification = Some(Notification {
                alert: None,
                android: None,
                ios: Some(ios_notification),
                hmos: None,
            });
        }
        self
    }
    pub fn set_android_notification(&mut self, android_notification: AndroidNotification) -> &mut Self {
        if let Some(ref mut notification) = &mut self.notification {
            notification.android = Some(android_notification);
        } else {
            self.notification = Some(Notification {
                alert: Some(android_notification.alert.to_string()),
                android: Some(android_notification),
                ios: None,
                hmos: None,
            });
        }
        self
    }
    pub fn set_hmos_notification(&mut self, hmos_notification: HarmonyOSNotification) -> &mut Self {
        if let Some(ref mut notification) = &mut self.notification {
            notification.hmos = Some(hmos_notification);
        } else {
            self.notification = Some(Notification {
                alert: None,
                android: None,
                ios: None,
                hmos: Some(hmos_notification),
            });
        }
        self
    }
    pub fn set_message(&mut self, message: Message) -> &mut Self {
        self.message = Some(message);
        self
    }
    pub fn set_options(&mut self, options: MessageOptions) -> &mut Self {
        self.options = Some(options);
        self
    }
    pub fn to_json(&self) -> String {
        serde_json::to_string(self).unwrap()
    }
}

impl Into<reqwest::Body> for JPushMessage {
    fn into(self) -> reqwest::Body {
        reqwest::Body::from(self.to_json())
    }
}

#[derive(Debug, Serialize, Deserialize)]
pub struct JPushMessageResp {
    pub msg_id: Option<String>,
    pub sendno: Option<String>,
    pub error: Option<JPushMessageError>,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct JPushMessageError {
    pub code: Option<i32>,
    pub message: Option<String>,
}

impl JPushMessageResp {
    pub fn is_ok(&self) -> bool {
        self.error.is_none()
    }
    pub fn is_error(&self) -> bool {
        self.error.is_some()
    }
    pub fn get_msg_id(&self) -> Option<String> {
        self.msg_id.clone()
    }
    pub fn get_error_code(&self) -> Option<i32> {
        self.error.as_ref().map(|x| x.code.unwrap_or(0))
    }
    pub fn get_error_message(&self) -> Option<String> {
        self.error.as_ref().map(|x| x.message.clone().unwrap_or("".to_string()))
    }
}