1use serde::{Deserialize, Serialize};
2use serde_json::json;
3
4use crate::client::WeChatClient;
5use crate::error::Result;
6
7#[derive(Debug, Clone, Serialize)]
9#[serde(tag = "msgtype", rename_all = "lowercase")]
10pub enum CustomerServiceMessage {
11 Text {
12 touser: String,
13 text: TextContent,
14 },
15 Image {
16 touser: String,
17 image: MediaContent,
18 },
19 Voice {
20 touser: String,
21 voice: MediaContent,
22 },
23 Video {
24 touser: String,
25 video: VideoContent,
26 },
27 Music {
28 touser: String,
29 music: MusicContent,
30 },
31 News {
32 touser: String,
33 news: NewsContent,
34 },
35 #[serde(rename = "mpnews")]
36 MpNews {
37 touser: String,
38 mpnews: MediaContent,
39 },
40 #[serde(rename = "msgmenu")]
41 MsgMenu {
42 touser: String,
43 msgmenu: MsgMenuContent,
44 },
45}
46
47#[derive(Debug, Clone, Serialize)]
48pub struct TextContent {
49 pub content: String,
50}
51
52#[derive(Debug, Clone, Serialize)]
53pub struct MediaContent {
54 pub media_id: String,
55}
56
57#[derive(Debug, Clone, Serialize)]
58pub struct VideoContent {
59 pub media_id: String,
60 pub thumb_media_id: String,
61 #[serde(skip_serializing_if = "Option::is_none")]
62 pub title: Option<String>,
63 #[serde(skip_serializing_if = "Option::is_none")]
64 pub description: Option<String>,
65}
66
67#[derive(Debug, Clone, Serialize)]
68pub struct MusicContent {
69 pub title: String,
70 pub description: String,
71 pub musicurl: String,
72 pub hqmusicurl: String,
73 pub thumb_media_id: String,
74}
75
76#[derive(Debug, Clone, Serialize)]
77pub struct NewsContent {
78 pub articles: Vec<NewsArticle>,
79}
80
81#[derive(Debug, Clone, Serialize)]
82pub struct NewsArticle {
83 pub title: String,
84 pub description: String,
85 pub url: String,
86 pub picurl: String,
87}
88
89#[derive(Debug, Clone, Serialize)]
90pub struct MsgMenuContent {
91 pub head_content: String,
92 pub list: Vec<MsgMenuItem>,
93 pub tail_content: String,
94}
95
96#[derive(Debug, Clone, Serialize)]
97pub struct MsgMenuItem {
98 pub id: String,
99 pub content: String,
100}
101
102#[derive(Deserialize)]
103#[allow(dead_code)]
104struct SendResponse {
105 errcode: i64,
106 errmsg: String,
107}
108
109impl WeChatClient {
110 pub async fn send_customer_service_message(
112 &self,
113 message: &CustomerServiceMessage,
114 ) -> Result<()> {
115 let _: SendResponse = self.post_json("/message/custom/send", message).await?;
116 Ok(())
117 }
118
119 pub async fn send_text(&self, to_user: &str, content: &str) -> Result<()> {
121 let message = CustomerServiceMessage::Text {
122 touser: to_user.to_string(),
123 text: TextContent {
124 content: content.to_string(),
125 },
126 };
127 self.send_customer_service_message(&message).await
128 }
129
130 pub async fn send_image(&self, to_user: &str, media_id: &str) -> Result<()> {
132 let message = CustomerServiceMessage::Image {
133 touser: to_user.to_string(),
134 image: MediaContent {
135 media_id: media_id.to_string(),
136 },
137 };
138 self.send_customer_service_message(&message).await
139 }
140
141 pub async fn send_voice(&self, to_user: &str, media_id: &str) -> Result<()> {
143 let message = CustomerServiceMessage::Voice {
144 touser: to_user.to_string(),
145 voice: MediaContent {
146 media_id: media_id.to_string(),
147 },
148 };
149 self.send_customer_service_message(&message).await
150 }
151
152 pub async fn send_video(
154 &self,
155 to_user: &str,
156 media_id: &str,
157 thumb_media_id: &str,
158 title: Option<&str>,
159 description: Option<&str>,
160 ) -> Result<()> {
161 let message = CustomerServiceMessage::Video {
162 touser: to_user.to_string(),
163 video: VideoContent {
164 media_id: media_id.to_string(),
165 thumb_media_id: thumb_media_id.to_string(),
166 title: title.map(|s| s.to_string()),
167 description: description.map(|s| s.to_string()),
168 },
169 };
170 self.send_customer_service_message(&message).await
171 }
172
173 pub async fn send_news(&self, to_user: &str, articles: Vec<NewsArticle>) -> Result<()> {
175 let message = CustomerServiceMessage::News {
176 touser: to_user.to_string(),
177 news: NewsContent { articles },
178 };
179 self.send_customer_service_message(&message).await
180 }
181
182 pub async fn set_typing(&self, to_user: &str, typing: bool) -> Result<()> {
184 let command = if typing { "Typing" } else { "CancelTyping" };
185 let body = json!({
186 "touser": to_user,
187 "command": command
188 });
189 let _: SendResponse = self.post_json("/message/custom/typing", &body).await?;
190 Ok(())
191 }
192}