1use 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 pub async fn audio_collections_list(
196 &self,
197 pn: u32,
198 ps: u32
199 ) -> Result<BpiResponse<AudioCollectionsListData>, BpiError> {
200 self
201 .get("https://www.bilibili.com/audio/music-service-c/web/collections/list")
202 .query(
203 &[
204 ("pn", pn.to_string()),
205 ("ps", ps.to_string()),
206 ]
207 )
208 .send_bpi("查询自己创建的歌单").await
209 }
210
211 pub async fn audio_collection_info(
221 &self,
222 sid: u64
223 ) -> Result<BpiResponse<AudioCollection>, BpiError> {
224 self
225 .get("https://www.bilibili.com/audio/music-service-c/web/collections/info")
226 .query(&[("sid", sid.to_string())])
227 .send_bpi("查询音频收藏夹信息").await
228 }
229
230 pub async fn audio_hot_menu(
241 &self,
242 pn: u32,
243 ps: u32
244 ) -> Result<BpiResponse<AudioHotMenuData>, BpiError> {
245 self
246 .get("https://www.bilibili.com/audio/music-service-c/web/menu/hit")
247 .query(
248 &[
249 ("pn", pn.to_string()),
250 ("ps", ps.to_string()),
251 ]
252 )
253 .send_bpi("查询热门歌单").await
254 }
255
256 pub async fn audio_rank_menu(
267 &self,
268 pn: u32,
269 ps: u32
270 ) -> Result<BpiResponse<AudioRankMenuData>, BpiError> {
271 self
272 .get("https://www.bilibili.com/audio/music-service-c/web/menu/rank")
273 .query(
274 &[
275 ("pn", pn.to_string()),
276 ("ps", ps.to_string()),
277 ]
278 )
279 .send_bpi("查询热门榜单").await
280 }
281}
282
283#[cfg(test)]
284mod tests {
285 use super::*;
286
287 #[tokio::test]
288 async fn test_audio_collections_list() {
289 let bpi = BpiClient::new();
290 let result = bpi.audio_collections_list(1, 2).await;
291 if let Ok(response) = result {
292 assert_eq!(response.code, 0);
293 let data = response.data.unwrap();
294 assert!(data.cur_page >= 1);
295 assert!(data.page_size > 0);
296 }
297 }
298
299 #[tokio::test]
300 async fn test_audio_collection_info() {
301 let bpi = BpiClient::new();
302 let result = bpi.audio_collection_info(15967839).await;
303 if let Ok(response) = result {
304 assert_eq!(response.code, 0);
305 }
306 }
307
308 #[tokio::test]
309 async fn test_audio_hot_menu() {
310 let bpi = BpiClient::new();
311 let result = bpi.audio_hot_menu(1, 3).await;
312 assert!(result.is_ok());
313 let response = result.unwrap();
314 assert_eq!(response.code, 0);
315 let data = response.data.unwrap();
316
317 assert!(data.cur_page >= 1);
318 assert!(data.page_size > 0);
319 assert!(!data.data.is_empty());
320 }
321
322 #[tokio::test]
323 async fn test_audio_rank_menu() {
324 let bpi = BpiClient::new();
325 let result = bpi.audio_rank_menu(1, 6).await;
326 assert!(result.is_ok());
327 let response = result.unwrap();
328
329 assert_eq!(response.code, 0);
330
331 let data = response.data.unwrap();
332
333 assert!(data.cur_page >= 1);
334 assert!(data.page_size > 0);
335 assert!(!data.data.is_empty());
336 for menu in &data.data {
338 assert!(!menu.audios.is_empty());
339 for audio in &menu.audios {
340 assert!(audio.id > 0);
341 assert!(!audio.title.is_empty());
342 assert!(audio.duration > 0);
343 }
344 }
345 }
346}