1use crate::{ BilibiliRequest, BpiClient, BpiError, BpiResponse };
5use serde::{ Deserialize, Serialize };
6
7#[derive(Debug, Clone, Deserialize, Serialize)]
11pub struct InteractiveVideoInfoResponseData {
12 pub title: String,
14 pub edge_id: u64,
16 #[serde(default)]
18 pub story_list: Vec<InteractiveVideoStory>,
19 pub edges: Option<InteractiveVideoEdges>,
21 pub preload: Option<InteractiveVideoPreload>,
23 #[serde(default)]
25 pub hidden_vars: Vec<InteractiveVideoHiddenVar>,
26 pub is_leaf: u8,
28 #[serde(default)]
30 pub no_tutorial: u8,
31 #[serde(default)]
33 pub no_backtracking: u8,
34 #[serde(default)]
36 pub no_evaluation: u8,
37}
38
39#[derive(Debug, Clone, Deserialize, Serialize)]
41pub struct InteractiveVideoStory {
42 pub node_id: u64,
44 pub edge_id: u64,
46 pub title: String,
48 pub cid: u64,
50 pub start_pos: u64,
52 pub cover: String,
54 #[serde(default)]
56 pub is_current: u8,
57 pub cursor: u64,
59}
60
61#[derive(Debug, Clone, Deserialize, Serialize)]
63pub struct InteractiveVideoEdges {
64 pub dimension: Option<InteractiveVideoDimension>,
66 #[serde(default)]
68 pub questions: Vec<InteractiveVideoQuestion>,
69 pub skin: Option<serde_json::Value>,
71}
72
73#[derive(Debug, Clone, Deserialize, Serialize)]
75pub struct InteractiveVideoDimension {
76 pub width: u32,
78 pub height: u32,
80 pub rotate: u8,
82 pub sar: String,
84}
85
86#[derive(Debug, Clone, Deserialize, Serialize)]
88pub struct InteractiveVideoQuestion {
89 pub id: u64,
91 #[serde(rename = "type")]
93 pub question_type: u8,
94 pub start_time_r: u32,
96 pub duration: i64,
98 pub pause_video: u8,
100 pub title: String,
102 pub choices: Vec<InteractiveVideoChoice>,
104}
105
106#[derive(Debug, Clone, Deserialize, Serialize)]
108pub struct InteractiveVideoChoice {
109 pub id: u64,
111 pub platform_action: String,
113 pub native_action: String,
115 pub condition: String,
117 pub cid: u64,
119 pub option: String,
121 #[serde(default)]
123 pub is_default: Option<u8>,
124 #[serde(default)]
126 pub is_hidden: Option<u8>,
127}
128
129#[derive(Debug, Clone, Deserialize, Serialize)]
131pub struct InteractiveVideoPreload {
132 #[serde(default)]
134 pub video: Vec<InteractiveVideoPreloadVideo>,
135}
136
137#[derive(Debug, Clone, Deserialize, Serialize)]
139pub struct InteractiveVideoPreloadVideo {
140 pub aid: u64,
142 pub cid: u64,
144}
145
146#[derive(Debug, Clone, Deserialize, Serialize)]
148pub struct InteractiveVideoHiddenVar {
149 pub value: i64,
151 pub id: String,
153 pub id_v2: String,
155 #[serde(rename = "type")]
157 pub var_type: u8,
158 pub is_show: u8,
160 pub name: String,
162}
163
164impl BpiClient {
165 pub async fn video_interactive_video_info(
180 &self,
181 aid: Option<u64>,
182 bvid: Option<&str>,
183 graph_version: u64,
184 edge_id: Option<u64>
185 ) -> Result<BpiResponse<InteractiveVideoInfoResponseData>, BpiError> {
186 if aid.is_none() && bvid.is_none() {
187 return Err(BpiError::parse("必须提供 aid 或 bvid"));
188 }
189
190 let mut req = self
191 .get("https://api.bilibili.com/x/stein/edgeinfo_v2")
192 .query(&[("graph_version", &graph_version.to_string())]);
193
194 if let Some(a) = aid {
195 req = req.query(&[("aid", &a.to_string())]);
196 }
197 if let Some(b) = bvid {
198 req = req.query(&[("bvid", b)]);
199 }
200 if let Some(e) = edge_id {
201 req = req.query(&[("edge_id", &e.to_string())]);
202 }
203
204 req.send_bpi("获取互动视频模块详细信息").await
205 }
206}
207
208#[cfg(test)]
211mod tests {
212 use super::*;
213 use tracing::info;
214
215 const TEST_AID: u64 = 114347430905959;
216 const TEST_GRAPH_VERSION: u64 = 1273647;
217
218 #[tokio::test]
219 async fn test_video_interactive_video_info_by_aid() -> Result<(), BpiError> {
220 let bpi = BpiClient::new();
221 let resp = bpi.video_interactive_video_info(
222 Some(TEST_AID),
223 None,
224 TEST_GRAPH_VERSION,
225 None
226 ).await?;
227 let data = resp.into_data()?;
228
229 info!("互动视频信息: {:?}", data);
230 assert!(!data.title.is_empty());
231 assert!(!data.story_list.is_empty());
232
233 Ok(())
234 }
235}