async_wechat/official_account/
qrcode.rs1use std::{any::TypeId, str::FromStr};
2
3use crate::OfficialAccount;
4
5use serde::{Deserialize, Serialize};
6use urlencoding::encode;
7
8pub(crate) const QR_CREATE_URL: &str =
9 "https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token=";
10pub(crate) const QR_IMG_URL: &str = "https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket=";
11
12#[cfg(test)]
13mod tests {
14 use uuid::Uuid;
15
16 use crate::{
17 Config, OfficialAccount,
18 official_account::qrcode::{self, TicketRequest},
19 };
20 use std::env;
21
22 #[tokio::test]
23 #[ignore]
24 async fn get_qr_ticket() {
25 dotenv::dotenv().ok();
26
27 let appid = env::var("APPID").expect("APPID not set");
28 let app_secret = env::var("APP_SECRET").expect("APP_SECRET not set");
29 let redis_url = env::var("REDIS_URL").expect("REDIS_URL not set");
30
31 let config = Config {
32 appid: appid.clone(),
33 app_secret: app_secret.clone(),
34 token: "wechat".to_string(),
35 encoding_aes_key: None,
36 };
37 let account = OfficialAccount::new(config, redis_url);
38
39 let key = format!("login:{}", Uuid::new_v4().to_string());
40
41 let scene = qrcode::TicketScene {
42 scene_id: None,
43 scene_str: Some(key.clone()),
44 };
45 let params = TicketRequest {
46 expire_seconds: 200000,
47 action_name: qrcode::TicketActionName::QrStrScene,
48 action_info: qrcode::TicketActionInfo { scene },
49 };
50
51 let at = account.get_qr_ticket(params).await;
52 println!("get_qr_ticket: {:#?}", at.unwrap().show_qrcode());
53 }
54}
55
56#[derive(Debug, Serialize, Deserialize)]
57#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
58pub enum TicketActionName {
59 QrScene, QrStrScene,
61 QrLimitScene,
62 QrLimitStrScene,
63}
64
65#[derive(Serialize, Deserialize, Debug)]
66pub struct TicketActionInfo {
67 pub scene: TicketScene,
68}
69
70#[derive(Serialize, Deserialize, Debug)]
71pub struct TicketScene {
72 #[serde(skip_serializing_if = "Option::is_none")]
73 pub scene_id: Option<u32>,
74 #[serde(skip_serializing_if = "Option::is_none")]
75 pub scene_str: Option<String>,
76}
77
78#[derive(Serialize, Deserialize, Debug)]
79pub struct TicketRequest {
80 pub expire_seconds: u64,
81 pub action_name: TicketActionName,
82 pub action_info: TicketActionInfo,
83}
84
85#[derive(Serialize, Deserialize, Debug)]
86pub struct TicketResponse {
87 pub ticket: String,
88 pub expire_seconds: u64,
89 pub url: String,
90}
91
92pub fn build_tmp_qr_request<T>(exp: u64, scene: T) -> TicketRequest
93where
94 T: 'static + ToString + FromStr,
95 <T as FromStr>::Err: std::fmt::Debug,
96{
97 let type_id = TypeId::of::<T>();
98 let mut ticket_scene = TicketScene {
99 scene_id: None,
100 scene_str: None,
101 };
102
103 let action_name;
104
105 if type_id == TypeId::of::<String>() || type_id == TypeId::of::<&str>() {
106 action_name = TicketActionName::QrStrScene;
107 ticket_scene.scene_str = Some(scene.to_string());
108 } else {
109 action_name = TicketActionName::QrScene;
110 let parsed_id = scene
111 .to_string()
112 .parse::<u32>()
113 .expect("scene parse failed");
114 ticket_scene.scene_id = Some(parsed_id);
115 }
116
117 let expire_seconds = exp.clamp(60, 2592000);
118
119 TicketRequest {
120 expire_seconds,
121 action_name,
122 action_info: TicketActionInfo {
123 scene: ticket_scene,
124 },
125 }
126}
127
128impl TicketResponse {
129 pub fn show_qrcode(&self) -> String {
138 let ticket = encode(&self.ticket);
139 format!("{}{}", QR_IMG_URL, ticket)
140 }
141}
142
143impl OfficialAccount {
144 pub async fn get_qr_ticket(
154 &self,
155 req: TicketRequest,
156 ) -> Result<TicketResponse, Box<dyn std::error::Error>> {
157 println!("req: {:#?}", &req);
158 let token = self.token().await?;
159
160 let url = format!("{}{}", QR_CREATE_URL, token);
161 let response = match self.client.post(url).json(&req).send().await {
162 Ok(r) => r,
163 Err(e) => {
164 println!("json err: {:#?}", e);
165 return Err(e.into());
166 }
167 };
168
169 let result = match response.json::<TicketResponse>().await {
170 Ok(r) => r,
171 Err(e) => {
172 println!("json err: {:#?}", e);
173 return Err(e.into());
174 }
175 };
176
177 Ok(result)
178 }
179}