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(
179 &self,
180 aid: Option<u64>,
181 bvid: Option<&str>,
182 graph_version: u64,
183 edge_id: Option<u64>,
184 ) -> Result<BpiResponse<InteractiveVideoInfoResponseData>, BpiError> {
185 if aid.is_none() && bvid.is_none() {
186 return Err(BpiError::parse("必须提供 aid 或 bvid"));
187 }
188
189 let mut req = self
190 .get("https://api.bilibili.com/x/stein/edgeinfo_v2")
191 .query(&[("graph_version", &graph_version.to_string())]);
192
193 if let Some(a) = aid {
194 req = req.query(&[("aid", &a.to_string())]);
195 }
196 if let Some(b) = bvid {
197 req = req.query(&[("bvid", b)]);
198 }
199 if let Some(e) = edge_id {
200 req = req.query(&[("edge_id", &e.to_string())]);
201 }
202
203 req.send_bpi("获取互动视频模块详细信息").await
204 }
205}
206
207#[cfg(test)]
210mod tests {
211 use super::*;
212 use tracing::info;
213
214 const TEST_AID: u64 = 114347430905959;
215 const TEST_GRAPH_VERSION: u64 = 1273647;
216
217 #[tokio::test]
218
219 async fn test_video_interactive_video_info_by_aid() -> Result<(), BpiError> {
220 let bpi = BpiClient::new();
221 let resp = bpi
222 .video_interactive_video_info(Some(TEST_AID), None, TEST_GRAPH_VERSION, None)
223 .await?;
224 let data = resp.into_data()?;
225
226 info!("互动视频信息: {:?}", data);
227 assert!(!data.title.is_empty());
228 assert!(!data.story_list.is_empty());
229
230 Ok(())
231 }
232}