use crate::{ BilibiliRequest, BpiClient, BpiError, BpiResponse };
use serde::{ Deserialize, Serialize };
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AudioInfoData {
pub id: i64,
pub uid: i64,
pub uname: String,
pub author: String,
pub title: String,
pub cover: String,
pub intro: String,
pub lyric: String,
pub crtype: i32,
pub duration: i64,
pub passtime: i64,
pub curtime: i64,
pub aid: i64,
pub bvid: String,
pub cid: i64,
pub msid: i64,
pub attr: i64,
pub limit: i64,
#[serde(rename = "activityId")]
pub activity_id: i64,
pub limitdesc: String,
pub ctime: Option<serde_json::Value>,
pub statistic: AudioStatistic,
#[serde(rename = "vipInfo")]
pub vip_info: AudioVipInfo,
#[serde(rename = "collectIds")]
pub collect_ids: Vec<i64>,
pub coin_num: i64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AudioStatistic {
pub sid: i64,
pub play: i64,
pub collect: i64,
pub comment: i64,
pub share: i64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AudioVipInfo {
pub r#type: i32,
pub status: i32,
pub due_date: i64,
pub vip_pay_type: i32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AudioTag {
pub r#type: String,
pub subtype: i32,
pub key: i32,
pub info: String,
}
pub type AudioMemberResponse = BpiResponse<Vec<AudioMemberType>>;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AudioMemberType {
pub list: Vec<AudioMember>,
pub r#type: i32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AudioMember {
pub mid: i64,
pub name: String,
pub member_id: i64,
}
impl BpiClient {
pub async fn audio_info(&self, sid: u64) -> Result<BpiResponse<AudioInfoData>, BpiError> {
self
.get("https://www.bilibili.com/audio/music-service-c/web/song/info")
.query(&[("sid", sid.to_string())])
.send_bpi("查询歌曲基本信息").await
}
pub async fn audio_tags(&self, sid: u64) -> Result<BpiResponse<Vec<AudioTag>>, BpiError> {
self
.get("https://www.bilibili.com/audio/music-service-c/web/tag/song")
.query(&[("sid", sid.to_string())])
.send_bpi("查询歌曲TAG").await
}
pub async fn audio_members(&self, sid: u64) -> Result<AudioMemberResponse, BpiError> {
self
.get("https://www.bilibili.com/audio/music-service-c/web/member/song")
.query(&[("sid", sid.to_string())])
.send_bpi("查询歌曲创作成员列表").await
}
pub async fn audio_lyric(&self, sid: u64) -> Result<BpiResponse<String>, BpiError> {
self
.get("https://www.bilibili.com/audio/music-service-c/web/song/lyric")
.query(&[("sid", sid.to_string())])
.send_bpi("获取歌曲歌词").await
}
}
#[cfg(test)]
mod tests {
use super::*;
const TEST_SID: u64 = 13603;
#[tokio::test]
async fn test_audio_info() -> Result<(), Box<BpiError>> {
let bpi = BpiClient::new();
let result = bpi.audio_info(TEST_SID).await?;
let data = result.data.unwrap();
assert!(!data.title.is_empty());
assert!(!data.author.is_empty());
assert!(data.duration > 0);
Ok(())
}
#[tokio::test]
async fn test_audio_tags() -> Result<(), Box<BpiError>> {
let bpi = BpiClient::new();
let result = bpi.audio_tags(TEST_SID).await?;
let data = result.into_data()?;
tracing::info!("{:#?}", data);
Ok(())
}
#[tokio::test]
async fn test_audio_members() -> Result<(), Box<BpiError>> {
let bpi = BpiClient::new();
let result = bpi.audio_members(TEST_SID).await?;
let data = result.into_data()?;
tracing::info!("{:#?}", data);
Ok(())
}
#[tokio::test]
async fn test_audio_lyric() -> Result<(), Box<BpiError>> {
let bpi = BpiClient::new();
let result = bpi.audio_lyric(TEST_SID).await?;
let data = result.into_data()?;
tracing::info!("{:#?}", data);
Ok(())
}
#[tokio::test]
async fn test_audio_info_fields() -> Result<(), Box<BpiError>> {
let bpi = BpiClient::new();
let result = bpi.audio_info(13598).await?;
let data = &result.data.unwrap();
assert!(data.id > 0);
assert!(data.uid > 0);
assert!(!data.uname.is_empty());
assert!(!data.title.is_empty());
assert!(data.duration > 0);
assert!(data.passtime > 0);
let stats = &data.statistic;
assert!(stats.sid > 0);
assert!(stats.play >= 0);
assert!(stats.collect >= 0);
Ok(())
}
}