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(
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(¶ms);
167
168 req.send_bpi("查询用户投稿视频明细").await
169 }
170}
171
172#[cfg(test)]
175mod tests {
176 use super::*;
177 use tracing::info;
178
179 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}