Skip to main content

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, model::misc::LoginData};
23//!
24//! #[tokio::main]
25//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
26//!     let login_data = LoginData::new("your_name_or_email", "your_password", None);
27//!     let user = FishPi::login(&login_data).await?;
28//!     let points = user.get_points("target_user").await?;
29//!     println!("{}: {}", points.name, points.point);
30//!     Ok(())
31//! }
32//! ```
33pub mod api;
34pub mod model;
35pub mod utils;
36
37use serde_json::{Value, json};
38
39use crate::{
40    api::user::User,
41    model::{
42        misc::{Log, LoginData, PreRegisterInfo, RegisterInfo, UserLite, UserVipInfo},
43        user::AtUser,
44    },
45    utils::{ResponseResult, build_http_path, error::Error, get, post},
46};
47
48/// 摸鱼派 Rust SDK 接口
49pub struct FishPi;
50
51impl FishPi {
52    /// 登录
53    ///
54    /// - `data` 登录账密
55    ///
56    /// 返回用户实例
57    pub async fn login(data: &LoginData) -> Result<User, Error> {
58        let url = "api/getKey".to_string();
59
60        let data_json = data.to_value()?;
61
62        let rsp = post(&url, Some(data_json)).await?;
63
64        if rsp.get("code").and_then(|c| c.as_i64()).unwrap_or(-1) != 0 {
65            return Err(Error::Api(
66                rsp["msg"].as_str().unwrap_or("API error").to_string(),
67            ));
68        }
69
70        let token = rsp["Key"].as_str().unwrap_or("").trim().to_string();
71
72        Ok(User::new(token))
73    }
74
75    /// 预注册
76    ///
77    /// - `data` 预注册数据
78    ///
79    /// 返回预注册结果
80    pub async fn pre_register(data: &PreRegisterInfo) -> Result<ResponseResult, Error> {
81        let url = "register".to_string();
82
83        let data_json = serde_json::to_value(data)
84            .map_err(|e| Error::Parse(format!("Failed to serialize PreRegisterInfo: {}", e)))?;
85
86        let rsp = post(&url, Some(data_json)).await?;
87
88        ResponseResult::from_value(&rsp)
89    }
90
91    /// 验证手机验证码
92    ///
93    /// - `code` 验证码
94    ///
95    /// 返回用户 ID
96    pub async fn verify(code: &str) -> Result<String, Error> {
97        let url = build_http_path("verify", &[("code", code.to_string())]);
98
99        let rsp = get(&url).await?;
100
101        if rsp.get("code").and_then(|c| c.as_i64()).unwrap_or(-1) != 0 {
102            return Err(Error::Api(
103                rsp["msg"].as_str().unwrap_or("API error").to_string(),
104            ));
105        }
106
107        Ok(rsp["userId"].as_str().unwrap_or("").to_string())
108    }
109
110    /// 注册
111    ///
112    /// - `data` 注册数据 [RegisterInfo]
113    ///
114    /// 返回注册结果
115    pub async fn register(data: &RegisterInfo) -> Result<ResponseResult, Error> {
116        let url = if let Some(r) = &data.r {
117            build_http_path("register2", &[("r", r.to_string())])
118        } else {
119            "register2".to_string()
120        };
121
122        let data_json = serde_json::to_value(data)
123            .map_err(|e| Error::Parse(format!("Failed to serialize RegisterInfo: {}", e)))?;
124
125        let rsp = post(&url, Some(data_json)).await?;
126
127        if let Some(code) = rsp.get("code").and_then(|c| c.as_i64())
128            && code != 0
129        {
130            return Err(Error::Api(
131                rsp["msg"].as_str().unwrap_or("API error").to_string(),
132            ));
133        }
134
135        ResponseResult::from_value(&rsp)
136    }
137
138    /// 获取用户名联想
139    ///
140    /// - `name` 用户名
141    ///
142    /// 返回用户名联想列表
143    pub async fn names(name: &str) -> Result<Vec<AtUser>, Error> {
144        let url = "users/names".to_string();
145
146        let data_json = json!({
147            "name": name,
148        });
149
150        let rsp = post(&url, Some(data_json)).await?;
151
152        if rsp.get("code").and_then(|c| c.as_i64()).unwrap_or(0) != 0 {
153            return Err(Error::Api(
154                rsp["msg"].as_str().unwrap_or("API error").to_string(),
155            ));
156        }
157
158        let at_users = rsp["data"]
159            .as_array()
160            .ok_or_else(|| Error::Api("Data is not an array".to_string()))?
161            .iter()
162            .map(AtUser::from_value)
163            .collect::<Result<Vec<AtUser>, _>>()?;
164
165        Ok(at_users)
166    }
167
168    /// 获取最近注册的 20 个用户
169    ///
170    /// 返回用户列表
171    pub async fn recent_register() -> Result<Vec<UserLite>, Error> {
172        let url = "api/user/recentReg".to_string();
173
174        let rsp = get(&url).await?;
175
176        let user_lites = rsp["data"]
177            .as_array()
178            .ok_or_else(|| Error::Api("Data is not an array".to_string()))?
179            .iter()
180            .map(UserLite::from_value)
181            .collect::<Result<Vec<UserLite>, _>>()?;
182
183        Ok(user_lites)
184    }
185
186    /// 获取用户VIP信息
187    ///
188    /// - `user_id` 用户ID
189    ///
190    /// 返回用户VIP信息
191    pub async fn vip_info(user_id: &str) -> Result<UserVipInfo, Error> {
192        let url = format!("api/membership/{}", user_id);
193
194        let rsp = get(&url).await?;
195
196        if rsp.get("code").and_then(|c| c.as_i64()).unwrap_or(0) != 0 {
197            return Err(Error::Api(
198                rsp["msg"].as_str().unwrap_or("API error").to_string(),
199            ));
200        }
201
202        let data_obj = rsp["data"]
203            .as_object()
204            .ok_or_else(|| Error::Api("Data is not an object".to_string()))?;
205
206        let config_json_str = data_obj
207            .get("configJson")
208            .and_then(|v| v.as_str())
209            .unwrap_or("null");
210        let mut data: Value = serde_json::from_str(config_json_str).unwrap_or_else(|_| json!({}));
211
212        if let Some(data_map) = data.as_object_mut() {
213            data_map.insert(
214                "state".to_string(),
215                data_obj.get("state").cloned().unwrap_or(Value::Null),
216            );
217            if data_obj.get("state").and_then(|v| v.as_i64()).unwrap_or(0) == 1 {
218                data_map.insert(
219                    "oId".to_string(),
220                    data_obj.get("oId").cloned().unwrap_or(Value::Null),
221                );
222                data_map.insert(
223                    "userId".to_string(),
224                    data_obj.get("userId").cloned().unwrap_or(Value::Null),
225                );
226                data_map.insert(
227                    "lvCode".to_string(),
228                    data_obj.get("lvCode").cloned().unwrap_or(Value::Null),
229                );
230                data_map.insert(
231                    "expiresAt".to_string(),
232                    data_obj.get("expiresAt").cloned().unwrap_or(Value::Null),
233                );
234                data_map.insert(
235                    "createdAt".to_string(),
236                    data_obj.get("createdAt").cloned().unwrap_or(Value::Null),
237                );
238                data_map.insert(
239                    "updatedAt".to_string(),
240                    data_obj.get("updatedAt").cloned().unwrap_or(Value::Null),
241                );
242            }
243        }
244
245        UserVipInfo::from_value(&data)
246    }
247
248    /// 获取操作日志
249    ///
250    /// - `page` 页码
251    /// - `page_size` 每页数量
252    ///
253    /// 返回日志列表
254    pub async fn log(page: u32, page_size: u32) -> Result<Vec<Log>, Error> {
255        let url = build_http_path(
256            "logs/more",
257            &[
258                ("page", page.to_string()),
259                ("pageSize", page_size.to_string()),
260            ],
261        );
262
263        let rsp = get(&url).await?;
264
265        let logs = rsp["data"]
266            .as_array()
267            .ok_or_else(|| Error::Api("Data is not an array".to_string()))?
268            .iter()
269            .map(Log::from_value)
270            .collect::<Result<Vec<Log>, _>>()?;
271
272        Ok(logs)
273    }
274}