Skip to main content

fishpi_sdk/api/
user.rs

1//! # 用户 API 模块
2//!
3//! 这个模块提供了与用户相关的 API 操作,包括获取用户信息、查询表情、活跃度、签到、转账、关注、修改头像和用户信息等功能。
4//! 主要结构体是 `User`,用于管理用户的 API 请求。
5//!
6//! ## 主要组件
7//!
8//! - [`User`] - 用户客户端结构体,负责所有用户相关的 API 调用。
9//!
10//! ## 方法列表
11//!
12//! - [`User::new`] - 创建新的用户客户端实例。
13//! - [`User::get_token`] - 获取当前 API token。
14//! - [`User::set_token`] - 重新设置请求 token。
15//! - [`User::is_logined`] - 检查用户是否已登录(API key 是否为空)。
16//! - [`User::info`] - 返回登录账户信息。
17//! - [`User::emotions`] - 查询登录用户常用表情。
18//! - [`User::liveness`] - 查询登录用户当前活跃度。
19//! - [`User::is_checkin`] - 检查用户是否已经签到。
20//! - [`User::is_collected_liveness`] - 检查用户是否领取昨日活跃奖励。
21//! - [`User::reward_liveness`] - 领取昨日活跃度奖励。
22//! - [`User::transfer`] - 转账。
23//! - [`User::follow`] - 关注用户。
24//! - [`User::unfollow`] - 取消关注用户。
25//! - [`User::update_avatar`] - 修改用户头像。
26//! - [`User::update_user_info`] - 修改用户信息。
27//! - [`User::get_user`] - 获取其他用户信息。
28//! - [`User::report`] - 举报。
29//! - [`User::upload`] - 上传文件。
30//! - [`User::get_points`] - 获取用户积分。
31//!
32//! ## 示例
33//!
34//! ```rust,no_run
35//! use fishpi_sdk::{FishPi, model::misc::LoginData};
36//! use fishpi_sdk::model::user::UpdateUserInfoParams;
37//!
38//! #[tokio::main]
39//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
40//!     // 登录获取用户客户端
41//!     let login_data = LoginData::new("your_name_or_email", "your_password", None);
42//!     let user = FishPi::login(&login_data).await?;
43//!
44//!     // 获取用户信息
45//!     let info = user.info().await?;
46//!     println!("User name: {}", info.name());
47//!
48//!     // 查询表情
49//!     let emotions = user.emotions().await?;
50//!     println!("Emotions: {:?}", emotions);
51//!
52//!     // 转账
53//!     user.transfer("target_user", 100, "Gift").await?;
54//!
55//!     // 修改用户信息
56//!     let params = UpdateUserInfoParams {
57//!         nickName: Some("New Name".to_string()),
58//!         userUrl: Some("https://example.com".to_string()),
59//!         userIntro: Some("New intro".to_string()),
60//!         userTag: Some("tag".to_string()),
61//!         mbti: None,
62//!     };
63//!     user.update_user_info(params).await?;
64//!
65//!     Ok(())
66//! }
67//! ```
68use crate::api::article::Article;
69use crate::api::breezemoon::BreezeMoon;
70use crate::api::chat::Chat;
71use crate::api::chatroom::ChatRoom;
72use crate::api::comment::Comment;
73use crate::api::notice::Notice;
74use crate::api::redpacket::Redpacket;
75use crate::model::misc::{Report, UploadResult};
76use crate::model::user::{UpdateUserInfoParams, UserInfo, UserPoint};
77use crate::utils::error::Error;
78use crate::utils::{ResponseResult, build_http_path, get, post, upload_files};
79use serde_json::{Value, json};
80
81pub struct User {
82    api_key: String,
83    pub chatroom: ChatRoom,
84    pub chat: Chat,
85    pub breezemoon: BreezeMoon,
86    pub article: Article,
87    pub notice: Notice,
88    pub redpacket: Redpacket,
89    pub comment: Comment,
90}
91
92impl User {
93    pub fn new(api_key: String) -> Self {
94        Self {
95            api_key: api_key.clone(),
96            chatroom: ChatRoom::new(api_key.clone()),
97            chat: Chat::new(api_key.clone()),
98            breezemoon: BreezeMoon::new(api_key.clone()),
99            article: Article::new(api_key.clone()),
100            notice: Notice::new(api_key.clone()),
101            redpacket: Redpacket::new(api_key.clone()),
102            comment: Comment::new(api_key.clone()),
103        }
104    }
105
106    pub fn get_token(&self) -> &str {
107        &self.api_key
108    }
109
110    /// 重新设置请求token
111    pub fn set_token(&mut self, token: String) {
112        self.api_key = token.clone();
113        self.chatroom.set_api_key(token.clone());
114        self.chat = Chat::new(token.clone());
115        self.breezemoon = BreezeMoon::new(token.clone());
116        self.article = Article::new(token.clone());
117        self.notice = Notice::new(token.clone());
118        self.redpacket = Redpacket::new(token.clone());
119        self.comment = Comment::new(token);
120    }
121
122    pub fn is_logined(&self) -> bool {
123        !self.api_key.is_empty()
124    }
125
126    /// 返回登录账户信息,需要先登录或设置有效api_key
127    pub async fn info(&self) -> Result<UserInfo, Error> {
128        let mut resp = get(&build_http_path("api/user", &[("apiKey", self.api_key.clone())])).await?;
129
130        if resp["code"] != 0 {
131            return Err(Error::Api(
132                resp["msg"].as_str().unwrap_or("API error").to_string(),
133            ));
134        }
135
136        let data_value = if let Some(data_str) = resp["data"].as_str() {
137            serde_json::from_str(data_str).map_err(|e| Error::Api(e.to_string()))?
138        } else {
139            resp["data"].take()
140        };
141
142        UserInfo::from_value(&data_value)
143    }
144
145    /// 查询登录用户常用表情
146    pub async fn emotions(&self) -> Result<Vec<String>, Error> {
147        let mut resp = get(&build_http_path("users/emotions", &[("apiKey", self.api_key.clone())])).await?;
148
149        if resp["code"] != 0 {
150            return Err(Error::Api(
151                resp["msg"].as_str().unwrap_or("API error").to_string(),
152            ));
153        }
154
155        let data: Vec<Value> =
156            serde_json::from_value(resp["data"].take()).map_err(|e| Error::Parse(format!("Failed to parse emotions: {}", e)))?;
157        let emotions: Vec<String> = data
158            .into_iter()
159            .filter_map(|v| {
160                v.as_object()
161                    .and_then(|obj| obj.values().next())
162                    .and_then(|val| val.as_str())
163                    .map(|s| s.to_string())
164            })
165            .collect();
166        Ok(emotions)
167    }
168
169    /// 查询登录用户当前活跃度,请求频率请至少 10 分钟一次
170    pub async fn liveness(&self) -> Result<u32, Error> {
171        let resp = get(&build_http_path("user/liveness", &[("apiKey", self.api_key.clone())])).await?;
172
173        let liveness_raw = resp
174            .get("liveness")
175            .and_then(|v| {
176                v.as_f64()
177                    .or_else(|| v.as_u64().map(|n| n as f64))
178                    .or_else(|| v.as_i64().map(|n| n as f64))
179                    .or_else(|| v.as_str().and_then(|s| s.parse::<f64>().ok()))
180            })
181            .ok_or_else(|| Error::Api("Missing or invalid liveness".to_string()))?;
182
183        Ok(liveness_raw.max(0.0).round() as u32)
184    }
185
186    /// 检查用户是否已经签到
187    pub async fn is_checkin(&self) -> Result<bool, Error> {
188        let resp = get(&build_http_path("user/isCheckin", &[("apiKey", self.api_key.clone())])).await?;
189
190        let is_checkin: bool = resp["isCheckin"].as_bool().unwrap_or(false);
191        Ok(is_checkin)
192    }
193
194    /// 检查用户是否领取昨日活跃奖励
195    pub async fn is_collected_liveness(&self) -> Result<bool, Error> {
196        let resp = get(&build_http_path(
197            "api/activity/is-collected-liveness",
198            &[("apiKey", self.api_key.clone())],
199        ))
200        .await?;
201
202        let is_rewarded: bool = resp["isLivenessRewarded"].as_bool().unwrap_or(false);
203        Ok(is_rewarded)
204    }
205
206    /// 领取昨日活跃度奖励
207    pub async fn reward_liveness(&self) -> Result<u32, Error> {
208        let resp = get(&build_http_path(
209            "activity/yesterday-liveness-reward-api",
210            &[("apiKey", self.api_key.clone())],
211        ))
212        .await?;
213
214        let success: u32 = resp["sum"].as_u64().unwrap_or(0) as u32;
215        Ok(success)
216    }
217
218    /// 转账
219    pub async fn transfer(&self, username: &str, amount: u32, memo: &str) -> Result<bool, Error> {
220        let data = json!({
221            "username": username,
222            "amount": amount,
223            "memo": memo,
224            "apiKey": self.api_key,
225        });
226
227        let resp = post("point/transfer", Some(data)).await?;
228
229        if resp["code"] != 0 {
230            return Err(Error::Api(
231                resp["msg"].as_str().unwrap_or("API error").to_string(),
232            ));
233        }
234
235        Ok(true)
236    }
237
238    /// 关注用户
239    pub async fn follow(&self, following_id: &str) -> Result<bool, Error> {
240        let data = json!({
241            "followingId": following_id,
242            "apiKey": self.api_key,
243        });
244
245        let resp = post("follow/user", Some(data)).await?;
246
247        if resp["code"] != 0 {
248            return Err(Error::Api(
249                resp["msg"].as_str().unwrap_or("API error").to_string(),
250            ));
251        }
252
253        Ok(true)
254    }
255
256    /// 取消关注用户
257    pub async fn unfollow(&self, following_id: &str) -> Result<bool, Error> {
258        let data = json!({
259            "followingId": following_id,
260            "apiKey": self.api_key,
261        });
262
263        let resp = post("unfollow/user", Some(data)).await?;
264
265        if resp["code"] != 0 {
266            return Err(Error::Api(
267                resp["msg"].as_str().unwrap_or("API error").to_string(),
268            ));
269        }
270
271        Ok(true)
272    }
273
274    /// 修改用户头像
275    pub async fn update_avatar(&self, avatar_url: &str) -> Result<bool, Error> {
276        let data = json!({
277            "userAvatarURL": avatar_url,
278            "apiKey": self.api_key
279        });
280
281        let resp = post("api/settings/avatar", Some(data)).await?;
282
283        if resp["code"] != 0 {
284            return Err(Error::Api(
285                resp["msg"].as_str().unwrap_or("API error").to_string(),
286            ));
287        }
288
289        Ok(true)
290    }
291
292    /// 修改用户信息
293    ///
294    /// #### 参数
295    /// * `params` 用户信息参数 [UpdateUserInfoParams]
296    pub async fn update_user_info(&self, params: UpdateUserInfoParams) -> Result<bool, Error> {
297        let data = json!({
298            "userNickname": params.nickName,
299            "userURL": params.userUrl,
300            "userIntro": params.userIntro,
301            "userTag": params.userTag,
302            "apiKey": self.api_key,
303        });
304
305        let resp = post("api/settings/profiles", Some(data)).await?;
306
307        if resp["code"] != 0 {
308            return Err(Error::Api(
309                resp["msg"].as_str().unwrap_or("API error").to_string(),
310            ));
311        }
312
313        Ok(true)
314    }
315
316    /// 获取用户信息
317    ///
318    /// - `username` 用户名
319    ///
320    /// 返回用户信息
321    pub async fn get_user(&self, username: &str) -> Result<UserInfo, Error> {
322        let url = build_http_path(
323            &format!("user/{}", username),
324            &[("apiKey", self.api_key.clone())],
325        );
326
327        let rsp = get(&url).await?;
328
329        if rsp.get("code").and_then(|c| c.as_i64()).unwrap_or(0) != 0 {
330            return Err(Error::Api(
331                rsp["msg"].as_str().unwrap_or("API error").to_string(),
332            ));
333        }
334
335        UserInfo::from_value(&rsp)
336    }
337
338    /// 举报
339    ///
340    /// - `data` 举报数据 [Report]
341    ///
342    /// 返回举报结果
343    pub async fn report(&self, data: &Report) -> Result<ResponseResult, Error> {
344        let url = "report".to_string();
345
346        let mut data_json = serde_json::to_value(data)
347            .map_err(|e| Error::Parse(format!("Failed to serialize Report: {}", e)))?;
348        data_json["apiKey"] = Value::String(self.api_key.clone());
349
350        let rsp = post(&url, Some(data_json)).await?;
351
352        ResponseResult::from_value(&rsp)
353    }
354
355    /// 上传文件
356    ///
357    /// - `files` 文件路径列表
358    ///
359    /// 返回上传结果
360    pub async fn upload(&self, files: Vec<String>) -> Result<UploadResult, Error> {
361        // 检查文件是否存在
362        for file in &files {
363            if !std::path::Path::new(file).exists() {
364                return Err(Error::Api(format!("File not exist: {}", file)));
365            }
366        }
367
368        let url = "upload".to_string();
369        let rsp = upload_files(&url, files, &self.api_key).await?;
370
371        if rsp.get("code").and_then(|c| c.as_i64()).unwrap_or(-1) != 0 {
372            return Err(Error::Api(
373                rsp["msg"].as_str().unwrap_or("API error").to_string(),
374            ));
375        }
376
377        UploadResult::from_value(&rsp["data"])
378    }
379
380    /// 获取用户积分
381    /// 
382    /// - `username` 用户名
383    ///   返回用户积分信息 [UserPoint]
384    pub async fn get_points(&self, username: &str) -> Result<UserPoint, Error> {
385        let resp = get(&format!("user/{}/point", username)).await?;
386
387        if resp.get("code").and_then(|c| c.as_i64()).unwrap_or(-1) != 0 {
388            return Err(Error::Api(
389                resp["msg"].as_str().unwrap_or("API error").to_string(),
390            ));
391        }
392
393        UserPoint::from_value(&resp)
394    }
395}