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()))
}
}