Skip to main content

bpi_rs/audio/
info.rs

1//! 歌曲基本信息
2//!
3//! [查看 API 文档](https://github.com/SocialSisterYi/bilibili-API-collect/blob/master/docs/audio/info.md)
4
5use crate::{ BilibiliRequest, BpiClient, BpiError, BpiResponse };
6use serde::{ Deserialize, Serialize };
7
8/// 歌曲基本信息数据
9#[derive(Debug, Clone, Serialize, Deserialize)]
10pub struct AudioInfoData {
11    /// 音频auid
12    pub id: i64,
13    /// UP主mid
14    pub uid: i64,
15    /// UP主昵称
16    pub uname: String,
17    /// 作者名
18    pub author: String,
19    /// 歌曲标题
20    pub title: String,
21    /// 封面图片url
22    pub cover: String,
23    /// 歌曲简介
24    pub intro: String,
25    /// lrc歌词url
26    pub lyric: String,
27    /// 1 作用尚不明确
28    pub crtype: i32,
29    /// 歌曲时间长度 单位为秒
30    pub duration: i64,
31    /// 歌曲发布时间 时间戳
32    pub passtime: i64,
33    /// 当前请求时间 时间戳
34    pub curtime: i64,
35    /// 关联稿件avid 无为0
36    pub aid: i64,
37    /// 关联稿件bvid 无为空
38    pub bvid: String,
39    /// 关联视频cid 无为0
40    pub cid: i64,
41    /// 0 作用尚不明确
42    pub msid: i64,
43    /// 0 作用尚不明确
44    pub attr: i64,
45    /// 0 作用尚不明确
46    pub limit: i64,
47    /// 0 作用尚不明确
48    #[serde(rename = "activityId")]
49    pub activity_id: i64,
50    pub limitdesc: String,
51    /// null 作用尚不明确
52    pub ctime: Option<serde_json::Value>,
53    /// 状态数
54    pub statistic: AudioStatistic,
55    /// UP主会员状态
56    #[serde(rename = "vipInfo")]
57    pub vip_info: AudioVipInfo,
58    /// 歌曲所在的收藏夹mlid 需要登录(SESSDATA)
59    #[serde(rename = "collectIds")]
60    pub collect_ids: Vec<i64>,
61    /// 投币数
62    pub coin_num: i64,
63}
64
65/// 音频状态数
66#[derive(Debug, Clone, Serialize, Deserialize)]
67pub struct AudioStatistic {
68    /// 音频auid
69    pub sid: i64,
70    /// 播放次数
71    pub play: i64,
72    /// 收藏数
73    pub collect: i64,
74    /// 评论数
75    pub comment: i64,
76    /// 分享数
77    pub share: i64,
78}
79
80/// UP主会员状态
81#[derive(Debug, Clone, Serialize, Deserialize)]
82pub struct AudioVipInfo {
83    /// 会员类型 0:无 1:月会员 2:年会员
84    pub r#type: i32,
85    /// 会员状态 0:无 1:有
86    pub status: i32,
87    /// 会员到期时间 时间戳 毫秒
88    pub due_date: i64,
89    /// 会员开通状态 0:无 1:有
90    pub vip_pay_type: i32,
91}
92
93/// 歌曲TAG
94#[derive(Debug, Clone, Serialize, Deserialize)]
95pub struct AudioTag {
96    /// song 作用尚不明确
97    pub r#type: String,
98    /// ??? 作用尚不明确
99    pub subtype: i32,
100    /// TAG id?? 作用尚不明确
101    pub key: i32,
102    /// TAG名
103    pub info: String,
104}
105
106/// 歌曲创作成员响应类型
107pub type AudioMemberResponse = BpiResponse<Vec<AudioMemberType>>;
108
109/// 歌曲创作成员类型
110#[derive(Debug, Clone, Serialize, Deserialize)]
111pub struct AudioMemberType {
112    /// 成员列表
113    pub list: Vec<AudioMember>,
114    /// 成员类型代码 1:歌手 2:作词 3:作曲 4:编曲 5:后期/混音 7:封面制作 8:音源 9:调音 10:演奏 11:乐器 127:UP主
115    pub r#type: i32,
116}
117
118/// 歌曲创作成员
119#[derive(Debug, Clone, Serialize, Deserialize)]
120pub struct AudioMember {
121    /// 0 作用尚不明确
122    pub mid: i64,
123    /// 成员名
124    pub name: String,
125    /// 成员id?? 作用尚不明确
126    pub member_id: i64,
127}
128
129impl BpiClient {
130    /// 查询歌曲基本信息
131    ///
132    /// # 参数
133    /// | 名称   | 类型   | 说明              |
134    /// | ------ | ------ | ----------------- |
135    /// | `sid`  | u64    | 音频 auid (必要)  |
136    ///
137    /// # 文档
138    /// [查询歌曲基本信息](https://github.com/SocialSisterYi/bilibili-API-collect/blob/master/docs/audio/info.md#查询歌曲基本信息)
139    pub async fn audio_info(&self, sid: u64) -> Result<BpiResponse<AudioInfoData>, BpiError> {
140        self
141            .get("https://www.bilibili.com/audio/music-service-c/web/song/info")
142            .query(&[("sid", sid.to_string())])
143            .send_bpi("查询歌曲基本信息").await
144    }
145
146    /// 查询歌曲 TAG
147    ///
148    /// # 参数
149    /// | 名称   | 类型   | 说明              |
150    /// | ------ | ------ | ----------------- |
151    /// | `sid`  | u64    | 音频 auid (必要)  |
152    ///
153    /// # 文档
154    /// [查询歌曲 TAG](https://github.com/SocialSisterYi/bilibili-API-collect/blob/master/docs/audio/info.md#查询歌曲tag)
155    pub async fn audio_tags(&self, sid: u64) -> Result<BpiResponse<Vec<AudioTag>>, BpiError> {
156        self
157            .get("https://www.bilibili.com/audio/music-service-c/web/tag/song")
158            .query(&[("sid", sid.to_string())])
159            .send_bpi("查询歌曲TAG").await
160    }
161
162    /// 查询歌曲创作成员列表
163    ///
164    /// # 参数
165    /// | 名称   | 类型   | 说明              |
166    /// | ------ | ------ | ----------------- |
167    /// | `sid`  | u64    | 音频 auid (必要)  |
168    ///
169    /// # 文档
170    /// [查询歌曲创作成员列表](https://github.com/SocialSisterYi/bilibili-API-collect/blob/master/docs/audio/info.md#查询歌曲创作成员列表)
171    pub async fn audio_members(&self, sid: u64) -> Result<AudioMemberResponse, BpiError> {
172        self
173            .get("https://www.bilibili.com/audio/music-service-c/web/member/song")
174            .query(&[("sid", sid.to_string())])
175            .send_bpi("查询歌曲创作成员列表").await
176    }
177
178    /// 获取歌曲歌词
179    ///
180    /// # 参数
181    /// | 名称   | 类型   | 说明              |
182    /// | ------ | ------ | ----------------- |
183    /// | `sid`  | u64    | 音频 auid (必要)  |
184    ///
185    /// # 文档
186    /// [获取歌曲歌词](https://github.com/SocialSisterYi/bilibili-API-collect/blob/master/docs/audio/info.md#获取歌曲歌词)
187    pub async fn audio_lyric(&self, sid: u64) -> Result<BpiResponse<String>, BpiError> {
188        self
189            .get("https://www.bilibili.com/audio/music-service-c/web/song/lyric")
190            .query(&[("sid", sid.to_string())])
191            .send_bpi("获取歌曲歌词").await
192    }
193}
194
195#[cfg(test)]
196mod tests {
197    use super::*;
198    const TEST_SID: u64 = 13603;
199    #[tokio::test]
200    async fn test_audio_info() -> Result<(), Box<BpiError>> {
201        let bpi = BpiClient::new();
202        let result = bpi.audio_info(TEST_SID).await?;
203        let data = result.data.unwrap();
204        assert!(!data.title.is_empty());
205        assert!(!data.author.is_empty());
206        assert!(data.duration > 0);
207
208        Ok(())
209    }
210
211    #[tokio::test]
212    async fn test_audio_tags() -> Result<(), Box<BpiError>> {
213        let bpi = BpiClient::new();
214        let result = bpi.audio_tags(TEST_SID).await?;
215        let data = result.into_data()?;
216
217        tracing::info!("{:#?}", data);
218
219        Ok(())
220    }
221
222    #[tokio::test]
223    async fn test_audio_members() -> Result<(), Box<BpiError>> {
224        let bpi = BpiClient::new();
225        let result = bpi.audio_members(TEST_SID).await?;
226        let data = result.into_data()?;
227
228        tracing::info!("{:#?}", data);
229
230        Ok(())
231    }
232
233    #[tokio::test]
234    async fn test_audio_lyric() -> Result<(), Box<BpiError>> {
235        let bpi = BpiClient::new();
236
237        let result = bpi.audio_lyric(TEST_SID).await?;
238
239        let data = result.into_data()?;
240
241        tracing::info!("{:#?}", data);
242
243        Ok(())
244    }
245
246    #[tokio::test]
247    async fn test_audio_info_fields() -> Result<(), Box<BpiError>> {
248        let bpi = BpiClient::new();
249
250        let result = bpi.audio_info(13598).await?;
251
252        let data = &result.data.unwrap();
253        assert!(data.id > 0);
254        assert!(data.uid > 0);
255        assert!(!data.uname.is_empty());
256        assert!(!data.title.is_empty());
257        assert!(data.duration > 0);
258        assert!(data.passtime > 0);
259
260        let stats = &data.statistic;
261        assert!(stats.sid > 0);
262        assert!(stats.play >= 0);
263        assert!(stats.collect >= 0);
264
265        Ok(())
266    }
267}