bpi_rs/danmaku/
danmaku_xml.rs1use crate::{ BpiClient, BpiError };
6use flate2::read::DeflateDecoder;
7use quick_xml::de::from_str;
8use reqwest::Client;
9use std::io::Read;
10
11use serde::{ Deserialize, Serialize };
12
13#[derive(Debug, Clone, Serialize, Deserialize)]
15pub struct DanmakuMeta {
16 pub time: f32, pub danmaku_type: i32, pub font_size: i32, pub color: i32, pub send_time: i64, pub pool_type: i32, pub user_hash: String, pub dmid: i64, pub block_level: i32, }
26
27#[derive(Debug, Clone, Serialize, Deserialize)]
28#[serde(rename = "d")]
29pub struct Danmaku {
30 #[serde(rename = "$value")]
31 pub content: String, #[serde(rename = "@p")]
34 pub p_value: String, #[serde(skip_serializing)]
37 pub meta: Option<DanmakuMeta>,
38}
39
40impl Danmaku {
41 pub fn parse_p(&mut self) -> Result<(), BpiError> {
43 let parts: Vec<&str> = self.p_value.split(',').collect();
44 if parts.len() < 8 {
45 return Err(BpiError::parse("解析xml失败 弹幕参数不足8"));
46 }
47
48 let time: f32 = parts[0].parse().unwrap_or(0.0);
49 let danmaku_type: i32 = parts[1].parse().unwrap_or(1);
50 let font_size: i32 = parts[2].parse().unwrap_or(25);
51 let color: i32 = parts[3].parse().unwrap_or(16777215); let send_time: i64 = parts[4].parse().unwrap_or(0);
53 let pool_type: i32 = parts[5].parse().unwrap_or(0);
54 let user_hash = parts[6].to_string();
55 let dmid: i64 = parts[7].parse().unwrap_or(0);
56
57 let block_level = parts[8].parse().unwrap_or(0);
58 self.meta = Some(DanmakuMeta {
59 time,
60 danmaku_type,
61 font_size,
62 color,
63 send_time,
64 pool_type,
65 user_hash,
66 dmid,
67 block_level,
68 });
69 Ok(())
70 }
71}
72
73#[derive(Debug, Clone, Serialize, Deserialize)]
75#[serde(rename = "i")]
76pub struct DanmakuXml {
77 pub chatserver: String,
78 pub chatid: String,
79 pub mission: i32,
80 pub maxlimit: i32,
81 pub state: i32, pub real_name: i32,
83 pub source: String,
84 #[serde(rename = "d", default)]
85 pub danmakus: Vec<Danmaku>,
86}
87
88impl BpiClient {
89 pub async fn danmaku_xml_list_so(&self, oid: i64) -> Result<DanmakuXml, BpiError> {
100 let client = Client::builder()
101 .gzip(false)
102 .brotli(false)
103 .deflate(false) .build()?;
105
106 let bytes = client
107 .get("https://api.bilibili.com/x/v1/dm/list.so")
108 .query(&[("oid", oid.to_string())])
110 .send().await
111 .map_err(BpiError::from)?
112 .bytes().await
113 .map_err(|e| BpiError::network(format!("获取响应体失败: {}", e)))?;
114
115 let mut d = DeflateDecoder::new(&bytes[..]);
116 let mut xml = String::new();
117 d.read_to_string(&mut xml).map_err(|_| BpiError::parse("读取xml失败"))?;
118
119 let mut parsed: DanmakuXml = from_str(&xml).map_err(|_| BpiError::parse("解析xml失败"))?;
120
121 parsed.danmakus.iter_mut().try_for_each(|dm| dm.parse_p())?;
122
123 Ok(parsed)
124 }
125
126 pub async fn danmaku_xml_list(&self, cid: i64) -> Result<DanmakuXml, BpiError> {
138 let url = format!("https://comment.bilibili.com/{}.xml", cid);
139
140 let client = Client::builder()
141 .gzip(false)
142 .brotli(false)
143 .deflate(false) .build()?;
145
146 let bytes = client.get(url).send().await?.bytes().await?;
147
148 let mut d = DeflateDecoder::new(&bytes[..]);
149 let mut xml = String::new();
150 d.read_to_string(&mut xml).map_err(|_| BpiError::parse("读取xml失败"))?;
151
152 let mut parsed: DanmakuXml = from_str(&xml).map_err(|_| BpiError::parse("解析xml失败"))?;
153
154 parsed.danmakus.iter_mut().try_for_each(|dm| dm.parse_p())?;
155
156 Ok(parsed)
157 }
158}
159
160#[cfg(test)]
161mod tests {
162 use super::*;
163 use tokio::time::Instant;
164 use tracing::info;
165
166 #[tokio::test]
167 async fn test_get_danmaku_xml_api() -> Result<(), Box<BpiError>> {
168 let bpi = BpiClient::new();
169 let start = Instant::now();
170
171 let data = bpi.danmaku_xml_list_so(16546).await?;
172 let duration = start.elapsed();
173
174 info!("耗时1 {:?} 弹幕装填个数: {:?} ", duration, data.danmakus.len());
175 Ok(())
176 }
177
178 #[tokio::test]
179 async fn test_get_danmaku_xml_cid() -> Result<(), Box<BpiError>> {
180 let bpi = BpiClient::new();
181 let start = Instant::now();
182
183 let data = bpi.danmaku_xml_list(16546).await?;
184 let duration = start.elapsed();
185
186 info!("耗时2 {:?} 弹幕装填个数: {:?} ", duration, data.danmakus.len());
187
188 Ok(())
189 }
190}