easy_msr_api/client/remote.rs
1//! # 远程API客户端
2//!
3//! 提供与MSR API进行HTTP通信的客户端实现。
4//!
5//! 该模块包含`RemoteApiClient`结构体,它封装了HTTP客户端并提供了
6//! 所有MSR API的调用方法。
7
8use crate::error::AppError;
9use crate::dto::*;
10use reqwest::{Client, ClientBuilder};
11use std::time::Duration;
12
13/// 远程API客户端
14///
15/// 封装了HTTP客户端和API基础URL,提供所有MSR API的调用方法。
16/// 支持自定义超时时间和基础URL配置。
17#[derive(Clone)]
18pub struct RemoteApiClient {
19 inner: Client,
20 base: String,
21}
22
23impl RemoteApiClient {
24 /// 创建默认配置的客户端
25 ///
26 /// 使用30秒的超时时间和默认的用户代理字符串。
27 ///
28 /// # 参数
29 ///
30 /// * `base` - API的基础URL地址
31 ///
32 /// # 示例
33 ///
34 /// ```rust
35 /// use easy_msr_api::client::remote::RemoteApiClient;
36 ///
37 /// let client = RemoteApiClient::new("https://monster-siren.hypergryph.com/api".to_string());
38 /// ```
39 pub fn new(base: String) -> Self {
40 Self::with_config(base, Duration::from_secs(30))
41 }
42
43 /// 使用自定义超时时间创建客户端
44 ///
45 /// # 参数
46 ///
47 /// * `base` - API的基础URL地址
48 /// * `timeout` - HTTP请求超时时间
49 ///
50 /// # 示例
51 ///
52 /// ```rust
53 /// use std::time::Duration;
54 /// use easy_msr_api::client::remote::RemoteApiClient;
55 ///
56 /// let client = RemoteApiClient::with_config(
57 /// "https://monster-siren.hypergryph.com/api".to_string(),
58 /// Duration::from_secs(60)
59 /// );
60 /// ```
61 pub fn with_config(base: String, timeout: Duration) -> Self {
62 let client = ClientBuilder::new()
63 .timeout(timeout)
64 .user_agent(format!("{}/{}", env!("CARGO_PKG_NAME"), env!("CARGO_PKG_VERSION")))
65 .build()
66 .expect("Failed to build HTTP client");
67
68 Self {
69 inner: client,
70 base: base.trim_end_matches('/').to_string(),
71 }
72 }
73
74 /// 统一的请求发送方法,减少代码重复
75 ///
76 /// 内部使用的辅助方法,用于发送GET请求并解析响应。
77 ///
78 /// # 类型参数
79 ///
80 /// * `T` - 响应数据的类型,必须实现`DeserializeOwned`
81 ///
82 /// # 参数
83 ///
84 /// * `path` - API路径(相对于基础URL)
85 /// * `query` - 查询参数列表
86 ///
87 /// # 返回
88 ///
89 /// 返回解析后的响应数据或错误
90 async fn send_get_request<T>(&self, path: &str, query: &[(&str, &str)]) -> Result<T, AppError>
91 where
92 T: serde::de::DeserializeOwned,
93 {
94 let url = format!("{}/{}", self.base, path.trim_start_matches('/'));
95
96 let response = self.inner
97 .get(&url)
98 .query(query)
99 .send()
100 .await?
101 .error_for_status()?;
102
103 Ok(response.json().await?)
104 }
105
106 /// 获取指定ID的歌曲详情
107 ///
108 /// # 参数
109 ///
110 /// * `id` - 歌曲的唯一标识符(cid)
111 ///
112 /// # 返回
113 ///
114 /// 返回包含歌曲详细信息的响应
115 pub async fn get_song(&self, id: String) -> Result<SongResp, AppError> {
116 self.send_get_request(&format!("song/{}", id), &[]).await
117 }
118
119 /// 获取所有歌曲列表
120 ///
121 /// # 返回
122 ///
123 /// 返回包含所有歌曲基本信息的列表
124 pub async fn get_all_songs(&self) -> Result<AllSongsResp, AppError> {
125 self.send_get_request("songs", &[]).await
126 }
127
128 /// 获取指定ID的专辑信息
129 ///
130 /// # 参数
131 ///
132 /// * `id` - 专辑的唯一标识符(cid)
133 ///
134 /// # 返回
135 ///
136 /// 返回包含专辑详细信息的响应
137 pub async fn get_album(&self, id: String) -> Result<AlbumResp, AppError> {
138 self.send_get_request(&format!("album/{}/data", id), &[]).await
139 }
140
141 /// 获取指定ID的专辑详情(包含歌曲列表)
142 ///
143 /// # 参数
144 ///
145 /// * `id` - 专辑的唯一标识符(cid)
146 ///
147 /// # 返回
148 ///
149 /// 返回包含专辑详情和歌曲列表的响应
150 pub async fn get_album_detail(&self, id: String) -> Result<AlbumDetailResp, AppError> {
151 self.send_get_request(&format!("album/{}/detail", id), &[]).await
152 }
153
154 /// 获取所有专辑列表
155 ///
156 /// # 返回
157 ///
158 /// 返回包含所有专辑基本信息的列表
159 pub async fn get_all_albums(&self) -> Result<ApiResp<Vec<AllAlbumsItem>>, AppError> {
160 self.send_get_request("albums", &[]).await
161 }
162
163 /// 获取所有新闻列表
164 ///
165 /// # 参数
166 ///
167 /// * `last_cid` - 可选参数,用于分页,从指定cid之后开始获取
168 ///
169 /// # 返回
170 ///
171 /// 返回包含新闻列表的响应
172 pub async fn get_all_news(&self, last_cid: Option<String>) -> Result<SearchNewsResp, AppError> {
173 let mut query = Vec::new();
174 if let Some(cid) = &last_cid {
175 query.push(("lastCid", cid.as_str()));
176 }
177 self.send_get_request("news", &query).await
178 }
179
180 /// 获取指定ID的新闻详情
181 ///
182 /// # 参数
183 ///
184 /// * `id` - 新闻的唯一标识符(cid)
185 ///
186 /// # 返回
187 ///
188 /// 返回包含新闻详细内容的响应
189 pub async fn get_news_detail(&self, id: String) -> Result<NewsDetailResp, AppError> {
190 self.send_get_request(&format!("news/{}", id), &[]).await
191 }
192
193 /// 获取字体配置信息
194 ///
195 /// # 返回
196 ///
197 /// 返回包含字体文件URL配置的响应
198 pub async fn get_font(&self) -> Result<FontResp, AppError> {
199 self.send_get_request("fontset", &[]).await
200 }
201
202 /// 综合搜索(同时搜索专辑和新闻)
203 ///
204 /// # 参数
205 ///
206 /// * `keyword` - 搜索关键词
207 ///
208 /// # 返回
209 ///
210 /// 返回包含专辑和新闻搜索结果的响应
211 pub async fn search(&self, keyword: String) -> Result<SearchResp, AppError> {
212 self.send_get_request("search", &[("keyword", &keyword)]).await
213 }
214
215 /// 搜索专辑
216 ///
217 /// # 参数
218 ///
219 /// * `keyword` - 搜索关键词
220 /// * `last_cid` - 可选参数,用于分页
221 ///
222 /// # 返回
223 ///
224 /// 返回包含专辑搜索结果的响应
225 pub async fn search_albums(
226 &self,
227 keyword: String,
228 last_cid: Option<String>,
229 ) -> Result<SearchAlbumResp, AppError> {
230 let mut query = vec![("keyword", keyword.as_str())];
231 if let Some(cid) = &last_cid {
232 query.push(("lastCid", cid.as_str()));
233 }
234 self.send_get_request("search/album", &query).await
235 }
236
237 /// 搜索新闻
238 ///
239 /// # 参数
240 ///
241 /// * `keyword` - 搜索关键词
242 /// * `last_cid` - 可选参数,用于分页
243 ///
244 /// # 返回
245 ///
246 /// 返回包含新闻搜索结果的响应
247 pub async fn search_news(
248 &self,
249 keyword: String,
250 last_cid: Option<String>,
251 ) -> Result<SearchNewsResp, AppError> {
252 let mut query = vec![("keyword", keyword.as_str())];
253 if let Some(cid) = &last_cid {
254 query.push(("lastCid", cid.as_str()));
255 }
256 self.send_get_request("search/news", &query).await
257 }
258}