wecom_agent/
message.rs

1use crate::error::Error;
2use serde::Serialize;
3use serde_json::{json, Value};
4
5pub trait WecomMessage {
6    fn msg_type(&self) -> MessageType;
7    fn key(&self) -> String;
8    fn value(&self) -> impl Serialize
9    where
10        Self: Serialize,
11    {
12        self
13    }
14}
15
16#[derive(Debug, Serialize)]
17pub enum MessageType {
18    Text,
19    Image,
20    Audio,
21    Video,
22    File,
23    TextCard,
24    News,
25    Markdown,
26}
27
28#[derive(Debug)]
29pub struct MessageBuilder {
30    users: Option<String>,
31    groups: Option<String>,
32    tags: Option<String>,
33    agent_id: Option<usize>,
34    safe: i64,
35    enable_id_trans: i64,
36    enable_duplicate_check: i64,
37    duplicate_check_interval: usize,
38}
39
40impl Default for MessageBuilder {
41    fn default() -> Self {
42        Self {
43            users: None,
44            groups: None,
45            tags: None,
46            agent_id: None,
47            safe: 0,
48            enable_id_trans: 0,
49            enable_duplicate_check: 0,
50            duplicate_check_interval: 1800,
51        }
52    }
53}
54
55impl MessageBuilder {
56    pub fn new() -> Self {
57        Self::default()
58    }
59
60    pub fn to_users(mut self, users: Vec<&str>) -> Self {
61        self.users = Some(
62            users
63                .iter()
64                .fold("".to_string(), |acc, &u| format!("{acc}|{u}"))
65                .trim_start_matches('|')
66                .to_string(),
67        );
68        self
69    }
70
71    pub fn to_groups(mut self, groups: Vec<&str>) -> Self {
72        self.groups = Some(
73            groups
74                .iter()
75                .fold("".to_string(), |acc, &u| format!("{acc}|{u}"))
76                .trim_start_matches('|')
77                .to_string(),
78        );
79        self
80    }
81
82    pub fn to_tags(mut self, tags: Vec<&str>) -> Self {
83        self.tags = Some(
84            tags.iter()
85                .fold("".to_string(), |acc, &u| format!("{acc}|{u}"))
86                .trim_start_matches('|')
87                .to_string(),
88        );
89        self
90    }
91
92    pub fn from_agent(mut self, agent_id: usize) -> Self {
93        self.agent_id = Some(agent_id);
94        self
95    }
96
97    pub fn with_safe(mut self, safe: i64) -> Self {
98        self.safe = safe;
99        self
100    }
101    pub fn with_enable_id_trans(mut self, enable_id_trans: i64) -> Self {
102        self.enable_id_trans = enable_id_trans;
103        self
104    }
105    pub fn with_enable_duplicate_check(mut self, enable_duplicate_check: i64) -> Self {
106        self.enable_duplicate_check = enable_duplicate_check;
107        self
108    }
109    pub fn with_duplicate_check_interval(mut self, duplicate_check_interval: usize) -> Self {
110        self.duplicate_check_interval = duplicate_check_interval;
111        self
112    }
113
114    pub fn build<T>(&self, content: T) -> Result<Value, Box<dyn std::error::Error>>
115    where
116        T: Serialize + WecomMessage,
117    {
118        if [&self.users, &self.groups, &self.tags]
119            .iter()
120            .all(|&x| x.is_none())
121        {
122            return Err(Box::new(Error::new(-999, "收件人不可为空".to_string())));
123        }
124
125        if self.agent_id.is_none() {
126            return Err(Box::new(Error::new(-999, "AgentID不可为空".to_string())));
127        }
128
129        let empty_string = "".to_string();
130        let mut j = json!({
131            "touser": self.users.clone().unwrap_or(empty_string.clone()),
132            "toparty": self.groups.clone().unwrap_or(empty_string.clone()),
133            "totag": self.tags.clone().unwrap_or(empty_string.clone()),
134            "msgtype": match content.msg_type() {
135                MessageType::Audio => "voice".to_string(),
136                MessageType::File => "file".to_string(),
137                MessageType::Image => "image".to_string(),
138                MessageType::Markdown => "markdown".to_string(),
139                MessageType::News => "news".to_string(),
140                MessageType::Text => "text".to_string(),
141                MessageType::TextCard => "textcard".to_string(),
142                MessageType::Video => "video".to_string(),
143            },
144            "agentid": self.agent_id.expect("AgentID should not be None"),
145            "safe": self.safe,
146            "enable_id_trans": self.enable_id_trans,
147            "enable_duplicate_check": self.enable_duplicate_check,
148            "duplicate_check_interval": self.duplicate_check_interval,});
149        j.as_object_mut()
150            .unwrap()
151            .insert(content.key(), serde_json::to_value(content.value())?);
152        Ok(j)
153    }
154}
155
156// 文本消息
157// 示例
158// {
159//    "touser" : "UserID1|UserID2|UserID3",
160//    "toparty" : "PartyID1|PartyID2",
161//    "totag" : "TagID1 | TagID2",
162//    "msgtype" : "text",
163//    "agentid" : 1,
164//    "text" : {
165//        "content" : "Hello from <a href=\"https://yinguobing.com\">Wondering AI</a>!"
166//    },
167//    "safe":0,
168//    "enable_id_trans": 0,
169//    "enable_duplicate_check": 0,
170//    "duplicate_check_interval": 1800
171// }
172#[derive(Debug, Serialize, PartialEq)]
173pub struct Text {
174    content: String,
175}
176
177impl Text {
178    pub fn new(content: String) -> Self {
179        Self { content }
180    }
181}
182
183impl WecomMessage for Text {
184    fn msg_type(&self) -> MessageType {
185        MessageType::Text
186    }
187
188    fn key(&self) -> String {
189        "text".to_string()
190    }
191}
192
193// 图片消息
194// 示例
195// {
196//    "touser" : "UserID1|UserID2|UserID3",
197//    "toparty" : "PartyID1|PartyID2",
198//    "totag" : "TagID1 | TagID2",
199//    "msgtype" : "image",
200//    "agentid" : 1,
201//    "image" : {
202//         "media_id" : "MEDIA_ID"
203//    },
204//    "safe":0,
205//    "enable_duplicate_check": 0,
206//    "duplicate_check_interval": 1800
207// }
208pub struct ImageMsg {}
209
210// 语音消息
211// 示例
212// {
213//     "touser" : "UserID1|UserID2|UserID3",
214//     "toparty" : "PartyID1|PartyID2",
215//     "totag" : "TagID1 | TagID2",
216//     "msgtype" : "voice",
217//     "agentid" : 1,
218//     "voice" : {
219//          "media_id" : "MEDIA_ID"
220//     },
221//     "enable_duplicate_check": 0,
222//     "duplicate_check_interval": 1800
223// }
224pub struct AudioMsg {}
225
226// 视频消息
227// 示例
228// {
229//     "touser" : "UserID1|UserID2|UserID3",
230//     "toparty" : "PartyID1|PartyID2",
231//     "totag" : "TagID1 | TagID2",
232//     "msgtype" : "video",
233//     "agentid" : 1,
234//     "video" : {
235//          "media_id" : "MEDIA_ID",
236//          "title" : "Title",
237//          "description" : "Description"
238//     },
239//     "safe":0,
240//     "enable_duplicate_check": 0,
241//     "duplicate_check_interval": 1800
242// }
243pub struct VideoMsg {}
244
245// 文件消息
246// 示例
247// {
248//     "touser" : "UserID1|UserID2|UserID3",
249//     "toparty" : "PartyID1|PartyID2",
250//     "totag" : "TagID1 | TagID2",
251//     "msgtype" : "file",
252//     "agentid" : 1,
253//     "file" : {
254//          "media_id" : "1Yv-zXfHjSjU-7LH-GwtYqDGS-zz6w22KmWAT5COgP7o"
255//     },
256//     "safe":0,
257//     "enable_duplicate_check": 0,
258//     "duplicate_check_interval": 1800
259// }
260pub struct FileMsg {}
261
262// 文本卡片消息
263// 示例
264// {
265//     "touser" : "UserID1|UserID2|UserID3",
266//     "toparty" : "PartyID1 | PartyID2",
267//     "totag" : "TagID1 | TagID2",
268//     "msgtype" : "textcard",
269//     "agentid" : 1,
270//     "textcard" : {
271//              "title" : "领奖通知",
272//              "description" : "<div class=\"gray\">2016年9月26日</div> <div class=\"normal\">恭喜你抽中iPhone 7一台,领奖码:xxxx</div><div class=\"highlight\">请于2016年10月10日前联系行政同事领取</div>",
273//              "url" : "URL",
274//                          "btntxt":"更多"
275//     },
276//     "enable_id_trans": 0,
277//     "enable_duplicate_check": 0,
278//     "duplicate_check_interval": 1800
279// }
280pub struct TextCardMsg {}
281
282// MarkDown消息
283// 示例
284// {
285//     "touser" : "UserID1|UserID2|UserID3",
286//     "toparty" : "PartyID1|PartyID2",
287//     "totag" : "TagID1 | TagID2",
288//     "msgtype": "markdown",
289//     "agentid" : 1,
290//     "markdown": {
291//          "content": "您的会议室已经预定,稍后会同步到`邮箱`  \n>**事项详情**  \n>事 项:<font color=\"info\">开会</font>  \n>组织者:@miglioguan  \n>参与者:@miglioguan、@kunliu、@jamdeezhou、@kanexiong、@kisonwang  \n>  \n>会议室:<font color=\"info\">广州TIT 1楼 301</font>  \n>日 期:<font color=\"warning\">2018年5月18日</font>  \n>时 间:<font color=\"comment\">上午9:00-11:00</font>  \n>  \n>请准时参加会议。  \n>  \n>如需修改会议信息,请点击:[修改会议信息](https://work.weixin.qq.com)"
292//     },
293//     "enable_duplicate_check": 0,
294//     "duplicate_check_interval": 1800
295// }
296pub struct MarkDownMsg {}
297
298#[cfg(test)]
299mod test {
300    use std::vec;
301
302    use super::*;
303    #[test]
304    fn test_builder() {
305        let content = Text::new("hello text!".to_string());
306        let msg = MessageBuilder::default()
307            .to_users(vec!["robin", "tom", "Alex", "Susanna"])
308            .to_groups(vec!["a", "b", "c"])
309            .to_tags(vec!["x", "y", "z"])
310            .from_agent(1)
311            .with_safe(1)
312            .with_enable_id_trans(1)
313            .with_enable_duplicate_check(1)
314            .with_duplicate_check_interval(800)
315            .build(content)
316            .expect("Massage should be built");
317        let raw = json!({
318            "touser" : "robin|tom|Alex|Susanna",
319            "toparty" : "a|b|c",
320            "totag" : "x|y|z",
321            "msgtype": "text",
322            "agentid" : 1,
323            "safe": 1,
324            "enable_id_trans": 1,
325            "enable_duplicate_check": 1,
326            "duplicate_check_interval": 800,
327            "text": {
328                 "content": "hello text!"
329            },
330        });
331        assert_eq!(msg, serde_json::to_value(raw).unwrap());
332    }
333}