1use crate::{ BilibiliRequest, BpiClient, BpiError, BpiResponse };
6use serde::{ Deserialize, Serialize };
7
8#[derive(Debug, Clone, Serialize, Deserialize)]
10pub struct AudioInfoData {
11 pub id: i64,
13 pub uid: i64,
15 pub uname: String,
17 pub author: String,
19 pub title: String,
21 pub cover: String,
23 pub intro: String,
25 pub lyric: String,
27 pub crtype: i32,
29 pub duration: i64,
31 pub passtime: i64,
33 pub curtime: i64,
35 pub aid: i64,
37 pub bvid: String,
39 pub cid: i64,
41 pub msid: i64,
43 pub attr: i64,
45 pub limit: i64,
47 #[serde(rename = "activityId")]
49 pub activity_id: i64,
50 pub limitdesc: String,
51 pub ctime: Option<serde_json::Value>,
53 pub statistic: AudioStatistic,
55 #[serde(rename = "vipInfo")]
57 pub vip_info: AudioVipInfo,
58 #[serde(rename = "collectIds")]
60 pub collect_ids: Vec<i64>,
61 pub coin_num: i64,
63}
64
65#[derive(Debug, Clone, Serialize, Deserialize)]
67pub struct AudioStatistic {
68 pub sid: i64,
70 pub play: i64,
72 pub collect: i64,
74 pub comment: i64,
76 pub share: i64,
78}
79
80#[derive(Debug, Clone, Serialize, Deserialize)]
82pub struct AudioVipInfo {
83 pub r#type: i32,
85 pub status: i32,
87 pub due_date: i64,
89 pub vip_pay_type: i32,
91}
92
93#[derive(Debug, Clone, Serialize, Deserialize)]
95pub struct AudioTag {
96 pub r#type: String,
98 pub subtype: i32,
100 pub key: i32,
102 pub info: String,
104}
105
106pub type AudioMemberResponse = BpiResponse<Vec<AudioMemberType>>;
108
109#[derive(Debug, Clone, Serialize, Deserialize)]
111pub struct AudioMemberType {
112 pub list: Vec<AudioMember>,
114 pub r#type: i32,
116}
117
118#[derive(Debug, Clone, Serialize, Deserialize)]
120pub struct AudioMember {
121 pub mid: i64,
123 pub name: String,
125 pub member_id: i64,
127}
128
129impl BpiClient {
130 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 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 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 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}