1use crate::{ BilibiliRequest, BpiClient, BpiError, BpiResponse };
5use serde::{ Deserialize, Serialize };
6
7#[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#[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#[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#[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#[derive(Debug, Clone, Deserialize, Serialize)]
92pub struct PageInfo {
93 pub count: u64,
94 pub pn: u32,
95 pub ps: u32,
96}
97
98#[derive(Debug, Clone, Deserialize, Serialize)]
100pub struct EpisodicButton {
101 pub text: String,
102 pub uri: String,
103}
104
105#[derive(Debug, Clone, Deserialize, Serialize)]
107pub struct ContributedVideosResponseData {
108 pub list: ContributedVideoList,
110 pub page: PageInfo,
112 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
119impl BpiClient {
122 pub async fn user_contributed_videos(
138 &self,
139 mid: u64,
140 order: Option<&str>,
141 tid: Option<u64>,
142 keyword: Option<&str>,
143 pn: Option<u32>,
144 ps: Option<u32>
145 ) -> Result<BpiResponse<ContributedVideosResponseData>, BpiError> {
146 let pn_val = pn.unwrap_or(1);
147 let ps_val = ps.unwrap_or(30);
148 let order_val = order.unwrap_or("pubdate");
149 let tid_val = tid.unwrap_or(0);
150
151 let mut params = vec![
152 ("mid", mid.to_string()),
153 ("order", order_val.to_string()),
154 ("tid", tid_val.to_string()),
155 ("pn", pn_val.to_string()),
156 ("ps", ps_val.to_string())
157 ];
158
159 if let Some(k) = keyword {
160 params.push(("keyword", k.to_string()));
161 }
162
163 let params = self.get_wbi_sign2(params).await?;
164
165 let req = self.get("https://api.bilibili.com/x/space/wbi/arc/search").query(¶ms);
166
167 req.send_bpi("查询用户投稿视频明细").await
168 }
169}
170
171#[cfg(test)]
174mod tests {
175 use super::*;
176 use tracing::info;
177
178 const TEST_MID: u64 = 53456;
180 const TEST_KEYWORD: &str = "科技";
181
182 #[tokio::test]
183 async fn test_user_contributed_videos_default() -> Result<(), BpiError> {
184 let bpi = BpiClient::new();
185 let resp = bpi.user_contributed_videos(TEST_MID, None, None, None, Some(1), Some(2)).await?;
186 let data = resp.into_data()?;
187
188 info!("用户投稿视频明细: {:?}", data);
189 assert_eq!(data.page.pn, 1);
190 assert_eq!(data.page.ps, 2);
191 assert_eq!(data.list.vlist.len(), 2);
192 assert!(data.page.count > 0);
193
194 Ok(())
195 }
196
197 #[tokio::test]
198 async fn test_user_contributed_videos_with_keyword() -> Result<(), BpiError> {
199 let bpi = BpiClient::new();
200 let resp = bpi.user_contributed_videos(
201 TEST_MID,
202 None,
203 None,
204 Some(TEST_KEYWORD),
205 Some(1),
206 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}