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.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 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 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 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 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}