use crate::req_utils::*;
use crate::{SaasContext, Wechat, WechatError, WechatResult};
use async_trait::async_trait;
use maplit::hashmap;
use serde::{Deserialize, Serialize};
use serde_json::json;
use std::vec::Vec;
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct KfAccount {
#[serde(rename = "kf_account")]
pub account: String,
#[serde(rename = "kf_nick")]
pub nickname: String,
#[serde(rename = "kf_id")]
pub id: String,
#[serde(rename = "kf_headimgurl")]
pub headimageurl: String,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct KfMsgMunuListItem {
pub id: String,
pub content: String,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(untagged)]
pub enum KfMessage {
Text { content: String },
Image { media_id: String },
Voice { media_id: String },
Video {
media_id: String,
thumb_media_id: String,
title: Option<String>,
description: Option<String>,
},
Music {
title: Option<String>,
description: Option<String>,
musicurl: String,
hqmusicurl: String,
thumb_media_id: String,
},
News {
title: Option<String>,
description: Option<String>,
url: Option<String>,
picurl: Option<String>,
},
MpNews { media_id: String },
MsgMenu {
head_content: String,
tail_content: String,
list: Vec<KfMsgMunuListItem>,
},
WxCard { card_id: String },
MiniProgramPage {
title: Option<String>,
appid: String,
pagepath: String,
thumb_media_id: String,
},
}
impl KfMessage {
fn get_msgtype(&self) -> &str {
match self {
KfMessage::Text { content: _ } => "text",
KfMessage::Image { media_id: _ } => "image",
KfMessage::Voice { media_id: _ } => "voice",
KfMessage::Video {
media_id: _,
thumb_media_id: _,
title: _,
description: _,
} => "video",
KfMessage::Music {
title: _,
description: _,
musicurl: _,
hqmusicurl: _,
thumb_media_id: _,
} => "music",
KfMessage::News {
title: _,
description: _,
url: _,
picurl: _,
} => "news",
KfMessage::MpNews { media_id: _ } => "mpnews",
KfMessage::MsgMenu {
head_content: _,
tail_content: _,
list: _,
} => "msgmenu",
KfMessage::WxCard { card_id: _ } => "wxcard",
KfMessage::MiniProgramPage {
title: _,
appid: _,
pagepath: _,
thumb_media_id: _,
} => "miniprogrampage",
}
}
}
impl<S> From<S> for KfMessage
where
S: Into<String>,
{
fn from(msg: S) -> Self {
KfMessage::Text {
content: msg.into(),
}
}
}
#[async_trait]
pub trait Customservice {
async fn add_kf_account(
&self,
context: &SaasContext,
account: &String,
nickname: &String,
password: Option<String>,
) -> WechatResult<()>;
async fn update_kf_account(
&self,
context: &SaasContext,
account: &String,
nickname: &String,
password: Option<String>,
) -> WechatResult<()>;
async fn del_kf_account(
&self,
context: &SaasContext,
account: &String,
nickname: &String,
password: Option<String>,
) -> WechatResult<()>;
async fn get_kf_list(&self, context: &SaasContext) -> Result<Vec<KfAccount>, WechatError>;
async fn send_kf_msg(
&self,
context: &SaasContext,
touser: &String,
msg: &KfMessage,
kf_account: &Option<String>,
) -> WechatResult<()>;
async fn typing(&self, context: &SaasContext, touser: String, typing: bool)
-> WechatResult<()>;
}
#[async_trait]
impl Customservice for Wechat {
async fn add_kf_account(
&self,
context: &SaasContext,
account: &String,
nickname: &String,
password: Option<String>,
) -> WechatResult<()> {
let mut body = hashmap! {
"kf_account" => account,
"nickname" => nickname,
};
if let Some(password) = &password {
body.insert("password", password);
}
let _: EmptyApiResult = self
.api_post(context, "customservice/kfaccount/add", None, &body)
.await?;
Ok(())
}
async fn update_kf_account(
&self,
context: &SaasContext,
account: &String,
nickname: &String,
password: Option<String>,
) -> WechatResult<()> {
let mut body = hashmap! {
"kf_account" => account,
"nickname" => nickname,
};
if let Some(password) = &password {
body.insert("password", password);
}
let _: EmptyApiResult = self
.api_post(context, "customservice/kfaccount/update", None, &body)
.await?;
Ok(())
}
async fn del_kf_account(
&self,
context: &SaasContext,
account: &String,
nickname: &String,
password: Option<String>,
) -> WechatResult<()> {
let mut body = hashmap! {
"kf_account" => account,
"nickname" => nickname,
};
if let Some(password) = &password {
body.insert("password", password);
}
let _: EmptyApiResult = self
.api_post(context, "customservice/kfaccount/del", None, &body)
.await?;
Ok(())
}
async fn get_kf_list(&self, context: &SaasContext) -> Result<Vec<KfAccount>, WechatError> {
self.api_get(context, "cgi-bin/customservice/getkflist", None)
.await
}
async fn send_kf_msg(
&self,
context: &SaasContext,
touser: &String,
msg: &KfMessage,
kf_account: &Option<String>,
) -> WechatResult<()> {
let msgtype = msg.get_msgtype();
let mut msg_value = serde_json::to_value(msg.clone())?;
if let KfMessage::News {
title: _,
description: _,
url: _,
picurl: _,
} = &msg
{
msg_value = json! ({
"articles": [ msg_value ],
});
};
let mut msg_value = json!({
"touser": touser,
"msgtype": msgtype.clone(),
msgtype: msg_value,
});
if let Some(kf_account) = kf_account {
msg_value["customservice"] = json!({
"kf_account": kf_account,
});
}
let _: EmptyApiResult = self
.api_post(context, "cgi-bin/message/custom/send", None, &msg_value)
.await?;
Ok(())
}
async fn typing(
&self,
context: &SaasContext,
touser: String,
typing: bool,
) -> WechatResult<()> {
let body = json!({
"touser": touser,
"command": if typing {"Typing"} else {"CancelTyping"},
});
let _: EmptyApiResult = self
.api_post(context, "cgi-bin/message/custom/typing", None, &body)
.await?;
Ok(())
}
}