Skip to main content

bpi_rs/audio/
rank.rs

1//! 音频榜单
2//!
3//! [查看 API 文档](https://github.com/Yuelioi/bilibili-API-collect/tree/cfc5fddcc8a94b74d91970bb5b4eaeb349addc47/docs/audio/rank.md)
4use 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    /// 获取音频榜单每期列表
67    ///
68    /// # 参数
69    /// | 名称       | 类型          | 说明       |
70    /// | ---------- | ------------- | ---------- |
71    /// | `list_type`| u32  | 榜单类型 1:hot 2:origin   |
72    ///
73    /// # 文档
74    /// [获取音频榜单每期列表](https://github.com/Yuelioi/bilibili-API-collect/tree/cfc5fddcc8a94b74d91970bb5b4eaeb349addc47/docs/audio/rank.md#获取音频榜单每期列表)
75    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(&params)
85            .send_bpi("获取音频榜单每期列表").await
86    }
87
88    /// 查询音频榜单单期信息
89    ///
90    /// # 参数
91    /// | 名称      | 类型  | 说明    |
92    /// | --------- | ----- | ------- |
93    /// | `list_id` | u64   | 榜单 id |
94    ///
95    /// # 文档
96    /// [查询音频榜单单期信息](https://github.com/Yuelioi/bilibili-API-collect/tree/cfc5fddcc8a94b74d91970bb5b4eaeb349addc47/docs/audio/rank.md#查询音频榜单单期信息)
97    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(&params)
107            .send_bpi("查询音频榜单单期信息").await
108    }
109
110    /// 获取音频榜单单期内容
111    ///
112    /// # 参数
113    /// | 名称      | 类型  | 说明                 |
114    /// | --------- | ----- | ------------------- |
115    /// | `list_id` | u64   | 榜单 id             |
116    /// | `csrf`    | String| CSRF Token(可选)   |
117    ///
118    /// # 文档
119    /// [获取音频榜单单期内容](https://github.com/Yuelioi/bilibili-API-collect/tree/cfc5fddcc8a94b74d91970bb5b4eaeb349addc47/docs/audio/rank.md#获取音频榜单单期内容)
120    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(&params)
130            .send_bpi("获取音频榜单单期内容").await
131    }
132
133    /// 订阅或退订榜单
134    ///
135    /// # 参数
136    /// | 名称      | 类型           | 说明                       |
137    /// | --------- | -------------- | -------------------------- |
138    /// | `state`   | u32            | 操作代码(1:订阅,2:退订)|
139    /// | `list_id` | `Option<u64>`    | 榜单 id(可选)            |
140    ///
141    /// # 文档
142    /// [订阅或退订榜单](https://github.com/Yuelioi/bilibili-API-collect/tree/cfc5fddcc8a94b74d91970bb5b4eaeb349addc47/docs/audio/rank.md#订阅或退订榜单)
143    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(&params)
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        // 检查年份数据
175        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}