1use std::collections::HashMap;
6
7use roxmltree::Document;
8
9use crate::{
10 access_token::AccessTokenProvider,
11 error::{CommonError, SdkError},
12 mp::event::signature::Signature,
13};
14use crate::{
15 wechat::{WxApiRequestBuilder, WxSdk},
16 SdkResult,
17};
18
19use self::{
20 customservice::CustomServiceModule, datacube::DataCubeModule, draft::DraftModule,
21 freepublish::FreePublishModule, material::MaterialModule, media::MediaModule, menu::MenuModule,
22 message::MessageModule, qrcode::QrcodeModule, reply::Reply, shorten::ShortenModule,
23 sns::SnsModule, tags::TagsModule, template::TemplateModule, ticket::TicketModule,
24 user::UserModule,
25};
26pub mod customservice;
27pub mod datacube;
28pub mod draft;
29pub mod event;
30pub mod freepublish;
31pub mod material;
32pub mod media;
33pub mod menu;
34pub mod message;
35pub mod qrcode;
36pub mod reply;
37pub mod shorten;
38pub mod sns;
39pub mod tags;
40pub mod template;
41pub mod ticket;
42pub mod user;
43
44#[derive(Clone)]
46pub struct ServerConfig {
47 pub token: String,
48 pub encoding_mode: EncodingMode,
49}
50
51type AesKey = String;
52
53#[derive(Clone)]
56pub enum EncodingMode {
57 Plain,
58 Compat(AesKey),
59 Security(AesKey),
60}
61
62impl ServerConfig {
63 pub fn new<S: AsRef<str>>(token: S, encoding_mode: EncodingMode) -> Self {
64 ServerConfig {
65 token: token.as_ref().to_owned(),
66 encoding_mode,
67 }
68 }
69}
70
71#[derive(Clone)]
73pub struct MpSdk<T: AccessTokenProvider> {
74 pub(crate) sdk: WxSdk<T>,
75 pub(crate) server_config: ServerConfig,
76}
77
78impl<T: AccessTokenProvider> MpSdk<T> {
79 pub async fn clear_quota(&self) -> SdkResult<()> {
84 let base_url = "https://api.weixin.qq.com/cgi-bin/clear_quota";
85 let sdk = &self.sdk;
86 let app_id = sdk.app_id.clone();
87 let res = sdk
88 .wx_post(base_url)
89 .await?
90 .json(&serde_json::json!({ "appid": app_id }))
91 .send()
92 .await?
93 .json::<CommonError>()
94 .await?;
95
96 res.into()
97 }
98
99 pub fn qrcode(&self) -> QrcodeModule<WxSdk<T>> {
101 QrcodeModule(&self.sdk)
102 }
103 pub fn shorten(&self) -> ShortenModule<WxSdk<T>> {
105 ShortenModule(&self.sdk)
106 }
107 pub fn tags(&self) -> TagsModule<WxSdk<T>> {
109 TagsModule(&self.sdk)
110 }
111 pub fn user(&self) -> UserModule<WxSdk<T>> {
113 UserModule(&self.sdk)
114 }
115 pub fn message(&self) -> MessageModule<WxSdk<T>> {
117 MessageModule(&self.sdk)
118 }
119 pub fn menu(&self) -> MenuModule<WxSdk<T>> {
121 MenuModule(&self.sdk)
122 }
123 pub fn template(&self) -> TemplateModule<WxSdk<T>> {
125 TemplateModule(&self.sdk)
126 }
127
128 pub fn media(&self) -> MediaModule<WxSdk<T>> {
130 MediaModule(&self.sdk)
131 }
132
133 pub fn material(&self) -> MaterialModule<WxSdk<T>> {
135 MaterialModule(&self.sdk)
136 }
137
138 pub fn datacube(&self) -> DataCubeModule<WxSdk<T>> {
140 DataCubeModule(&self.sdk)
141 }
142
143 pub fn customservice(&self) -> CustomServiceModule<WxSdk<T>> {
145 CustomServiceModule(&self.sdk)
146 }
147
148 pub fn ticket(&self) -> TicketModule<T> {
150 TicketModule(&self.sdk)
151 }
152
153 pub fn sns(&self) -> SnsModule<T> {
155 SnsModule(&self.sdk)
156 }
157
158 pub fn draft(&self) -> DraftModule<T> {
160 DraftModule(&self.sdk)
161 }
162
163 pub fn freepublish(&self) -> FreePublishModule<T> {
165 FreePublishModule(&self.sdk)
166 }
167
168 pub fn parse_received_msg<S: AsRef<str>>(
170 &self,
171 msg: S,
172 url_params: Option<HashMap<String, String>>,
173 ) -> SdkResult<event::ReceivedEvent> {
174 let server_config = &self.server_config;
175 let msg = match server_config.encoding_mode {
176 EncodingMode::Plain => event::ReceivedEvent::parse(msg.as_ref()),
177 EncodingMode::Compat(_) => event::ReceivedEvent::parse(msg.as_ref()),
178 EncodingMode::Security(ref aes_key) => {
179 let url_params = url_params
180 .ok_or_else(|| SdkError::InvalidParams("needs url_params".to_owned()))?;
181 let signature = url_params
182 .get("msg_signature")
183 .ok_or_else(|| SdkError::InvalidParams("msg_signature".to_owned()))?;
184 let timestamp = url_params
185 .get("timestamp")
186 .ok_or_else(|| SdkError::InvalidParams("timestamp".to_owned()))?;
187 let nonce = url_params
188 .get("nonce")
189 .ok_or_else(|| SdkError::InvalidParams("nonce".to_owned()))?;
190 let token = server_config.token.clone();
191 let root = Document::parse(msg.as_ref())?;
192 let encrypt_msg = root
193 .descendants()
194 .find(|n| n.has_tag_name("Encrypt"))
195 .map(|n| n.text())
196 .flatten()
197 .unwrap();
198
199 let check_sign = vec![
200 token,
201 timestamp.clone(),
202 nonce.clone(),
203 encrypt_msg.to_owned(),
204 ];
205 let sign = event::signature::Signature::new(signature, check_sign);
206 if !sign.is_ok() {
207 return Err(SdkError::InvalidSignature);
208 }
209 let (msg, app_id) = event::crypto::decrypt_message(encrypt_msg, aes_key)?;
211 if app_id != self.sdk.app_id {
212 return Err(SdkError::InvalidAppid);
213 }
214 event::ReceivedEvent::parse(msg.as_ref())
215 }
216 };
217 msg
218 }
219
220 pub fn reply_to_xml<S: Into<String>>(
222 &self,
223 reply: Reply,
224 from: S,
225 to: S,
226 url_params: Option<HashMap<String, String>>,
227 ) -> SdkResult<String> {
228 let server_config = &self.server_config;
229 let mut reply_xml = reply::reply_to_xml(reply, from, to)?;
230 if let EncodingMode::Security(ref aes_key) = server_config.encoding_mode {
231 let ref app_id = self.sdk.app_id;
232 let encrypt_msg = event::crypto::encrypt_message(&reply_xml, aes_key, app_id)?;
233 let url_params =
234 url_params.ok_or_else(|| SdkError::InvalidParams("needs url_params".to_owned()))?;
235
236 let timestamp = url_params
237 .get("timestamp")
238 .ok_or_else(|| SdkError::InvalidParams("timestamp".to_owned()))?;
239 let nonce = url_params
240 .get("nonce")
241 .ok_or_else(|| SdkError::InvalidParams("nonce".to_owned()))?;
242 let token = server_config.token.clone();
243 let check_sign = vec![
244 token,
245 timestamp.clone(),
246 nonce.clone(),
247 encrypt_msg.to_owned(),
248 ];
249 let msg_signaturet = Signature::generate_signature(check_sign);
250 reply_xml = format!(
251 "<xml>
252<Encrypt><![CDATA[{}]]></Encrypt>
253<MsgSignature><![CDATA[{}]]></MsgSignature>
254<TimeStamp>{}</TimeStamp>
255<Nonce><![CDATA[{}]]></Nonce>
256</xml>",
257 encrypt_msg, msg_signaturet, timestamp, nonce
258 );
259 }
260 Ok(reply_xml)
261 }
262}