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