Skip to main content

wechat_oa_sdk/api/
customer_service.rs

1use serde::{Deserialize, Serialize};
2use serde_json::json;
3
4use crate::client::WeChatClient;
5use crate::error::Result;
6
7/// Customer service message types.
8#[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    /// Send a customer service message.
111    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    /// Send a text message via customer service.
120    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    /// Send an image via customer service.
131    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    /// Send a voice message via customer service.
142    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    /// Send a video via customer service.
153    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    /// Send news articles via customer service.
174    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    /// Set typing status for a user.
183    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}