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}