open_wechat/
user.rs

1use std::collections::HashMap;
2
3use async_trait::async_trait;
4use serde::{Deserialize, Serialize};
5use tracing::{event, Level};
6
7use crate::{
8    credential::{GenericAccessToken, GetAccessToken, GetStableAccessToken, StableAccessToken},
9    error::Error::InternalServer,
10    response::Response,
11    Result,
12};
13
14#[derive(Debug, Serialize, Deserialize, Clone)]
15pub struct User {
16    nickname: String,
17    gender: u8,
18    country: String,
19    province: String,
20    city: String,
21    avatar: String,
22    watermark: Watermark,
23}
24
25impl User {
26    pub fn nickname(&self) -> &str {
27        &self.nickname
28    }
29
30    pub fn gender(&self) -> u8 {
31        self.gender
32    }
33
34    pub fn country(&self) -> &str {
35        &self.country
36    }
37
38    pub fn province(&self) -> &str {
39        &self.province
40    }
41
42    pub fn city(&self) -> &str {
43        &self.city
44    }
45
46    pub fn avatar(&self) -> &str {
47        &self.avatar
48    }
49
50    pub fn app_id(&self) -> &str {
51        &self.watermark.app_id
52    }
53
54    pub fn timestamp(&self) -> u64 {
55        self.watermark.timestamp
56    }
57}
58
59#[derive(Debug, Deserialize)]
60#[serde(rename_all = "camelCase")]
61pub(crate) struct UserBuilder {
62    #[serde(rename = "nickName")]
63    nickname: String,
64    gender: u8,
65    country: String,
66    province: String,
67    city: String,
68    #[serde(rename = "avatarUrl")]
69    avatar: String,
70    watermark: WatermarkBuilder,
71}
72
73impl UserBuilder {
74    pub(crate) fn build(self) -> User {
75        User {
76            nickname: self.nickname,
77            gender: self.gender,
78            country: self.country,
79            province: self.province,
80            city: self.city,
81            avatar: self.avatar,
82            watermark: self.watermark.build(),
83        }
84    }
85}
86
87#[derive(Debug, Serialize, Deserialize, Clone)]
88pub struct Contact {
89    phone_number: String,
90    pure_phone_number: String,
91    country_code: String,
92    watermark: Watermark,
93}
94
95impl Contact {
96    pub fn phone_number(&self) -> &str {
97        &self.phone_number
98    }
99
100    pub fn pure_phone_number(&self) -> &str {
101        &self.pure_phone_number
102    }
103
104    pub fn country_code(&self) -> &str {
105        &self.country_code
106    }
107
108    pub fn app_id(&self) -> &str {
109        &self.watermark.app_id
110    }
111
112    pub fn timestamp(&self) -> u64 {
113        self.watermark.timestamp
114    }
115}
116
117#[derive(Debug, Deserialize, Clone)]
118pub(crate) struct ContactBuilder {
119    #[serde(rename = "phone_info")]
120    inner: PhoneInner,
121}
122
123impl ContactBuilder {
124    pub(crate) fn build(self) -> Contact {
125        Contact {
126            phone_number: self.inner.phone_number,
127            pure_phone_number: self.inner.pure_phone_number,
128            country_code: self.inner.country_code,
129            watermark: self.inner.watermark.build(),
130        }
131    }
132}
133
134#[derive(Debug, Deserialize, Clone)]
135#[serde(rename_all = "camelCase")]
136struct PhoneInner {
137    #[serde(rename = "phoneNumber")]
138    phone_number: String,
139    #[serde(rename = "purePhoneNumber")]
140    pure_phone_number: String,
141    country_code: String,
142    watermark: WatermarkBuilder,
143}
144
145#[derive(Debug, Serialize, Deserialize, Clone)]
146struct Watermark {
147    app_id: String,
148    timestamp: u64,
149}
150
151#[derive(Debug, Deserialize, Clone)]
152struct WatermarkBuilder {
153    #[serde(rename = "appid")]
154    app_id: String,
155    timestamp: u64,
156}
157
158impl WatermarkBuilder {
159    fn build(self) -> Watermark {
160        Watermark {
161            app_id: self.app_id,
162            timestamp: self.timestamp,
163        }
164    }
165}
166
167#[async_trait]
168pub trait GetContact {
169    const PHONE: &str = "https://api.weixin.qq.com/wxa/business/getuserphonenumber";
170
171    async fn get_contact(&self, code: &str, open_id: Option<&str>) -> Result<Contact>;
172}
173
174#[async_trait]
175impl GetContact for GenericAccessToken {
176    async fn get_contact(&self, code: &str, open_id: Option<&str>) -> Result<Contact> {
177        use reqwest::Client;
178
179        event!(Level::DEBUG, "code: {}, open_id: {:?}", code, open_id);
180
181        let mut query = HashMap::new();
182        let mut body = HashMap::new();
183
184        query.insert("access_token", self.access_token().await?);
185        body.insert("code", code);
186
187        if let Some(open_id) = open_id {
188            body.insert("openid", open_id);
189        }
190
191        let response = Client::new()
192            .post(Self::PHONE)
193            .query(&query)
194            .json(&body)
195            .send()
196            .await?;
197
198        event!(Level::DEBUG, "response: {:#?}", response);
199
200        if response.status().is_success() {
201            let response = response.json::<Response<ContactBuilder>>().await?;
202
203            let builder = response.extract()?;
204
205            event!(Level::DEBUG, "contact builder: {:#?}", builder);
206
207            Ok(builder.build())
208        } else {
209            Err(InternalServer(response.text().await?))
210        }
211    }
212}
213
214#[async_trait]
215impl GetContact for GenericAccessToken<StableAccessToken> {
216    async fn get_contact(&self, code: &str, open_id: Option<&str>) -> Result<Contact> {
217        use reqwest::Client;
218
219        event!(Level::DEBUG, "code: {}, open_id: {:?}", code, open_id);
220
221        let mut query = HashMap::new();
222        let mut body = HashMap::new();
223
224        query.insert("access_token", self.access_token().await?);
225        body.insert("code", code);
226
227        if let Some(open_id) = open_id {
228            body.insert("openid", open_id);
229        }
230
231        let response = Client::new()
232            .post(Self::PHONE)
233            .query(&query)
234            .json(&body)
235            .send()
236            .await?;
237
238        event!(Level::DEBUG, "response: {:#?}", response);
239
240        if response.status().is_success() {
241            let response = response.json::<Response<ContactBuilder>>().await?;
242
243            let builder = response.extract()?;
244
245            event!(Level::DEBUG, "contact builder: {:#?}", builder);
246
247            Ok(builder.build())
248        } else {
249            Err(InternalServer(response.text().await?))
250        }
251    }
252}