1use crate::models::{Official, Pendant, Vip};
2use crate::{BilibiliRequest, BpiClient, BpiError, BpiResponse};
3use serde::{Deserialize, Serialize};
4#[derive(Debug, Clone, Deserialize, Serialize)]
8pub struct DynamicDetailData {
9 pub item: DynamicDetailItem,
10}
11
12#[derive(Debug, Clone, Deserialize, Serialize)]
14pub struct DynamicDetailItem {
15 pub id_str: String,
16 pub basic: DynamicBasic,
17
18 pub modules: serde_json::Value,
19
20 pub r#type: String,
21
22 pub visible: bool,
23}
24
25#[derive(Debug, Clone, Deserialize, Serialize)]
27pub struct DynamicForwardItem {
28 pub desc: Desc,
29 pub id_str: String,
30 pub pub_time: String,
31 pub user: User,
32}
33
34#[derive(Debug, Clone, Serialize, Deserialize)]
35pub struct Desc {
36 pub rich_text_nodes: Vec<RichTextNode>,
37 pub text: String,
38}
39
40#[derive(Debug, Clone, Serialize, Deserialize)]
41pub struct RichTextNode {
42 pub orig_text: String,
43 pub text: String,
44 #[serde(rename = "type")]
45 pub type_field: String,
46}
47
48#[derive(Debug, Clone, Serialize, Deserialize)]
49pub struct User {
50 pub face: String,
51 pub face_nft: bool,
52 pub mid: i64,
53 pub name: String,
54 pub official: Official,
55 pub pendant: Pendant,
56 pub vip: Vip,
57}
58
59#[derive(Debug, Clone, Deserialize, Serialize)]
60pub struct DynamicBasic {
61 pub comment_id_str: String,
62 pub comment_type: i64,
63 pub editable: bool,
64 pub jump_url: String,
65 pub like_icon: serde_json::Value,
66 pub rid_str: String,
67}
68
69#[derive(Debug, Clone, Deserialize, Serialize)]
73pub struct DynamicReactionItem {
74 pub action: String,
75 pub attend: u8,
77 pub desc: String,
78 pub face: String,
79 pub mid: String,
80 pub name: String,
81}
82
83#[derive(Debug, Clone, Deserialize, Serialize)]
85pub struct DynamicReactionData {
86 pub has_more: bool,
87 pub items: Vec<DynamicReactionItem>,
88 pub offset: String,
89 pub total: u64,
90}
91
92#[derive(Debug, Clone, Deserialize, Serialize)]
96pub struct LotteryResultItem {
97 pub uid: u64,
98 pub name: String,
99 pub face: String,
100 pub hongbao_money: Option<f64>,
101}
102
103#[derive(Debug, Clone, Deserialize, Serialize)]
105pub struct DynamicLotteryData {
106 pub lottery_id: u64,
107 pub sender_uid: u64,
108 pub business_type: u8,
109 pub business_id: u64,
110 pub status: u8,
111 pub lottery_time: u64,
112 pub participants: u64,
113 pub first_prize_cmt: String,
114 pub second_prize_cmt: Option<String>,
115 pub third_prize_cmt: Option<String>,
116 pub lottery_result: Option<serde_json::Value>, }
119
120#[derive(Debug, Clone, Deserialize, Serialize)]
124pub struct DynamicForwardData {
125 pub has_more: bool,
126 pub items: Vec<DynamicForwardItem>,
127 pub offset: String,
128 pub total: u64,
129}
130#[derive(Debug, Clone, Deserialize, Serialize)]
131
132pub struct DynamicForwardInfoData {
133 pub item: DynamicForwardItem,
134}
135
136#[derive(Debug, Clone, Deserialize, Serialize)]
140pub struct DynamicPic {
141 pub height: u64,
142 pub size: f64,
143 pub src: String,
144 pub width: u64,
145}
146
147#[derive(Debug, Clone, Deserialize, Serialize)]
149pub struct DynamicPicsData {
150 pub data: Vec<DynamicPic>,
151}
152
153impl BpiClient {
154 pub async fn dynamic_detail(
165 &self,
166 id: &str,
167 features: Option<&str>,
168 ) -> Result<BpiResponse<DynamicDetailData>, BpiError> {
169 let mut req = self
170 .get("https://api.bilibili.com/x/polymer/web-dynamic/v1/detail")
171 .query(&[("id", id)]);
172
173 if let Some(f) = features {
174 req = req.query(&[("features", f)]);
175 } else {
176 req = req.query(&[("features", "htmlNewStyle,itemOpusStyle,decorationCard")]);
178 }
179
180 req.send_bpi("获取动态详情").await
181 }
182
183 pub async fn dynamic_reactions(
194 &self,
195 id: &str,
196 offset: Option<&str>,
197 ) -> Result<BpiResponse<DynamicReactionData>, BpiError> {
198 let mut req = self
199 .get("https://api.bilibili.com/x/polymer/web-dynamic/v1/detail/reaction")
200 .query(&[("id", id)]);
201
202 if let Some(o) = offset {
203 req = req.query(&[("offset", o)]);
204 }
205
206 req.send_bpi("获取动态点赞与转发列表").await
207 }
208
209 pub async fn dynamic_lottery_notice(
219 &self,
220 business_id: &str,
221 ) -> Result<BpiResponse<DynamicLotteryData>, BpiError> {
222 let csrf = self.csrf()?;
223 self.get("https://api.vc.bilibili.com/lottery_svr/v1/lottery_svr/lottery_notice")
224 .query(&[
225 ("business_id", business_id),
226 ("business_type", "1"),
227 ("csrf", &csrf),
228 ])
229 .send_bpi("获取动态抽奖详情")
230 .await
231 }
232
233 pub async fn dynamic_forwards(
244 &self,
245 id: &str,
246 offset: Option<&str>,
247 ) -> Result<BpiResponse<DynamicForwardData>, BpiError> {
248 let mut req = self
249 .get("https://api.bilibili.com/x/polymer/web-dynamic/v1/detail/forward")
250 .query(&[("id", id)]);
251
252 if let Some(o) = offset {
253 req = req.query(&[("offset", o)]);
254 }
255
256 req.send_bpi("获取动态转发列表").await
257 }
258
259 pub async fn dynamic_pics(&self, id: &str) -> Result<BpiResponse<Vec<DynamicPic>>, BpiError> {
269 self.get("https://api.bilibili.com/x/polymer/web-dynamic/v1/detail/pic")
270 .query(&[("id", id)])
271 .send_bpi("获取动态图片列表")
272 .await
273 }
274
275 pub async fn dynamic_forward_item(
285 &self,
286 id: &str,
287 ) -> Result<BpiResponse<DynamicForwardInfoData>, BpiError> {
288 self.get("https://api.bilibili.com/x/polymer/web-dynamic/v1/detail/forward/item")
289 .query(&[("id", id)])
290 .send_bpi("获取转发动态信息")
291 .await
292 }
293}
294
295#[cfg(test)]
296mod tests {
297 use super::*;
298 use tracing::info;
299
300 #[tokio::test]
301 async fn test_get_dynamic_detail() -> Result<(), BpiError> {
302 let bpi = BpiClient::new();
303 let dynamic_id = "1099138163191840776";
304 let resp = bpi.dynamic_detail(dynamic_id, None).await?;
305 let data = resp.into_data()?;
306
307 info!("动态详情: {:?}", data.item);
308 assert_eq!(data.item.id_str, dynamic_id);
309
310 Ok(())
311 }
312
313 #[tokio::test]
314 async fn test_get_dynamic_reactions() -> Result<(), BpiError> {
315 let bpi = BpiClient::new();
316 let dynamic_id = "1099138163191840776";
317 let resp = bpi.dynamic_reactions(dynamic_id, None).await?;
318 let data = resp.into_data()?;
319
320 info!("点赞/转发总数: {}", data.total);
321 assert!(!data.items.is_empty());
322
323 Ok(())
324 }
325
326 #[tokio::test]
327 async fn test_get_lottery_notice() -> Result<(), BpiError> {
328 let bpi = BpiClient::new();
329 let dynamic_id = "969916293954142214";
330 let resp = bpi.dynamic_lottery_notice(dynamic_id).await?;
331 let data = resp.into_data()?;
332
333 info!("抽奖状态: {}", data.status);
334 assert_eq!(data.business_id.to_string(), dynamic_id);
335
336 Ok(())
337 }
338
339 #[tokio::test]
340 async fn test_get_dynamic_forwards() -> Result<(), BpiError> {
341 let bpi = BpiClient::new();
342 let dynamic_id = "1099138163191840776";
343 let resp = bpi.dynamic_forwards(dynamic_id, None).await?;
344 let data = resp.into_data()?;
345
346 info!("转发总数: {}", data.total);
347 assert!(!data.items.is_empty());
348
349 Ok(())
350 }
351
352 #[tokio::test]
353 async fn test_get_dynamic_pics() -> Result<(), BpiError> {
354 let bpi = BpiClient::new();
355 let dynamic_id = "1099138163191840776";
356 let resp = bpi.dynamic_pics(dynamic_id).await?;
357 let data = resp.into_data()?;
358
359 info!("图片数量: {}", data.len());
360 assert!(!data.is_empty());
361
362 Ok(())
363 }
364
365 #[tokio::test]
366 async fn test_get_forward_item() -> Result<(), BpiError> {
367 let bpi = BpiClient::new();
368 let dynamic_id = "1110902525317349376";
369 let resp = bpi.dynamic_forward_item(dynamic_id).await?;
370 let data = resp.into_data()?;
371
372 info!("转发动态详情: {:?}", data.item);
373 assert_eq!(data.item.id_str, dynamic_id);
374
375 Ok(())
376 }
377}