bpi_rs/user/
search.rs

1//! B站用户搜索相关接口
2//!
3//! 文档: https://github.com/SocialSisterYi/bilibili-API-collect/tree/master/docs/user
4use crate::{BilibiliRequest, BpiClient, BpiError, BpiResponse};
5use serde::{Deserialize, Serialize};
6
7// --- 响应数据结构体 ---
8
9/// 合集或课堂统计数据
10#[derive(Debug, Clone, Deserialize, Serialize)]
11pub struct CollectionStat {
12    pub coin: u64,
13    pub danmaku: u64,
14    pub favorite: u64,
15    pub like: u64,
16    pub mtime: u64,
17    pub reply: u64,
18    pub season_id: u64,
19    pub share: u64,
20    pub view: u64,
21    pub vt: u64,
22    pub vv: u64,
23}
24
25/// 所属合集或课堂元数据
26#[derive(Debug, Clone, Deserialize, Serialize)]
27pub struct VideoMeta {
28    pub attribute: u64,
29    pub cover: String,
30    pub ep_count: u64,
31    pub ep_num: u64,
32    pub first_aid: u64,
33    pub id: u64,
34    pub intro: String,
35    pub mid: u64,
36    pub ptime: u64,
37    pub sign_state: u64,
38    pub stat: Option<CollectionStat>,
39    pub title: String,
40}
41
42/// 投稿视频列表项
43#[derive(Debug, Clone, Deserialize, Serialize)]
44pub struct ContributedVideo {
45    pub aid: u64,
46    pub attribute: u64,
47    pub author: String,
48    pub bvid: String,
49    pub comment: u64,
50    pub copyright: String,
51    pub created: u64,
52    pub description: String,
53    pub elec_arc_type: u8,
54    pub enable_vt: u8,
55    pub hide_click: bool,
56    pub is_avoided: u8,
57    pub is_charging_arc: bool,
58    pub is_lesson_video: u8,
59    pub is_lesson_finished: u8,
60    pub is_live_playback: u8,
61    pub is_pay: u8,
62    pub is_self_view: bool,
63    pub is_steins_gate: u8,
64    pub is_union_video: u8,
65    pub jump_url: Option<String>,
66    pub length: String,
67    pub mid: u64,
68    pub meta: Option<VideoMeta>,
69    pub pic: String,
70    pub play: u64,
71    pub playback_position: u64,
72    pub review: u64,
73    pub season_id: u64,
74    pub subtitle: String,
75    pub title: String,
76    pub typeid: u64,
77    pub video_review: u64,
78    pub vt: u64,
79    pub vt_display: String,
80}
81
82/// 投稿视频列表
83#[derive(Debug, Clone, Deserialize, Serialize)]
84pub struct ContributedVideoList {
85    pub slist: Vec<serde_json::Value>,
86    pub tlist: serde_json::Value,
87    pub vlist: Vec<ContributedVideo>,
88}
89
90/// 页面信息
91#[derive(Debug, Clone, Deserialize, Serialize)]
92pub struct PageInfo {
93    pub count: u64,
94    pub pn: u32,
95    pub ps: u32,
96}
97
98/// 播放全部按钮
99#[derive(Debug, Clone, Deserialize, Serialize)]
100pub struct EpisodicButton {
101    pub text: String,
102    pub uri: String,
103}
104
105/// 用户投稿视频明细响应数据
106#[derive(Debug, Clone, Deserialize, Serialize)]
107pub struct ContributedVideosResponseData {
108    /// 列表信息
109    pub list: ContributedVideoList,
110    /// 页面信息
111    pub page: PageInfo,
112    /// “播放全部”按钮
113    pub episodic_button: Option<EpisodicButton>,
114    pub is_risk: bool,
115    pub gaia_res_type: u8,
116    pub gaia_data: Option<serde_json::Value>,
117}
118
119// --- API 实现 ---
120
121impl BpiClient {
122    /// 查询用户投稿视频明细
123    ///
124    /// 文档: https://github.com/SocialSisterYi/bilibili-API-collect/tree/master/docs/user
125    ///
126    /// 参数
127    ///
128    /// | 名称 | 类型 | 说明 |
129    /// | ---- | ---- | ---- |
130    /// | `mid` | u64 | 目标用户 UID |
131    /// | `order` | Option<&str> | 排序方式,默认 `pubdate` |
132    /// | `tid` | Option<u64> | 分区筛选,默认 0 |
133    /// | `keyword` | Option<&str> | 关键词筛选 |
134    /// | `pn` | Option<u32> | 页码,默认 1 |
135    /// | `ps` | Option<u32> | 每页项数,默认 30 |
136    pub async fn user_contributed_videos(
137        &self,
138        mid: u64,
139        order: Option<&str>,
140        tid: Option<u64>,
141        keyword: Option<&str>,
142        pn: Option<u32>,
143        ps: Option<u32>,
144    ) -> Result<BpiResponse<ContributedVideosResponseData>, BpiError> {
145        let pn_val = pn.unwrap_or(1);
146        let ps_val = ps.unwrap_or(30);
147        let order_val = order.unwrap_or("pubdate");
148        let tid_val = tid.unwrap_or(0);
149
150        let mut params = vec![
151            ("mid", mid.to_string()),
152            ("order", order_val.to_string()),
153            ("tid", tid_val.to_string()),
154            ("pn", pn_val.to_string()),
155            ("ps", ps_val.to_string()),
156        ];
157
158        if let Some(k) = keyword {
159            params.push(("keyword", k.to_string()));
160        }
161
162        let params = self.get_wbi_sign2(params).await?;
163
164        let req = self
165            .get("https://api.bilibili.com/x/space/wbi/arc/search")
166            .query(&params);
167
168        req.send_bpi("查询用户投稿视频明细").await
169    }
170}
171
172// --- 测试模块 ---
173
174#[cfg(test)]
175mod tests {
176    use super::*;
177    use tracing::info;
178
179    // 假设这是一个已知用户
180    const TEST_MID: u64 = 53456;
181    const TEST_KEYWORD: &str = "科技";
182
183    #[tokio::test]
184
185    async fn test_user_contributed_videos_default() -> Result<(), BpiError> {
186        let bpi = BpiClient::new();
187        let resp = bpi
188            .user_contributed_videos(TEST_MID, None, None, None, Some(1), Some(2))
189            .await?;
190        let data = resp.into_data()?;
191
192        info!("用户投稿视频明细: {:?}", data);
193        assert_eq!(data.page.pn, 1);
194        assert_eq!(data.page.ps, 2);
195        assert_eq!(data.list.vlist.len(), 2);
196        assert!(data.page.count > 0);
197
198        Ok(())
199    }
200
201    #[tokio::test]
202
203    async fn test_user_contributed_videos_with_keyword() -> Result<(), BpiError> {
204        let bpi = BpiClient::new();
205        let resp = bpi
206            .user_contributed_videos(TEST_MID, None, None, Some(TEST_KEYWORD), Some(1), Some(10))
207            .await?;
208        let data = resp.into_data()?;
209
210        info!("用户投稿视频明细(关键词): {:?}", data);
211        assert!(data.page.count > 0);
212
213        Ok(())
214    }
215}