1use crate::{ BilibiliRequest, BpiClient, BpiError, BpiResponse };
5use serde::{ Deserialize, Serialize };
6
7#[derive(Debug, Clone, Serialize, Deserialize)]
8pub struct AudioRankPeriodData {
9 pub list: std::collections::HashMap<String, Vec<AudioRankPeriod>>,
10}
11
12#[derive(Debug, Clone, Serialize, Deserialize)]
13pub struct AudioRankPeriod {
14 #[serde(rename = "ID")]
15 pub id: u64,
16 pub priod: u64,
17 pub publish_time: u64,
18}
19
20#[derive(Debug, Clone, Serialize, Deserialize)]
21pub struct AudioRankDetailData {
22 pub listen_fid: u64,
23 pub all_fid: u64,
24 pub fav_mid: u64,
25 pub cover_url: String,
26 pub is_subscribe: bool,
27 pub listen_count: u64,
28}
29
30#[derive(Debug, Clone, Serialize, Deserialize)]
31pub struct AudioRankMusicListData {
32 pub list: Vec<AudioRankMusicItem>,
33}
34
35#[derive(Debug, Clone, Serialize, Deserialize)]
36pub struct AudioRankMusicItem {
37 pub music_id: String,
38 pub music_title: String,
39 pub singer: String,
40 pub album: String,
41 pub mv_aid: u64,
42 pub mv_bvid: String,
43 pub mv_cover: String,
44 pub heat: u64,
45 pub rank: u64,
46 pub can_listen: bool,
47 pub recommendation: String,
48 pub creation_aid: u64,
49 pub creation_bvid: String,
50 pub creation_cover: String,
51 pub creation_title: String,
52 pub creation_up: u64,
53 pub creation_nickname: String,
54 pub creation_duration: u64,
55 pub creation_play: u64,
56 pub creation_reason: String,
57 pub achievements: Vec<String>,
58 pub material_id: u64,
59 pub material_use_num: u64,
60 pub material_duration: u64,
61 pub material_show: u64,
62 pub song_type: u64,
63}
64
65impl BpiClient {
66 pub async fn audio_rank_period(
76 &self,
77 list_type: u32
78 ) -> Result<BpiResponse<AudioRankPeriodData>, BpiError> {
79 let csrf = self.csrf()?;
80 let params = vec![("list_type", list_type.to_string()), ("csrf", csrf)];
81
82 self
83 .get("https://api.bilibili.com/x/copyright-music-publicity/toplist/all_period")
84 .query(¶ms)
85 .send_bpi("获取音频榜单每期列表").await
86 }
87
88 pub async fn audio_rank_detail(
98 &self,
99 list_id: u64
100 ) -> Result<BpiResponse<AudioRankDetailData>, BpiError> {
101 let csrf = self.csrf()?;
102 let params = vec![("list_id", list_id.to_string()), ("csrf", csrf)];
103
104 self
105 .get("https://api.bilibili.com/x/copyright-music-publicity/toplist/detail")
106 .query(¶ms)
107 .send_bpi("查询音频榜单单期信息").await
108 }
109
110 pub async fn audio_rank_music_list(
121 &self,
122 list_id: u64
123 ) -> Result<BpiResponse<AudioRankMusicListData>, BpiError> {
124 let csrf = self.csrf()?;
125 let params = vec![("list_id", list_id.to_string()), ("csrf", csrf)];
126
127 self
128 .get("https://api.bilibili.com/x/copyright-music-publicity/toplist/music_list")
129 .query(¶ms)
130 .send_bpi("获取音频榜单单期内容").await
131 }
132
133 pub async fn audio_rank_subscribe(
144 &self,
145 state: u32,
146 list_id: Option<u64>
147 ) -> Result<BpiResponse<serde_json::Value>, BpiError> {
148 let csrf = self.csrf()?;
149 let mut params = vec![("state", state.to_string()), ("csrf", csrf.to_string())];
150 if let Some(id) = list_id {
151 params.push(("list_id", id.to_string()));
152 }
153
154 self
155 .post("https://api.bilibili.com/x/copyright-music-publicity/toplist/subscribe/update")
156 .form(¶ms)
157 .send_bpi("订阅或退订榜单").await
158 }
159}
160
161#[cfg(test)]
162mod tests {
163 use super::*;
164
165 const TEST_LIST_ID: u64 = 76;
166
167 #[tokio::test]
168 async fn test_audio_rank_period() -> Result<(), Box<BpiError>> {
169 let bpi = BpiClient::new();
170 let result = bpi.audio_rank_period(2).await?;
171 let data = result.into_data()?;
172 tracing::info!("{:#?}", data);
173
174 for (year, periods) in data.list {
176 assert!(!year.is_empty());
177 assert!(!periods.is_empty());
178 for period in periods {
179 assert!(period.id > 0);
180 assert!(period.priod > 0);
181 assert!(period.publish_time > 0);
182 }
183 }
184
185 Ok(())
186 }
187
188 #[tokio::test]
189 async fn test_audio_rank_detail() -> Result<(), Box<BpiError>> {
190 let bpi = BpiClient::new();
191 let result = bpi.audio_rank_detail(TEST_LIST_ID).await?;
192 let data = result.into_data()?;
193 tracing::info!("{:#?}", data);
194
195 assert!(data.listen_fid > 0);
196 assert!(data.all_fid > 0);
197 assert!(data.fav_mid > 0);
198 assert!(!data.cover_url.is_empty());
199
200 Ok(())
201 }
202
203 #[tokio::test]
204 async fn test_audio_rank_music_list() -> Result<(), Box<BpiError>> {
205 let bpi = BpiClient::new();
206 let result = bpi.audio_rank_music_list(TEST_LIST_ID).await?;
207 let data = result.into_data()?;
208 tracing::info!("{:#?}", data);
209
210 for item in &data.list {
211 assert!(!item.music_id.is_empty());
212 assert!(!item.music_title.is_empty());
213 assert!(!item.singer.is_empty());
214 assert!(!item.achievements.is_empty());
215 }
216
217 Ok(())
218 }
219
220 #[tokio::test]
221 async fn test_update_audio_rank_subscribe() -> Result<(), Box<BpiError>> {
222 let bpi = BpiClient::new();
223 bpi.audio_rank_subscribe(1, Some(76)).await?;
224
225 Ok(())
226 }
227}