1use quick_xml::de::from_str;
2use serde::Deserialize;
3
4use crate::client::WeChatClient;
5use crate::crypto::{
6 check_msg_signature, check_signature, compute_msg_signature, decode_aes_key, decrypt_message,
7 encrypt_message, generate_encrypted_xml, generate_nonce,
8};
9use crate::error::{Result, WeChatError};
10use crate::models::event::*;
11use crate::models::message::*;
12
13#[derive(Debug, Clone)]
15pub enum IncomingMessage {
16 Text(TextMessage),
17 Image(ImageMessage),
18 Voice(VoiceMessage),
19 Video(VideoMessage),
20 ShortVideo(ShortVideoMessage),
21 Location(LocationMessage),
22 Link(LinkMessage),
23 SubscribeEvent(SubscribeEvent),
24 UnsubscribeEvent(SubscribeEvent),
25 ScanEvent(ScanEvent),
26 LocationEvent(LocationEvent),
27 MenuClickEvent(MenuClickEvent),
28 MenuViewEvent(MenuViewEvent),
29 TemplateSendJobFinishEvent(TemplateSendJobFinishEvent),
30 Unknown(String),
31}
32
33impl WeChatClient {
34 pub fn verify_signature(&self, signature: &str, timestamp: &str, nonce: &str) -> bool {
41 check_signature(&self.config.token, signature, timestamp, nonce)
42 }
43
44 pub fn parse_message(&self, xml_body: &str) -> Result<IncomingMessage> {
48 let peek: MsgTypePeek = from_str(xml_body)?;
50 let msg_type = peek.msg_type.to_lowercase();
51
52 let message = match msg_type.as_str() {
53 "text" => IncomingMessage::Text(from_str(xml_body)?),
54 "image" => IncomingMessage::Image(from_str(xml_body)?),
55 "voice" => IncomingMessage::Voice(from_str(xml_body)?),
56 "video" => IncomingMessage::Video(from_str(xml_body)?),
57 "shortvideo" => IncomingMessage::ShortVideo(from_str(xml_body)?),
58 "location" => IncomingMessage::Location(from_str(xml_body)?),
59 "link" => IncomingMessage::Link(from_str(xml_body)?),
60 "event" => self.parse_event(xml_body)?,
61 _ => IncomingMessage::Unknown(xml_body.to_string()),
62 };
63
64 Ok(message)
65 }
66
67 fn parse_event(&self, xml_body: &str) -> Result<IncomingMessage> {
68 let peek: EventTypePeek = from_str(xml_body)?;
69 let event_type = peek.event.to_uppercase();
70
71 let event = match event_type.as_str() {
72 "SUBSCRIBE" => IncomingMessage::SubscribeEvent(from_str(xml_body)?),
73 "UNSUBSCRIBE" => IncomingMessage::UnsubscribeEvent(from_str(xml_body)?),
74 "SCAN" => IncomingMessage::ScanEvent(from_str(xml_body)?),
75 "LOCATION" => IncomingMessage::LocationEvent(from_str(xml_body)?),
76 "CLICK" => IncomingMessage::MenuClickEvent(from_str(xml_body)?),
77 "VIEW" => IncomingMessage::MenuViewEvent(from_str(xml_body)?),
78 "TEMPLATESENDJOBFINISH" => {
79 IncomingMessage::TemplateSendJobFinishEvent(from_str(xml_body)?)
80 }
81 _ => IncomingMessage::Unknown(xml_body.to_string()),
82 };
83
84 Ok(event)
85 }
86
87 fn get_aes_key(&self) -> Result<[u8; 32]> {
93 let encoding_aes_key = self
94 .config
95 .encoding_aes_key
96 .as_ref()
97 .ok_or(WeChatError::AesKeyNotConfigured)?;
98 decode_aes_key(encoding_aes_key)
99 }
100
101 pub fn verify_msg_signature(
105 &self,
106 msg_signature: &str,
107 timestamp: &str,
108 nonce: &str,
109 encrypt_msg: &str,
110 ) -> bool {
111 check_msg_signature(
112 &self.config.token,
113 msg_signature,
114 timestamp,
115 nonce,
116 encrypt_msg,
117 )
118 }
119
120 pub fn parse_encrypted_message(
140 &self,
141 xml_body: &str,
142 msg_signature: &str,
143 timestamp: &str,
144 nonce: &str,
145 ) -> Result<IncomingMessage> {
146 let encrypted_xml: EncryptedXml = from_str(xml_body)?;
148
149 if !self.verify_msg_signature(msg_signature, timestamp, nonce, &encrypted_xml.encrypt) {
151 return Err(WeChatError::InvalidSignature);
152 }
153
154 let aes_key = self.get_aes_key()?;
156 let (decrypted_xml, app_id) = decrypt_message(&aes_key, &encrypted_xml.encrypt)?;
157
158 if app_id != self.config.app_id {
160 return Err(WeChatError::AppIdMismatch);
161 }
162
163 self.parse_message(&decrypted_xml)
165 }
166
167 pub fn decrypt_echostr(&self, encrypted_echostr: &str) -> Result<String> {
178 let aes_key = self.get_aes_key()?;
179 let (decrypted, _app_id) = decrypt_message(&aes_key, encrypted_echostr)?;
180 Ok(decrypted)
181 }
182
183 pub fn encrypt_reply(&self, reply_xml: &str, timestamp: &str, nonce: &str) -> Result<String> {
201 let aes_key = self.get_aes_key()?;
202
203 let encrypted = encrypt_message(&aes_key, &self.config.app_id, reply_xml)?;
205
206 let signature = compute_msg_signature(&self.config.token, timestamp, nonce, &encrypted);
208
209 Ok(generate_encrypted_xml(
211 &encrypted, &signature, timestamp, nonce,
212 ))
213 }
214
215 pub fn encrypt_reply_auto(&self, reply_xml: &str) -> Result<String> {
219 let timestamp = std::time::SystemTime::now()
220 .duration_since(std::time::UNIX_EPOCH)
221 .unwrap()
222 .as_secs()
223 .to_string();
224 let nonce = generate_nonce();
225 self.encrypt_reply(reply_xml, ×tamp, &nonce)
226 }
227}
228
229#[derive(Debug, Deserialize)]
231#[serde(rename_all = "PascalCase")]
232struct EncryptedXml {
233 encrypt: String,
234}