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> {
99 let client = Client::builder()
100 .gzip(false)
101 .brotli(false)
102 .deflate(false) .build()?;
104
105 let bytes = client
106 .get("https://api.bilibili.com/x/v1/dm/list.so")
107 .query(&[("oid", oid.to_string())])
109 .send()
110 .await
111 .map_err(BpiError::from)?
112 .bytes()
113 .await
114 .map_err(|e| BpiError::network(format!("获取响应体失败: {}", e)))?;
115
116 let mut d = DeflateDecoder::new(&bytes[..]);
117 let mut xml = String::new();
118 d.read_to_string(&mut xml)
119 .map_err(|_| BpiError::parse("读取xml失败"))?;
120
121 let mut parsed: DanmakuXml = from_str(&xml).map_err(|_| BpiError::parse("解析xml失败"))?;
122
123 parsed.danmakus.iter_mut().try_for_each(|dm| dm.parse_p())?;
124
125 Ok(parsed)
126 }
127
128 pub async fn danmaku_xml_list(&self, cid: i64) -> Result<DanmakuXml, BpiError> {
139 let url = format!("https://comment.bilibili.com/{}.xml", cid);
140
141 let client = Client::builder()
142 .gzip(false)
143 .brotli(false)
144 .deflate(false) .build()?;
146
147 let bytes = client.get(url).send().await?.bytes().await?;
148
149 let mut d = DeflateDecoder::new(&bytes[..]);
150 let mut xml = String::new();
151 d.read_to_string(&mut xml)
152 .map_err(|_| BpiError::parse("读取xml失败"))?;
153
154 let mut parsed: DanmakuXml = from_str(&xml).map_err(|_| BpiError::parse("解析xml失败"))?;
155
156 parsed.danmakus.iter_mut().try_for_each(|dm| dm.parse_p())?;
157
158 Ok(parsed)
159 }
160}
161
162#[cfg(test)]
163mod tests {
164 use super::*;
165 use tokio::time::Instant;
166 use tracing::info;
167
168 #[tokio::test]
169 async fn test_get_danmaku_xml_api() -> Result<(), Box<BpiError>> {
170 let bpi = BpiClient::new();
171 let start = Instant::now();
172
173 let data = bpi.danmaku_xml_list_so(16546).await?;
174 let duration = start.elapsed();
175
176 info!(
177 "耗时1 {:?} 弹幕装填个数: {:?} ",
178 duration,
179 data.danmakus.len()
180 );
181 Ok(())
182 }
183
184 #[tokio::test]
185 async fn test_get_danmaku_xml_cid() -> Result<(), Box<BpiError>> {
186 let bpi = BpiClient::new();
187 let start = Instant::now();
188
189 let data = bpi.danmaku_xml_list(16546).await?;
190 let duration = start.elapsed();
191
192 info!(
193 "耗时2 {:?} 弹幕装填个数: {:?} ",
194 duration,
195 data.danmakus.len()
196 );
197
198 Ok(())
199 }
200}