bpi_rs/user/relation/
followers.rs

1//! B站用户粉丝列表相关接口
2//!
3//! 文档: https://github.com/SocialSisterYi/bilibili-API-collect/tree/master/docs/user
4use crate::models::Vip;
5use crate::{BilibiliRequest, BpiClient, BpiError, BpiResponse};
6use serde::{Deserialize, Serialize};
7// --- 响应数据结构体 ---
8
9/// 用户认证信息
10#[derive(Debug, Clone, Deserialize, Serialize)]
11pub struct OfficialVerify {
12    /// 用户认证类型,-1: 无, 0: UP 主认证, 1: 机构认证
13    #[serde(rename = "type")]
14    pub verify_type: i8,
15    /// 用户认证信息,无则为空
16    pub desc: String,
17}
18
19/// 关系列表对象
20#[derive(Debug, Clone, Deserialize, Serialize)]
21pub struct RelationListItem {
22    /// 用户 mid
23    pub mid: u64,
24    /// 对方对于自己的关系属性,0: 未关注, 1: 悄悄关注, 2: 已关注, 6: 已互粉, 128: 已拉黑
25    pub attribute: u8,
26    /// 对方关注目标用户时间,秒级时间戳
27    pub mtime: Option<u64>,
28    /// 目标用户将对方分组到的 id
29    pub tag: Option<Vec<u64>>,
30    /// 目标用户特别关注对方标识,0: 否, 1: 是
31    pub special: u8,
32    pub contract_info: Option<serde_json::Value>,
33    /// 用户昵称
34    pub uname: String,
35    /// 用户头像 url
36    pub face: String,
37    /// 用户签名
38    pub sign: String,
39    /// 是否为 NFT 头像
40    pub face_nft: u8,
41    /// 认证信息
42    pub official_verify: OfficialVerify,
43    /// 会员信息
44    pub vip: Vip,
45}
46
47/// 用户粉丝明细响应数据
48#[derive(Debug, Clone, Deserialize, Serialize)]
49pub struct FansListResponseData {
50    /// 明细列表
51    pub list: Vec<RelationListItem>,
52    /// 偏移量供下次请求使用
53    pub offset: String,
54    pub re_version: u32,
55    /// 粉丝总数
56    pub total: u64,
57}
58
59// --- API 实现 ---
60
61impl BpiClient {
62    /// 查询用户粉丝明细
63    ///
64    /// 文档: https://github.com/SocialSisterYi/bilibili-API-collect/tree/master/docs/user
65    ///
66    /// # 参数
67    /// | 名称            | 类型           | 说明                                   |
68    /// | --------------- | --------------| -------------------------------------- |
69    /// | `vmid`          | u64           | 目标用户 mid                           |
70    /// | `ps`            | Option<u32>   | 每页项数,默认50                       |
71    /// | `pn`            | Option<u32>   | 页码,默认1                            |
72    /// | `offset`        | Option<&str>  | 偏移量,翻页用                         |
73    /// | `last_access_ts`| Option<u64>   | 上次访问时间戳,秒                     |
74    /// | `from`          | Option<&str>  | 请求来源,部分场景传"main"             |
75    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// --- 测试模块 ---
110
111#[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}