1use crate::models::Account;
6use serde::{Deserialize, Serialize};
7
8#[derive(Debug, Clone, Serialize, Deserialize)]
10pub struct Dimension {
11 pub width: u32,
12 pub height: u32,
13 pub rotate: u8,
14}
15
16#[derive(Debug, Clone, Serialize, Deserialize)]
18pub struct Stat {
19 pub aid: u64,
20 pub view: u64,
21 pub danmaku: u64,
22 pub reply: u64,
23 #[serde(rename = "favorite")]
24 pub favorite: Option<u64>,
25 #[serde(rename = "fav")]
26 pub fav: Option<u64>,
27 pub coin: u64,
28 pub share: u64,
29 pub now_rank: i64,
30 pub his_rank: i64,
31 pub like: u64,
32 pub dislike: u64,
33 pub evaluation: String,
34 #[serde(rename = "argue_msg")]
35 pub argue_msg: Option<String>,
36 pub vt: u64,
37 pub vv: Option<u64>,
38}
39
40#[derive(Debug, Clone, Serialize, Deserialize)]
42pub struct ArgueInfo {
43 pub argue_link: String,
44 pub argue_msg: String,
45 pub argue_type: i32,
46}
47
48#[derive(Debug, Clone, Serialize, Deserialize)]
50pub struct Owner {
51 pub mid: u64,
52 pub name: String,
53 pub face: String,
54}
55
56#[derive(Debug, Clone, Serialize, Deserialize)]
58pub struct Rights {
59 pub bp: u8,
60 pub elec: u8,
61 pub download: u8,
62 pub movie: u8,
63 pub pay: u8,
64 pub hd5: u8,
65 pub no_reprint: u8,
66 pub autoplay: u8,
67 pub ugc_pay: u8,
68 pub is_cooperation: u8,
69 pub ugc_pay_preview: u8,
70 pub no_background: Option<u8>,
71 pub clean_mode: Option<u8>,
72 pub is_stein_gate: Option<u8>,
73 pub arc_pay: u8,
74 pub free_watch: u8,
75 pub is_360: Option<u8>,
77 pub no_share: Option<u8>,
78}
79
80#[derive(Debug, Clone, Serialize, Deserialize)]
82pub struct Page {
83 pub cid: u64,
84 pub page: u32,
85 pub from: String,
86 pub part: String,
87 pub duration: u64,
88 pub vid: String,
89 pub weblink: String,
90 pub dimension: Dimension,
91 pub first_frame: Option<String>,
92 pub ctime: Option<u64>,
93}
94
95#[derive(Debug, Clone, Serialize, Deserialize)]
99pub struct SubtitleListItem {
100 pub id: u64,
101 pub lan: String,
102 pub lan_doc: String,
103 pub is_lock: bool,
104 pub subtitle_url: String,
105 #[serde(rename = "type")]
106 pub sub_type: u8,
107 pub id_str: String,
108 pub ai_type: u8,
109 pub ai_status: u8,
110 pub author: Account,
111}
112
113#[derive(Debug, Clone, Serialize, Deserialize)]
115pub struct Subtitle {
116 pub allow_submit: bool,
117 pub list: Vec<SubtitleListItem>,
118}
119
120#[derive(Debug, Clone, Serialize, Deserialize)]
122pub struct StaffVip {
123 #[serde(rename = "type")]
124 pub type_: u8,
125 pub status: u8,
126 pub due_date: u64,
127 pub vip_pay_type: u8,
128 pub theme_type: u8,
129 pub label: serde_json::Value,
130}
131
132#[derive(Debug, Clone, Serialize, Deserialize)]
134pub struct StaffOfficial {
135 pub role: i32,
136 pub title: String,
137 pub desc: String,
138 #[serde(rename = "type")]
139 pub type_: i32,
140}
141
142#[derive(Debug, Clone, Serialize, Deserialize)]
144pub struct StaffItem {
145 pub mid: u64,
146 pub title: String,
147 pub name: String,
148 pub face: String,
149 pub vip: StaffVip,
150 pub official: StaffOfficial,
151 pub follower: u64,
152 pub label_style: u8,
153}
154
155#[derive(Debug, Clone, Serialize, Deserialize)]
157pub struct HonorItem {
158 pub aid: u64,
159 pub hover_type: u8,
160 pub desc: String,
161 pub weekly_recommend_num: u64,
162}
163
164#[derive(Debug, Clone, Serialize, Deserialize)]
165pub struct HonorReply {
166 pub honor: Vec<HonorItem>,
167}
168
169#[derive(Debug, Clone, Serialize, Deserialize)]
171pub struct UserGarb {
172 pub url_image_ani_cut: String,
173}
174
175#[derive(Debug, Clone, Serialize, Deserialize)]
177pub struct EpisodeArc {
178 pub aid: u64,
179 pub videos: u32,
180 pub type_id: u32,
181 pub type_name: String,
182 pub copyright: u8,
183 pub pic: String,
184 pub title: String,
185 pub pubdate: u64,
186 pub ctime: u64,
187 pub desc: String,
188 pub state: u8,
189 pub duration: u64,
190 pub rights: Rights,
191 pub author: Owner,
192 pub stat: Stat,
193 pub dynamic: String,
194 pub dimension: Dimension,
195 pub desc_v2: serde_json::Value,
196 pub is_chargeable_season: bool,
197 pub is_blooper: bool,
198 pub enable_vt: u8,
199 pub vt_display: String,
200 pub type_id_v2: u32,
201 pub type_name_v2: String,
202 pub is_lesson_video: u8,
203}
204
205#[derive(Debug, Clone, Serialize, Deserialize)]
207pub struct SectionEpisode {
208 pub season_id: u64,
209 pub section_id: u64,
210 pub id: u64,
211 pub aid: u64,
212 pub cid: u64,
213 pub title: String,
214 pub attribute: u32,
215 pub arc: EpisodeArc,
216 pub page: Page,
217 pub bvid: String,
218 pub pages: Vec<Page>,
219}
220
221#[derive(Debug, Clone, Serialize, Deserialize)]
223pub struct Section {
224 pub season_id: u64,
225 pub id: u64,
226 pub title: String,
227 #[serde(rename = "type")]
228 pub type_: u8,
229 pub episodes: Vec<SectionEpisode>,
230}
231
232#[derive(Debug, Clone, Serialize, Deserialize)]
234pub struct UgcSeasonStat {
235 pub season_id: u64,
236 pub view: u64,
237 pub danmaku: u64,
238 pub reply: u64,
239 pub fav: u64,
240 pub coin: u64,
241 pub share: u64,
242 pub now_rank: i64,
243 pub his_rank: i64,
244 pub like: u64,
245 pub vt: u64,
246 pub vv: u64,
247}
248
249#[derive(Debug, Clone, Serialize, Deserialize)]
250pub struct UgcSeason {
251 pub id: u64,
252 pub title: String,
253 pub cover: String,
254 pub mid: u64,
255 pub intro: String,
256 pub sign_state: u8,
257 pub attribute: u32,
258 pub sections: Vec<Section>,
259 pub stat: UgcSeasonStat,
260 pub ep_count: u32,
261 pub season_type: u8,
262 pub is_pay_season: bool,
263 pub enable_vt: u8,
264}
265
266#[derive(Debug, Clone, Serialize, Deserialize)]
268pub struct VideoData {
269 pub aid: u64,
270 pub bvid: String,
271 pub videos: u32,
272 pub tid: u32,
273 pub tid_v2: u32,
274 pub tname: String,
275 pub tname_v2: String,
276 pub copyright: u8,
277 pub pic: String,
278 pub title: String,
279 pub pubdate: u64,
280 pub ctime: u64,
281 pub desc: String,
282 pub desc_v2: Option<Vec<serde_json::Value>>,
283 pub state: u32,
284 pub duration: u64, #[serde(default = "default_forward")] pub forward: u64,
287 pub mission_id: Option<u64>,
288 #[serde(default = "default_redirect_url")] pub redirect_url: String,
290 pub rights: Rights,
291 pub owner: Owner,
292 pub stat: Stat,
293 pub argue_info: ArgueInfo,
294 pub dynamic: String,
295 pub cid: u64,
296 pub dimension: Dimension,
297 pub premiere: serde_json::Value,
298 pub teenage_mode: u8,
299 pub is_chargeable_season: bool,
300 pub is_story: bool,
301 pub is_upower_exclusive: bool,
302 pub is_upower_play: bool,
303 pub is_upower_preview: bool,
304 pub no_cache: bool,
305 pub pages: Vec<Page>,
306 pub subtitle: Subtitle,
307 pub ugc_season: Option<UgcSeason>, #[serde(default = "default_staff")]
309 pub staff: Vec<StaffItem>, pub is_season_display: bool,
311 pub user_garb: UserGarb,
312 pub honor_reply: serde_json::Value,
313 pub like_icon: String,
314 pub need_jump_bv: bool,
315 pub disable_show_up_info: bool,
316 pub is_story_play: u32,
317 pub is_view_self: bool,
318 pub is_upower_exclusive_with_qa: bool,
319}
320
321fn default_forward() -> u64 {
322 0
323}
324
325fn default_redirect_url() -> String {
326 String::new()
327}
328
329fn default_staff() -> Vec<StaffItem> {
330 let v: Vec<StaffItem> = Vec::new();
331 v
332}
333
334#[cfg(test)]
335mod tests {
336 use crate::ids::Aid;
337 use crate::video::params::VideoViewParams;
338 use crate::{BpiClient, BpiError};
339
340 #[ignore = "legacy live API test; requires explicit BPI_LIVE_TEST review"]
341 #[tokio::test]
342 async fn test_video_info() -> Result<(), BpiError> {
343 let bpi = BpiClient::new().expect("client should build");
344
345 let data = bpi
346 .video()
347 .view(VideoViewParams::from_aid(Aid::new(10001)?))
348 .await?;
349
350 assert!(!data.title.is_empty());
351
352 Ok(())
353 }
354}