bpi_rs/audio/
music_list.rs

1//! 歌单&音频收藏夹详细信息
2//!
3//! https://github.com/SocialSisterYi/bilibili-API-collect/blob/master/docs/audio/music_list.md
4use crate::{BilibiliRequest, BpiClient, BpiError, BpiResponse};
5use serde::{Deserialize, Serialize};
6
7#[derive(Debug, Clone, Serialize, Deserialize)]
8pub struct AudioCollectionsListData {
9    #[serde(rename = "curPage")]
10    pub cur_page: i32,
11
12    #[serde(rename = "pageCount")]
13    pub page_count: i32,
14
15    #[serde(rename = "totalSize")]
16    pub total_size: i32,
17
18    #[serde(rename = "pageSize")]
19    pub page_size: i32,
20
21    pub data: Vec<AudioCollection>,
22}
23
24#[derive(Debug, Clone, Serialize, Deserialize)]
25pub struct AudioCollection {
26    pub id: i64,
27    pub uid: i64,
28    pub uname: String,
29    pub title: String,
30    pub r#type: i32,
31    pub published: i32,
32    pub cover: String,
33    pub ctime: i64,
34    pub song: i32,
35    pub desc: String,
36    pub sids: Vec<i64>,
37    #[serde(rename = "menuId")]
38    pub menu_id: i64,
39    pub statistic: AudioCollectionStatistic,
40}
41
42#[derive(Debug, Clone, Serialize, Deserialize)]
43pub struct AudioCollectionStatistic {
44    pub sid: i64,
45    pub play: i64,
46    pub collect: i64,
47    pub comment: Option<i64>,
48    pub share: i64,
49}
50
51#[derive(Debug, Clone, Serialize, Deserialize)]
52pub struct AudioHotMenuData {
53    #[serde(rename = "curPage")]
54    pub cur_page: i32,
55
56    #[serde(rename = "pageCount")]
57    pub page_count: i32,
58
59    #[serde(rename = "totalSize")]
60    pub total_size: i32,
61
62    #[serde(rename = "pageSize")]
63    pub page_size: i32,
64
65    pub data: Vec<AudioHotMenu>,
66}
67
68#[derive(Debug, Clone, Serialize, Deserialize)]
69pub struct AudioHotMenu {
70    #[serde(rename = "menuId")]
71    pub menu_id: i64,
72
73    pub uid: i64,
74
75    pub uname: String,
76
77    pub title: String,
78
79    pub cover: String,
80
81    pub intro: String,
82
83    pub r#type: i32,
84
85    pub off: i32,
86
87    pub ctime: i64,
88
89    pub curtime: i64,
90
91    pub statistic: AudioHotMenuStatistic,
92
93    pub snum: i32,
94
95    pub attr: i32,
96
97    #[serde(rename = "isDefault")]
98    pub is_default: i32,
99
100    #[serde(rename = "collectionId")]
101    pub collection_id: i64,
102}
103
104#[derive(Debug, Clone, Serialize, Deserialize)]
105pub struct AudioHotMenuStatistic {
106    pub sid: i64,
107    pub play: i64,
108    pub collect: i64,
109    pub comment: i64,
110    pub share: i64,
111}
112
113#[derive(Debug, Clone, Serialize, Deserialize)]
114pub struct AudioRankMenuData {
115    #[serde(rename = "curPage")]
116    pub cur_page: i32,
117
118    #[serde(rename = "pageCount")]
119    pub page_count: i32,
120
121    #[serde(rename = "totalSize")]
122    pub total_size: i32,
123
124    #[serde(rename = "pageSize")]
125    pub page_size: i32,
126
127    pub data: Vec<AudioRankMenu>,
128}
129
130#[derive(Debug, Clone, Serialize, Deserialize)]
131pub struct AudioRankMenu {
132    #[serde(rename = "menuId")]
133    pub menu_id: i64,
134
135    pub uid: i64,
136
137    pub uname: String,
138
139    pub title: String,
140
141    pub cover: String,
142
143    pub intro: String,
144
145    pub r#type: i32,
146
147    pub off: i32,
148
149    pub ctime: i64,
150
151    pub curtime: i64,
152
153    pub statistic: AudioRankMenuStatistic,
154
155    pub snum: i32,
156
157    pub attr: i32,
158
159    #[serde(rename = "isDefault")]
160    pub is_default: i32,
161
162    #[serde(rename = "collectionId")]
163    pub collection_id: i64,
164
165    pub audios: Vec<AudioRankItem>,
166}
167
168#[derive(Debug, Clone, Serialize, Deserialize)]
169pub struct AudioRankMenuStatistic {
170    pub sid: i64,
171    pub play: i64,
172    pub collect: i64,
173    pub comment: i64,
174    pub share: i64,
175}
176
177#[derive(Debug, Clone, Serialize, Deserialize)]
178pub struct AudioRankItem {
179    pub id: i64,
180    pub title: String,
181    pub duration: i64,
182}
183
184impl BpiClient {
185    /// 查询自己创建的歌单
186    ///
187    /// # 参数
188    /// | 名称   | 类型   | 说明     |
189    /// | ------ | ------ | -------- |
190    /// | `pn`   | u32    | 页码     |
191    /// | `ps`   | u32    | 每页项数 |
192    ///
193    /// # 文档
194    /// [查询自己创建的歌单](https://github.com/SocialSisterYi/bilibili-API-collect/blob/master/docs/audio/music_list.md#查询自己创建的歌单)
195    pub async fn audio_collections_list(
196        &self,
197        pn: u32,
198        ps: u32,
199    ) -> Result<BpiResponse<AudioCollectionsListData>, BpiError> {
200        self.get("https://www.bilibili.com/audio/music-service-c/web/collections/list")
201            .query(&[("pn", pn.to_string()), ("ps", ps.to_string())])
202            .send_bpi("查询自己创建的歌单")
203            .await
204    }
205
206    /// 查询音频收藏夹信息
207    ///
208    /// # 参数
209    /// | 名称   | 类型   | 说明                                     |
210    /// | ------ | ------ | ---------------------------------------- |
211    /// | `sid`  | u64    | 音频收藏夹 mlid(必须为默认收藏夹 mlid) |
212    ///
213    /// # 文档
214    /// [查询音频收藏夹(默认歌单)信息](https://github.com/SocialSisterYi/bilibili-API-collect/blob/master/docs/audio/music_list.md#查询音频收藏夹默认歌单信息)
215    pub async fn audio_collection_info(
216        &self,
217        sid: u64,
218    ) -> Result<BpiResponse<AudioCollection>, BpiError> {
219        self.get("https://www.bilibili.com/audio/music-service-c/web/collections/info")
220            .query(&[("sid", sid.to_string())])
221            .send_bpi("查询音频收藏夹信息")
222            .await
223    }
224
225    /// 查询热门歌单
226    ///
227    /// # 参数
228    /// | 名称   | 类型   | 说明     |
229    /// | ------ | ------ | -------- |
230    /// | `pn`   | u32    | 页码     |
231    /// | `ps`   | u32    | 每页项数 |
232    ///
233    /// # 文档
234    /// [查询热门歌单](https://github.com/SocialSisterYi/bilibili-API-collect/blob/master/docs/audio/music_list.md#查询热门歌单)
235    pub async fn audio_hot_menu(
236        &self,
237        pn: u32,
238        ps: u32,
239    ) -> Result<BpiResponse<AudioHotMenuData>, BpiError> {
240        self.get("https://www.bilibili.com/audio/music-service-c/web/menu/hit")
241            .query(&[("pn", pn.to_string()), ("ps", ps.to_string())])
242            .send_bpi("查询热门歌单")
243            .await
244    }
245
246    /// 查询热门榜单
247    ///
248    /// # 参数
249    /// | 名称   | 类型   | 说明     |
250    /// | ------ | ------ | -------- |
251    /// | `pn`   | u32    | 页码     |
252    /// | `ps`   | u32    | 每页项数 |
253    ///
254    /// # 文档
255    /// [查询热门榜单](https://github.com/SocialSisterYi/bilibili-API-collect/blob/master/docs/audio/music_list.md#查询热门榜单)
256    pub async fn audio_rank_menu(
257        &self,
258        pn: u32,
259        ps: u32,
260    ) -> Result<BpiResponse<AudioRankMenuData>, BpiError> {
261        self.get("https://www.bilibili.com/audio/music-service-c/web/menu/rank")
262            .query(&[("pn", pn.to_string()), ("ps", ps.to_string())])
263            .send_bpi("查询热门榜单")
264            .await
265    }
266}
267
268#[cfg(test)]
269mod tests {
270    use super::*;
271
272    #[tokio::test]
273    async fn test_audio_collections_list() {
274        let bpi = BpiClient::new();
275        let result = bpi.audio_collections_list(1, 2).await;
276        if let Ok(response) = result {
277            assert_eq!(response.code, 0);
278            let data = response.data.unwrap();
279            assert!(data.cur_page >= 1);
280            assert!(data.page_size > 0);
281        }
282    }
283
284    #[tokio::test]
285    async fn test_audio_collection_info() {
286        let bpi = BpiClient::new();
287        let result = bpi.audio_collection_info(15967839).await;
288        if let Ok(response) = result {
289            assert_eq!(response.code, 0);
290        }
291    }
292
293    #[tokio::test]
294    async fn test_audio_hot_menu() {
295        let bpi = BpiClient::new();
296        let result = bpi.audio_hot_menu(1, 3).await;
297        assert!(result.is_ok());
298        let response = result.unwrap();
299        assert_eq!(response.code, 0);
300        let data = response.data.unwrap();
301
302        assert!(data.cur_page >= 1);
303        assert!(data.page_size > 0);
304        assert!(!data.data.is_empty());
305    }
306
307    #[tokio::test]
308    async fn test_audio_rank_menu() {
309        let bpi = BpiClient::new();
310        let result = bpi.audio_rank_menu(1, 6).await;
311        assert!(result.is_ok());
312        let response = result.unwrap();
313
314        assert_eq!(response.code, 0);
315
316        let data = response.data.unwrap();
317
318        assert!(data.cur_page >= 1);
319        assert!(data.page_size > 0);
320        assert!(!data.data.is_empty());
321        // 检查榜单中的音频信息
322        for menu in &data.data {
323            assert!(!menu.audios.is_empty());
324            for audio in &menu.audios {
325                assert!(audio.id > 0);
326                assert!(!audio.title.is_empty());
327                assert!(audio.duration > 0);
328            }
329        }
330    }
331}