1use crate::models::{ LevelInfo, Nameplate, Official, OfficialVerify, Pendant, VipLabel };
6use crate::{ BilibiliRequest, BpiClient, BpiError, BpiResponse };
7use serde::{ Deserialize, Serialize };
8
9#[derive(Debug, Deserialize, Serialize)]
10pub struct VideoDetailData {
11 #[serde(rename = "View")]
12 pub view: View,
13 #[serde(rename = "Card")]
14 pub card: Card,
15 #[serde(rename = "Tags")]
16 pub tags: Vec<Tag>,
17 #[serde(rename = "Reply")]
18 pub reply: Reply,
19
20 #[serde(rename = "Related")]
21 pub related: Vec<Related>,
22
23 #[serde(rename = "Spec")]
24 pub spec: Option<serde_json::Value>,
25 pub hot_share: HotShare,
26 pub elec: Option<serde_json::Value>,
27 pub emergency: Emergency,
28
29 pub view_addit: ViewAddit,
30 pub guide: Option<serde_json::Value>,
31 pub query_tags: Option<serde_json::Value>,
32 pub participle: Option<serde_json::Value>,
33 pub module_ctrl: Option<serde_json::Value>,
34 pub replace_recommend: bool,
35 pub is_hit_labour_day_activity: bool,
36}
37
38#[derive(Debug, Deserialize, Serialize)]
39pub struct View {
40 pub bvid: String,
41 pub aid: i64,
42 pub videos: i32,
43 pub tid: i32,
44 pub tid_v2: i32,
45 pub tname: String,
46 pub tname_v2: String,
47 pub copyright: i32,
48 pub pic: String,
49 pub title: String,
50 pub pubdate: i64,
51 pub ctime: i64,
52 pub desc: String,
53 pub desc_v2: Vec<DescV2>,
54 pub state: i32,
55 pub duration: i32,
56 pub rights: Rights,
57 pub owner: Owner,
58 pub stat: Stat,
59 pub argue_info: ArgueInfo,
60 pub dynamic: String,
61 pub cid: i64,
62 pub dimension: Dimension,
63 pub premiere: Option<serde_json::Value>,
64
65 pub teenage_mode: i32,
66 pub is_chargeable_season: bool,
67 pub is_story: bool,
68 pub is_upower_exclusive: bool,
69 pub is_upower_play: bool,
70 pub is_upower_preview: bool,
71 pub enable_vt: i32,
72 pub vt_display: String,
73 pub is_upower_exclusive_with_qa: bool,
74 pub no_cache: bool,
75 pub pages: Vec<Page>,
76
77 pub subtitle: Subtitle,
78 pub is_season_display: bool,
79 pub user_garb: UserGarb,
80 pub honor_reply: serde_json::Value,
81 pub like_icon: String,
82 pub need_jump_bv: bool,
83 pub disable_show_up_info: bool,
84 pub is_story_play: i32,
85 pub is_view_self: bool,
86}
87
88#[derive(Debug, Deserialize, Serialize)]
89pub struct DescV2 {
90 pub raw_text: String,
91 #[serde(rename = "type")]
92 pub desc_type: i32,
93 pub biz_id: i32,
94}
95
96#[derive(Debug, Deserialize, Serialize)]
97pub struct Rights {
98 pub bp: i32,
99 pub elec: i32,
100 pub download: i32,
101 pub movie: i32,
102 pub pay: i32,
103 pub hd5: i32,
104 pub no_reprint: i32,
105 pub autoplay: i32,
106 pub ugc_pay: i32,
107 pub is_cooperation: i32,
108 pub ugc_pay_preview: i32,
109 pub no_background: i32,
110 pub clean_mode: i32,
111 pub is_stein_gate: i32,
112 pub is_360: i32,
113 pub no_share: i32,
114 pub arc_pay: i32,
115 pub free_watch: i32,
116}
117
118#[derive(Debug, Deserialize, Serialize)]
119pub struct Owner {
120 pub mid: i64,
121 pub name: String,
122 pub face: String,
123}
124
125#[derive(Debug, Deserialize, Serialize)]
126pub struct Stat {
127 pub aid: i64,
128 pub view: i32,
129 pub danmaku: i32,
130 pub reply: i32,
131 pub favorite: i32,
132 pub coin: i32,
133 pub share: i32,
134 pub now_rank: i32,
135 pub his_rank: i32,
136 pub like: i32,
137 pub dislike: i32,
138 pub evaluation: String,
139 pub vt: i32,
140}
141
142#[derive(Debug, Deserialize, Serialize)]
143pub struct ArgueInfo {
144 pub argue_msg: String,
145 pub argue_type: i32,
146 pub argue_link: String,
147}
148
149#[derive(Debug, Deserialize, Serialize)]
150pub struct Dimension {
151 pub width: i32,
152 pub height: i32,
153 pub rotate: i32,
154}
155
156#[derive(Debug, Deserialize, Serialize)]
157pub struct Page {
158 pub cid: i64,
159 pub page: i32,
160 pub from: String,
161 pub part: String,
162 pub duration: i32,
163 pub vid: String,
164 pub weblink: String,
165 pub dimension: Dimension,
166 pub first_frame: Option<String>,
167 pub ctime: i64,
168}
169
170#[derive(Debug, Deserialize, Serialize)]
171pub struct Subtitle {
172 pub allow_submit: bool,
173 pub list: Vec<serde_json::Value>,
174}
175
176#[derive(Debug, Deserialize, Serialize)]
177pub struct UserGarb {
178 pub url_image_ani_cut: String,
179}
180
181#[derive(Debug, Deserialize, Serialize)]
182pub struct Card {
183 pub card: CardInfo,
184 pub space: Space,
185 pub following: bool,
186 pub archive_count: i32,
187 pub article_count: i32,
188 pub follower: i32,
189 pub like_num: i32,
190}
191
192#[derive(Debug, Deserialize, Serialize)]
193pub struct CardInfo {
194 pub mid: String,
195 pub name: String,
196 pub approve: bool,
197 pub sex: String,
198 pub rank: String,
199 pub face: String,
200 pub face_nft: i32,
201 pub face_nft_type: i32,
202 #[serde(rename = "DisplayRank")]
203 pub display_rank: String,
204 pub regtime: i64,
205 pub spacesta: i32,
206 pub birthday: String,
207 pub place: String,
208 pub description: String,
209 pub article: i32,
210 pub attentions: Vec<serde_json::Value>,
211 pub fans: i32,
212 pub friend: i32,
213 pub attention: i32,
214 pub sign: String,
215 pub level_info: LevelInfo,
216 pub pendant: Pendant,
217 pub nameplate: Nameplate,
218 #[serde(rename = "Official")]
219 pub official: Official,
220 pub official_verify: OfficialVerify,
221 pub vip: VIP,
222 pub is_senior_member: i32,
223 pub name_render: Option<serde_json::Value>,
224}
225
226#[derive(Debug, Deserialize, Serialize)]
227pub struct VIP {
228 #[serde(rename = "type")]
229 pub vip_type: i32,
230 pub status: i32,
231 pub due_date: i64,
232 pub vip_pay_type: i32,
233 pub theme_type: i32,
234 pub label: VipLabel,
235 pub avatar_subscript: i32,
236 pub nickname_color: String,
237 pub role: i32,
238 pub avatar_subscript_url: String,
239 pub tv_vip_status: i32,
240 pub tv_vip_pay_type: i32,
241 pub tv_due_date: i64,
242 pub avatar_icon: AvatarIcon,
243 #[serde(rename = "vipType")]
244 pub vip_type_alt: i32,
245 #[serde(rename = "vipStatus")]
246 pub vip_status_alt: i32,
247}
248
249#[derive(Debug, Deserialize, Serialize)]
250pub struct AvatarIcon {
251 pub icon_resource: serde_json::Value,
252}
253
254#[derive(Debug, Deserialize, Serialize)]
255pub struct Space {
256 pub s_img: String,
257 pub l_img: String,
258}
259
260#[derive(Debug, Deserialize, Serialize)]
261pub struct Tag {
262 pub tag_id: i32,
263 pub tag_name: String,
264 pub music_id: String,
265 pub tag_type: String,
266 pub jump_url: String,
267}
268
269#[derive(Debug, Deserialize, Serialize)]
270pub struct Reply {
271 pub page: Option<serde_json::Value>,
272 pub replies: Vec<ReplyItem>,
273}
274
275#[derive(Debug, Deserialize, Serialize)]
276pub struct ReplyItem {
277 pub rpid: i32,
278 pub oid: i32,
279 #[serde(rename = "type")]
280 pub reply_type: i32,
281 pub mid: i32,
282 pub root: i32,
283 pub parent: i32,
284 pub dialog: i32,
285 pub count: i32,
286 pub rcount: i32,
287 pub state: i32,
288 pub fansgrade: i32,
289 pub attr: i32,
290 pub ctime: i64,
291 pub like: i32,
292 pub action: i32,
293 pub content: Option<serde_json::Value>,
294 pub replies: Option<Vec<ReplyItem>>,
295 pub assist: i32,
296 pub show_follow: bool,
297}
298
299#[derive(Debug, Deserialize, Serialize)]
300pub struct Related {
301 pub aid: i64,
302 pub videos: i32,
303 pub tid: i32,
304 pub tname: String,
305 pub copyright: i32,
306 pub pic: String,
307 pub title: String,
308 pub pubdate: i64,
309 pub ctime: i64,
310 pub desc: String,
311 pub state: i32,
312 pub duration: i32,
313 pub mission_id: Option<i32>,
314 pub rights: RelatedRights,
315 pub owner: Owner,
316 pub stat: RelatedStat,
317 pub dynamic: String,
318
319 pub cid: i64,
320 pub dimension: Dimension,
321 pub short_link_v2: String,
322 pub up_from_v2: Option<i32>,
323 pub first_frame: Option<String>,
324
325 pub pub_location: Option<String>,
326 pub cover43: String,
327 pub tidv2: i32,
328 pub tnamev2: String,
329 pub pid_v2: i32,
330
331 pub pid_name_v2: String,
332 pub bvid: String,
333 pub season_type: i32,
334 pub season_id: Option<i32>,
335 pub is_ogv: bool,
336 pub ogv_info: Option<serde_json::Value>,
337 pub rcmd_reason: String,
338 pub enable_vt: i32,
339 pub ai_rcmd: AIRcmd,
340}
341
342#[derive(Debug, Deserialize, Serialize)]
343pub struct RelatedRights {
344 pub bp: i32,
345 pub elec: i32,
346 pub download: i32,
347 pub movie: i32,
348 pub pay: i32,
349 pub hd5: i32,
350 pub no_reprint: i32,
351 pub autoplay: i32,
352 pub ugc_pay: i32,
353 pub is_cooperation: i32,
354 pub ugc_pay_preview: i32,
355 pub no_background: i32,
356 pub arc_pay: i32,
357 pub pay_free_watch: Option<i32>,
358}
359
360#[derive(Debug, Deserialize, Serialize)]
361pub struct RelatedStat {
362 pub aid: i64,
363 pub view: i32,
364 pub danmaku: i32,
365 pub reply: i32,
366 pub favorite: i32,
367 pub coin: i32,
368 pub share: i32,
369 pub now_rank: i32,
370 pub his_rank: i32,
371 pub like: i32,
372 pub dislike: i32,
373 pub vt: i32,
374 pub vv: i32,
375 pub fav_g: i32,
376 pub like_g: i32,
377}
378
379#[derive(Debug, Deserialize, Serialize)]
380pub struct AIRcmd {
381 pub id: i64,
382 pub goto: String,
383 pub trackid: String,
384 pub uniq_id: String,
385}
386
387#[derive(Debug, Deserialize, Serialize)]
388pub struct HotShare {
389 pub show: bool,
390 pub list: Vec<serde_json::Value>,
391}
392
393#[derive(Debug, Deserialize, Serialize)]
394pub struct Emergency {
395 pub no_like: bool,
396 pub no_coin: bool,
397 pub no_fav: bool,
398 pub no_share: bool,
399}
400
401#[derive(Debug, Deserialize, Serialize)]
402pub struct ViewAddit {
403 #[serde(rename = "63")]
404 pub field_63: bool,
405 #[serde(rename = "64")]
406 pub field_64: bool,
407 #[serde(rename = "69")]
408 pub field_69: bool,
409 #[serde(rename = "71")]
410 pub field_71: bool,
411 #[serde(rename = "72")]
412 pub field_72: bool,
413}
414
415pub type VideoDetailResponse = BpiResponse<VideoDetailData>;
417
418impl BpiClient {
419 pub async fn video_detail(
433 &self,
434 aid: Option<u64>,
435 bvid: Option<&str>,
436 need_elec: Option<u8>
437 ) -> Result<VideoDetailResponse, BpiError> {
438 let aid = aid.map(|aid| aid.to_string());
439 let bvid = bvid.map(|bvid| bvid.to_string());
440 let need_elec = need_elec.map(|need_elec| need_elec.to_string());
441
442 self
443 .get("https://api.bilibili.com/x/web-interface/view/detail")
444 .query(
445 &[
446 ("aid", aid),
447 ("bvid", bvid),
448 ("need_elec", need_elec),
449 ]
450 )
451 .send_bpi("视频超详细信息").await
452 }
453}
454
455#[cfg(test)]
456mod tests {
457 use super::*;
458
459 #[tokio::test]
460 async fn test_video_detail() {
461 let bpi = BpiClient::new();
462
463 let aid = Some(10001);
464 let bvid = None;
466
467 match bpi.video_detail(aid, bvid, Some(0)).await {
468 Ok(resp) => {
469 if resp.code == 0 {
470 } else {
473 tracing::info!("请求失败: code={}, message={}", resp.code, resp.message);
474 }
475 }
476 Err(err) => {
477 panic!("请求出错: {}", err);
478 }
479 }
480 }
481}