1use crate::models::Account;
6use crate::{ BilibiliRequest, BpiClient, BpiError, BpiResponse };
7use serde::{ Deserialize, Serialize };
8
9#[derive(Debug, Clone, Serialize, Deserialize)]
11pub struct Dimension {
12 pub width: u32,
13 pub height: u32,
14 pub rotate: u8,
15}
16
17#[derive(Debug, Clone, Serialize, Deserialize)]
19pub struct Stat {
20 pub aid: u64,
21 pub view: u64,
22 pub danmaku: u64,
23 pub reply: u64,
24 #[serde(rename = "favorite")]
25 pub favorite: u64,
26 #[serde(rename = "fav")]
27 pub fav: Option<u64>,
28 pub coin: u64,
29 pub share: u64,
30 pub now_rank: i64,
31 pub his_rank: i64,
32 pub like: u64,
33 pub dislike: u64,
34 pub evaluation: String,
35 #[serde(rename = "argue_msg")]
36 pub argue_msg: Option<String>,
37 pub vt: u64,
38 pub vv: Option<u64>,
39}
40
41#[derive(Debug, Clone, Serialize, Deserialize)]
43pub struct ArgueInfo {
44 pub argue_link: String,
45 pub argue_msg: String,
46 pub argue_type: i32,
47}
48
49#[derive(Debug, Clone, Serialize, Deserialize)]
51pub struct Owner {
52 pub mid: u64,
53 pub name: String,
54 pub face: String,
55}
56
57#[derive(Debug, Clone, Serialize, Deserialize)]
59pub struct Rights {
60 pub bp: u8,
61 pub elec: u8,
62 pub download: u8,
63 pub movie: u8,
64 pub pay: u8,
65 pub hd5: u8,
66 pub no_reprint: u8,
67 pub autoplay: u8,
68 pub ugc_pay: u8,
69 pub is_cooperation: u8,
70 pub ugc_pay_preview: u8,
71 pub no_background: u8,
72 pub clean_mode: u8,
73 pub is_stein_gate: u8,
74 pub arc_pay: u8,
75 pub free_watch: u8,
76 pub is_360: u8,
78 pub no_share: u8,
79}
80
81#[derive(Debug, Clone, Serialize, Deserialize)]
83pub struct Page {
84 pub cid: u64,
85 pub page: u32,
86 pub from: String,
87 pub part: String,
88 pub duration: u64,
89 pub vid: String,
90 pub weblink: String,
91 pub dimension: Dimension,
92 pub first_frame: Option<String>,
93 pub ctime: u64,
94}
95
96#[derive(Debug, Clone, Serialize, Deserialize)]
100pub struct SubtitleListItem {
101 pub id: u64,
102 pub lan: String,
103 pub lan_doc: String,
104 pub is_lock: bool,
105 pub subtitle_url: String,
106 #[serde(rename = "type")]
107 pub sub_type: u8,
108 pub id_str: String,
109 pub ai_type: u8,
110 pub ai_status: u8,
111 pub author: Account,
112}
113
114#[derive(Debug, Clone, Serialize, Deserialize)]
116pub struct Subtitle {
117 pub allow_submit: bool,
118 pub list: Vec<SubtitleListItem>,
119}
120
121#[derive(Debug, Clone, Serialize, Deserialize)]
123pub struct StaffVip {
124 #[serde(rename = "type")]
125 pub type_: u8,
126 pub status: u8,
127 pub due_date: u64,
128 pub vip_pay_type: u8,
129 pub theme_type: u8,
130 pub label: serde_json::Value,
131}
132
133#[derive(Debug, Clone, Serialize, Deserialize)]
135pub struct StaffOfficial {
136 pub role: i32,
137 pub title: String,
138 pub desc: String,
139 #[serde(rename = "type")]
140 pub type_: i32,
141}
142
143#[derive(Debug, Clone, Serialize, Deserialize)]
145pub struct StaffItem {
146 pub mid: u64,
147 pub title: String,
148 pub name: String,
149 pub face: String,
150 pub vip: StaffVip,
151 pub official: StaffOfficial,
152 pub follower: u64,
153 pub label_style: u8,
154}
155
156#[derive(Debug, Clone, Serialize, Deserialize)]
158pub struct HonorItem {
159 pub aid: u64,
160 pub hover_type: u8,
161 pub desc: String,
162 pub weekly_recommend_num: u64,
163}
164
165#[derive(Debug, Clone, Serialize, Deserialize)]
166pub struct HonorReply {
167 pub honor: Vec<HonorItem>,
168}
169
170#[derive(Debug, Clone, Serialize, Deserialize)]
172pub struct UserGarb {
173 pub url_image_ani_cut: String,
174}
175
176#[derive(Debug, Clone, Serialize, Deserialize)]
178pub struct EpisodeArc {
179 pub aid: u64,
180 pub videos: u32,
181 pub type_id: u32,
182 pub type_name: String,
183 pub copyright: u8,
184 pub pic: String,
185 pub title: String,
186 pub pubdate: u64,
187 pub ctime: u64,
188 pub desc: String,
189 pub state: u8,
190 pub duration: u64,
191 pub rights: Rights,
192 pub author: Owner,
193 pub stat: Stat,
194 pub dynamic: String,
195 pub dimension: Dimension,
196 pub desc_v2: serde_json::Value,
197 pub is_chargeable_season: bool,
198 pub is_blooper: bool,
199 pub enable_vt: u8,
200 pub vt_display: String,
201 pub type_id_v2: u32,
202 pub type_name_v2: String,
203 pub is_lesson_video: u8,
204}
205
206#[derive(Debug, Clone, Serialize, Deserialize)]
208pub struct SectionEpisode {
209 pub season_id: u64,
210 pub section_id: u64,
211 pub id: u64,
212 pub aid: u64,
213 pub cid: u64,
214 pub title: String,
215 pub attribute: u32,
216 pub arc: EpisodeArc,
217 pub page: Page,
218 pub bvid: String,
219 pub pages: Vec<Page>,
220}
221
222#[derive(Debug, Clone, Serialize, Deserialize)]
224pub struct Section {
225 pub season_id: u64,
226 pub id: u64,
227 pub title: String,
228 #[serde(rename = "type")]
229 pub type_: u8,
230 pub episodes: Vec<SectionEpisode>,
231}
232
233#[derive(Debug, Clone, Serialize, Deserialize)]
235pub struct UgcSeasonStat {
236 pub season_id: u64,
237 pub view: u64,
238 pub danmaku: u64,
239 pub reply: u64,
240 pub fav: u64,
241 pub coin: u64,
242 pub share: u64,
243 pub now_rank: i64,
244 pub his_rank: i64,
245 pub like: u64,
246 pub vt: u64,
247 pub vv: u64,
248}
249
250#[derive(Debug, Clone, Serialize, Deserialize)]
251pub struct UgcSeason {
252 pub id: u64,
253 pub title: String,
254 pub cover: String,
255 pub mid: u64,
256 pub intro: String,
257 pub sign_state: u8,
258 pub attribute: u32,
259 pub sections: Vec<Section>,
260 pub stat: UgcSeasonStat,
261 pub ep_count: u32,
262 pub season_type: u8,
263 pub is_pay_season: bool,
264 pub enable_vt: u8,
265}
266
267#[derive(Debug, Clone, Serialize, Deserialize)]
269pub struct VideoData {
270 pub aid: u64,
271 pub bvid: String,
272 pub videos: u32,
273 pub tid: u32,
274 pub tid_v2: u32,
275 pub tname: String,
276 pub tname_v2: String,
277 pub copyright: u8,
278 pub pic: String,
279 pub title: String,
280 pub pubdate: u64,
281 pub ctime: u64,
282 pub desc: String,
283 pub desc_v2: Vec<serde_json::Value>,
284 pub state: u32,
285 pub duration: u64, #[serde(default = "default_forward")] pub forward: u64,
288 pub mission_id: Option<u64>,
289 #[serde(default = "default_redirect_url")] pub redirect_url: String,
291 pub rights: Rights,
292 pub owner: Owner,
293 pub stat: Stat,
294 pub argue_info: ArgueInfo,
295 pub dynamic: String,
296 pub cid: u64,
297 pub dimension: Dimension,
298 pub premiere: serde_json::Value,
299 pub teenage_mode: u8,
300 pub is_chargeable_season: bool,
301 pub is_story: bool,
302 pub is_upower_exclusive: bool,
303 pub is_upower_play: bool,
304 pub is_upower_preview: bool,
305 pub no_cache: bool,
306 pub pages: Vec<Page>,
307 pub subtitle: Subtitle,
308 pub ugc_season: Option<UgcSeason>, #[serde(default = "default_staff")]
310 pub staff: Vec<StaffItem>, pub is_season_display: bool,
312 pub user_garb: UserGarb,
313 pub honor_reply: serde_json::Value,
314 pub like_icon: String,
315 pub need_jump_bv: bool,
316 pub disable_show_up_info: bool,
317 pub is_story_play: u32,
318 pub is_view_self: bool,
319 pub is_upower_exclusive_with_qa: bool,
320}
321
322fn default_forward() -> u64 {
323 0
324}
325
326fn default_redirect_url() -> String {
327 String::new()
328}
329
330fn default_staff() -> Vec<StaffItem> {
331 let v: Vec<StaffItem> = Vec::new();
332 v
333}
334
335pub type VideoInfoResponse = BpiResponse<VideoData>;
337
338impl BpiClient {
339 pub async fn video_info(
352 &self,
353 aid: Option<u64>,
354 bvid: Option<&str>
355 ) -> Result<VideoInfoResponse, BpiError> {
356 let aid = aid.map(|v| v.to_string());
357 let bvid = bvid.map(|v| v.to_string());
358
359 self
360 .get("https://api.bilibili.com/x/web-interface/view")
361 .query(
362 &[
363 ("aid", aid),
364 ("bvid", bvid),
365 ]
366 )
367 .send_bpi("视频详细信息").await
368 }
369}
370
371#[cfg(test)]
372mod tests {
373 use super::*;
374
375 #[tokio::test]
376 async fn test_video_info() {
377 let bpi = BpiClient::new();
378
379 let aid = Some(10001);
380 let bvid = None;
381
382 match bpi.video_info(aid, bvid).await {
383 Ok(resp) => {
384 if resp.code == 0 {
385 } else {
390 tracing::info!("请求失败: code={}, message={}", resp.code, resp.message);
391 }
392 }
393 Err(err) => {
394 panic!("请求出错: {}", err);
395 }
396 }
397 }
398}