bpi_rs/user/relation/
followers.rs1use crate::models::Vip;
5use crate::{BilibiliRequest, BpiClient, BpiError, BpiResponse};
6use serde::{Deserialize, Serialize};
7#[derive(Debug, Clone, Deserialize, Serialize)]
11pub struct OfficialVerify {
12 #[serde(rename = "type")]
14 pub verify_type: i8,
15 pub desc: String,
17}
18
19#[derive(Debug, Clone, Deserialize, Serialize)]
21pub struct RelationListItem {
22 pub mid: u64,
24 pub attribute: u8,
26 pub mtime: Option<u64>,
28 pub tag: Option<Vec<u64>>,
30 pub special: u8,
32 pub contract_info: Option<serde_json::Value>,
33 pub uname: String,
35 pub face: String,
37 pub sign: String,
39 pub face_nft: u8,
41 pub official_verify: OfficialVerify,
43 pub vip: Vip,
45}
46
47#[derive(Debug, Clone, Deserialize, Serialize)]
49pub struct FansListResponseData {
50 pub list: Vec<RelationListItem>,
52 pub offset: String,
54 pub re_version: u32,
55 pub total: u64,
57}
58
59impl BpiClient {
62 pub async fn user_followers(
76 &self,
77 vmid: u64,
78 ps: Option<u32>,
79 pn: Option<u32>,
80 offset: Option<&str>,
81 last_access_ts: Option<u64>,
82 from: Option<&str>,
83 ) -> Result<BpiResponse<FansListResponseData>, BpiError> {
84 let mut req = self
85 .get("https://api.bilibili.com/x/relation/fans")
86 .with_bilibili_headers()
87 .query(&[("vmid", &vmid.to_string())]);
88
89 if let Some(p) = ps {
90 req = req.query(&[("ps", &p.to_string())]);
91 }
92 if let Some(p) = pn {
93 req = req.query(&[("pn", &p.to_string())]);
94 }
95 if let Some(o) = offset {
96 req = req.query(&[("offset", o)]);
97 }
98 if let Some(l) = last_access_ts {
99 req = req.query(&[("last_access_ts", &l.to_string())]);
100 }
101 if let Some(f) = from {
102 req = req.query(&[("from", f)]);
103 }
104
105 req.send_bpi("查询用户粉丝明细").await
106 }
107}
108
109#[cfg(test)]
112mod tests {
113 use super::*;
114 use tracing::info;
115
116 const TEST_VMID: u64 = 4279370;
117
118 #[tokio::test]
119
120 async fn test_user_followers() -> Result<(), BpiError> {
121 let bpi = BpiClient::new();
122 let resp = bpi
123 .user_followers(TEST_VMID, Some(50), Some(1), None, None, None)
124 .await?;
125 let data = resp.into_data()?;
126
127 info!("用户粉丝明细: {:?}", data);
128 assert!(!data.list.is_empty());
129 assert_eq!(data.list.len(), 50);
130 assert!(!data.offset.is_empty());
131 assert!(data.total > 0);
132
133 Ok(())
134 }
135}