fishpi_sdk/
lib.rs

1//! # 摸鱼派 Rust SDK
2//!
3//! 这是一个用于与摸鱼派社区 API 交互的 Rust SDK,提供用户管理、文章、聊天室、私聊、通知、清风明月、红包、评论、举报、日志、文件上传等功能的异步客户端。
4//!
5//! ## 主要组件
6//!
7//! - [`FishPi`] - 静态客户端,提供不需要认证的操作(如登录、注册、验证)。
8//! - [`api`] - API 客户端模块,包含各个子模块(如用户、文章等)。
9//! - [`model`] - 数据模型模块,定义请求和响应的数据结构。
10//! - [`utils`] - 工具模块,提供 HTTP 请求、错误处理等辅助功能。
11//!
12//! ## 功能特性
13//!
14//! - **异步支持**: 使用 `tokio` 提供异步 API 调用。
15//! - **类型安全**: 使用 Serde 进行序列化/反序列化,确保数据类型安全。
16//! - **错误处理**: 统一的错误类型和处理机制。
17//! - **文件上传**: 支持多文件上传。
18//!
19//! ## 示例
20//!
21//! ```rust,no_run
22//! use fishpi_sdk::{FishPi, api::user::User};
23//!
24//! #[tokio::main]
25//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
26//!     // 登录获取用户实例
27//!     let user = FishPi::login(&login_data).await?;
28//!
29//!     // 获取用户信息
30//!     let user_info = user.info().await?;
31//!
32//!     // 发送评论
33//!     let result = user.comment.send(&comment_data).await?;
34//!
35//!     Ok(())
36//! }
37//! ```
38pub mod api;
39pub mod model;
40pub mod utils;
41
42use serde_json::{Value, json};
43
44use crate::{
45    api::user::User,
46    model::{
47        misc::{Log, LoginData, PreRegisterInfo, RegisterInfo, UserLite, UserVipInfo},
48        user::AtUser,
49    },
50    utils::{ResponseResult, error::Error, get, post},
51};
52
53/// 摸鱼派 Rust SDK 接口
54pub struct FishPi;
55
56impl FishPi {
57    /// 登录
58    ///
59    /// - `data` 登录账密
60    ///
61    /// 返回用户实例
62    pub async fn login(data: &LoginData) -> Result<User, Error> {
63        let url = "api/getKey".to_string();
64
65        let data_json = data.to_value()?;
66
67        let rsp = post(&url, Some(data_json)).await?;
68
69        if rsp.get("code").and_then(|c| c.as_i64()).unwrap_or(-1) != 0 {
70            return Err(Error::Api(
71                rsp["msg"].as_str().unwrap_or("API error").to_string(),
72            ));
73        }
74
75        let token = rsp["Key"].as_str().unwrap_or("").trim().to_string();
76
77        Ok(User::new(token))
78    }
79
80    /// 预注册
81    ///
82    /// - `data` 预注册数据
83    ///
84    /// 返回预注册结果
85    pub async fn pre_register(data: &PreRegisterInfo) -> Result<ResponseResult, Error> {
86        let url = "register".to_string();
87
88        let data_json = serde_json::to_value(data)
89            .map_err(|e| Error::Parse(format!("Failed to serialize PreRegisterInfo: {}", e)))?;
90
91        let rsp = post(&url, Some(data_json)).await?;
92
93        ResponseResult::from_value(&rsp)
94    }
95
96    /// 验证手机验证码
97    ///
98    /// - `code` 验证码
99    ///
100    /// 返回用户 ID
101    pub async fn verify(code: &str) -> Result<String, Error> {
102        let url = format!("verify?code={}", code);
103
104        let rsp = get(&url).await?;
105
106        if rsp.get("code").and_then(|c| c.as_i64()).unwrap_or(-1) != 0 {
107            return Err(Error::Api(
108                rsp["msg"].as_str().unwrap_or("API error").to_string(),
109            ));
110        }
111
112        Ok(rsp["userId"].as_str().unwrap_or("").to_string())
113    }
114
115    /// 注册
116    ///
117    /// - `data` 注册数据 [RegisterInfo]
118    ///
119    /// 返回注册结果
120    pub async fn register(data: &RegisterInfo) -> Result<ResponseResult, Error> {
121        let url = if let Some(r) = &data.r {
122            format!("register2?r={}", r)
123        } else {
124            "register2".to_string()
125        };
126
127        let data_json = serde_json::to_value(data)
128            .map_err(|e| Error::Parse(format!("Failed to serialize RegisterInfo: {}", e)))?;
129
130        let rsp = post(&url, Some(data_json)).await?;
131
132        if let Some(code) = rsp.get("code").and_then(|c| c.as_i64())
133            && code != 0
134        {
135            return Err(Error::Api(
136                rsp["msg"].as_str().unwrap_or("API error").to_string(),
137            ));
138        }
139
140        ResponseResult::from_value(&rsp)
141    }
142
143    /// 获取用户名联想
144    ///
145    /// - `name` 用户名
146    ///
147    /// 返回用户名联想列表
148    pub async fn names(name: &str) -> Result<Vec<AtUser>, Error> {
149        let url = "users/names".to_string();
150
151        let data_json = json!({
152            "name": name,
153        });
154
155        let rsp = post(&url, Some(data_json)).await?;
156
157        if rsp.get("code").and_then(|c| c.as_i64()).unwrap_or(0) != 0 {
158            return Err(Error::Api(
159                rsp["msg"].as_str().unwrap_or("API error").to_string(),
160            ));
161        }
162
163        let at_users = rsp["data"]
164            .as_array()
165            .ok_or_else(|| Error::Api("Data is not an array".to_string()))?
166            .iter()
167            .map(AtUser::from_value)
168            .collect::<Result<Vec<AtUser>, _>>()?;
169
170        Ok(at_users)
171    }
172
173    /// 获取最近注册的 20 个用户
174    ///
175    /// 返回用户列表
176    pub async fn recent_register() -> Result<Vec<UserLite>, Error> {
177        let url = "api/user/recentReg".to_string();
178
179        let rsp = get(&url).await?;
180
181        let user_lites = rsp["data"]
182            .as_array()
183            .ok_or_else(|| Error::Api("Data is not an array".to_string()))?
184            .iter()
185            .map(UserLite::from_value)
186            .collect::<Result<Vec<UserLite>, _>>()?;
187
188        Ok(user_lites)
189    }
190
191    /// 获取用户VIP信息
192    ///
193    /// - `user_id` 用户ID
194    ///
195    /// 返回用户VIP信息
196    pub async fn vip_info(user_id: &str) -> Result<UserVipInfo, Error> {
197        let url = format!("api/membership/{}", user_id);
198
199        let rsp = get(&url).await?;
200
201        if rsp.get("code").and_then(|c| c.as_i64()).unwrap_or(0) != 0 {
202            return Err(Error::Api(
203                rsp["msg"].as_str().unwrap_or("API error").to_string(),
204            ));
205        }
206
207        let data_obj = rsp["data"]
208            .as_object()
209            .ok_or_else(|| Error::Api("Data is not an object".to_string()))?;
210
211        let config_json_str = data_obj
212            .get("configJson")
213            .and_then(|v| v.as_str())
214            .unwrap_or("null");
215        let mut data: Value = serde_json::from_str(config_json_str).unwrap_or_else(|_| json!({}));
216
217        if let Some(data_map) = data.as_object_mut() {
218            data_map.insert(
219                "state".to_string(),
220                data_obj.get("state").cloned().unwrap_or(Value::Null),
221            );
222            if data_obj.get("state").and_then(|v| v.as_i64()).unwrap_or(0) == 1 {
223                data_map.insert(
224                    "oId".to_string(),
225                    data_obj.get("oId").cloned().unwrap_or(Value::Null),
226                );
227                data_map.insert(
228                    "userId".to_string(),
229                    data_obj.get("userId").cloned().unwrap_or(Value::Null),
230                );
231                data_map.insert(
232                    "lvCode".to_string(),
233                    data_obj.get("lvCode").cloned().unwrap_or(Value::Null),
234                );
235                data_map.insert(
236                    "expiresAt".to_string(),
237                    data_obj.get("expiresAt").cloned().unwrap_or(Value::Null),
238                );
239                data_map.insert(
240                    "createdAt".to_string(),
241                    data_obj.get("createdAt").cloned().unwrap_or(Value::Null),
242                );
243                data_map.insert(
244                    "updatedAt".to_string(),
245                    data_obj.get("updatedAt").cloned().unwrap_or(Value::Null),
246                );
247            }
248        }
249
250        UserVipInfo::from_value(&data)
251    }
252
253    /// 获取操作日志
254    ///
255    /// - `page` 页码
256    /// - `page_size` 每页数量
257    ///
258    /// 返回日志列表
259    pub async fn log(page: u32, page_size: u32) -> Result<Vec<Log>, Error> {
260        let url = format!("logs/more?page={}&pageSize={}", page, page_size);
261
262        let rsp = get(&url).await?;
263
264        let logs = rsp["data"]
265            .as_array()
266            .ok_or_else(|| Error::Api("Data is not an array".to_string()))?
267            .iter()
268            .map(Log::from_value)
269            .collect::<Result<Vec<Log>, _>>()?;
270
271        Ok(logs)
272    }
273}