use crate::models::Vip;
use crate::{ BilibiliRequest, BpiClient, BpiError, BpiResponse };
use serde::{ Deserialize, Serialize };
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct OfficialVerify {
#[serde(rename = "type")]
pub verify_type: i8,
pub desc: String,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct RelationListItem {
pub mid: u64,
pub attribute: u8,
pub mtime: Option<u64>,
pub tag: Option<Vec<u64>>,
pub special: u8,
pub contract_info: Option<serde_json::Value>,
pub uname: String,
pub face: String,
pub sign: String,
pub face_nft: u8,
pub official_verify: OfficialVerify,
pub vip: Vip,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct FansListResponseData {
pub list: Vec<RelationListItem>,
pub offset: String,
pub re_version: u32,
pub total: u64,
}
impl BpiClient {
pub async fn user_followers(
&self,
vmid: u64,
ps: Option<u32>,
pn: Option<u32>,
offset: Option<&str>,
last_access_ts: Option<u64>,
from: Option<&str>
) -> Result<BpiResponse<FansListResponseData>, BpiError> {
let mut req = self
.get("https://api.bilibili.com/x/relation/fans")
.with_bilibili_headers()
.query(&[("vmid", &vmid.to_string())]);
if let Some(p) = ps {
req = req.query(&[("ps", &p.to_string())]);
}
if let Some(p) = pn {
req = req.query(&[("pn", &p.to_string())]);
}
if let Some(o) = offset {
req = req.query(&[("offset", o)]);
}
if let Some(l) = last_access_ts {
req = req.query(&[("last_access_ts", &l.to_string())]);
}
if let Some(f) = from {
req = req.query(&[("from", f)]);
}
req.send_bpi("查询用户粉丝明细").await
}
}
#[cfg(test)]
mod tests {
use super::*;
use tracing::info;
const TEST_VMID: u64 = 4279370;
#[tokio::test]
async fn test_user_followers() -> Result<(), BpiError> {
let bpi = BpiClient::new();
let resp = bpi.user_followers(TEST_VMID, Some(50), Some(1), None, None, None).await?;
let data = resp.into_data()?;
info!("用户粉丝明细: {:?}", data);
assert!(!data.list.is_empty());
assert_eq!(data.list.len(), 50);
assert!(!data.offset.is_empty());
assert!(data.total > 0);
Ok(())
}
}