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)]
131pub struct DynamicForwardInfoData {
132 pub item: DynamicForwardItem,
133}
134
135#[derive(Debug, Clone, Deserialize, Serialize)]
139pub struct DynamicPic {
140 pub height: u64,
141 pub size: f64,
142 pub src: String,
143 pub width: u64,
144}
145
146#[derive(Debug, Clone, Deserialize, Serialize)]
148pub struct DynamicPicsData {
149 pub data: Vec<DynamicPic>,
150}
151
152impl BpiClient {
153 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(
195 &self,
196 id: &str,
197 offset: Option<&str>
198 ) -> Result<BpiResponse<DynamicReactionData>, BpiError> {
199 let mut req = self
200 .get("https://api.bilibili.com/x/polymer/web-dynamic/v1/detail/reaction")
201 .query(&[("id", id)]);
202
203 if let Some(o) = offset {
204 req = req.query(&[("offset", o)]);
205 }
206
207 req.send_bpi("获取动态点赞与转发列表").await
208 }
209
210 pub async fn dynamic_lottery_notice(
221 &self,
222 business_id: &str
223 ) -> Result<BpiResponse<DynamicLotteryData>, BpiError> {
224 let csrf = self.csrf()?;
225 self
226 .get("https://api.vc.bilibili.com/lottery_svr/v1/lottery_svr/lottery_notice")
227 .query(
228 &[
229 ("business_id", business_id),
230 ("business_type", "1"),
231 ("csrf", &csrf),
232 ]
233 )
234 .send_bpi("获取动态抽奖详情").await
235 }
236
237 pub async fn dynamic_forwards(
249 &self,
250 id: &str,
251 offset: Option<&str>
252 ) -> Result<BpiResponse<DynamicForwardData>, BpiError> {
253 let mut req = self
254 .get("https://api.bilibili.com/x/polymer/web-dynamic/v1/detail/forward")
255 .query(&[("id", id)]);
256
257 if let Some(o) = offset {
258 req = req.query(&[("offset", o)]);
259 }
260
261 req.send_bpi("获取动态转发列表").await
262 }
263
264 pub async fn dynamic_pics(&self, id: &str) -> Result<BpiResponse<Vec<DynamicPic>>, BpiError> {
275 self
276 .get("https://api.bilibili.com/x/polymer/web-dynamic/v1/detail/pic")
277 .query(&[("id", id)])
278 .send_bpi("获取动态图片列表").await
279 }
280
281 pub async fn dynamic_forward_item(
292 &self,
293 id: &str
294 ) -> Result<BpiResponse<DynamicForwardInfoData>, BpiError> {
295 self
296 .get("https://api.bilibili.com/x/polymer/web-dynamic/v1/detail/forward/item")
297 .query(&[("id", id)])
298 .send_bpi("获取转发动态信息").await
299 }
300}
301
302#[cfg(test)]
303mod tests {
304 use super::*;
305 use tracing::info;
306
307 #[tokio::test]
308 async fn test_get_dynamic_detail() -> Result<(), BpiError> {
309 let bpi = BpiClient::new();
310 let dynamic_id = "1099138163191840776";
311 let resp = bpi.dynamic_detail(dynamic_id, None).await?;
312 let data = resp.into_data()?;
313
314 info!("动态详情: {:?}", data.item);
315 assert_eq!(data.item.id_str, dynamic_id);
316
317 Ok(())
318 }
319
320 #[tokio::test]
321 async fn test_get_dynamic_reactions() -> Result<(), BpiError> {
322 let bpi = BpiClient::new();
323 let dynamic_id = "1099138163191840776";
324 let resp = bpi.dynamic_reactions(dynamic_id, None).await?;
325 let data = resp.into_data()?;
326
327 info!("点赞/转发总数: {}", data.total);
328 assert!(!data.items.is_empty());
329
330 Ok(())
331 }
332
333 #[tokio::test]
334 async fn test_get_lottery_notice() -> Result<(), BpiError> {
335 let bpi = BpiClient::new();
336 let dynamic_id = "969916293954142214";
337 let resp = bpi.dynamic_lottery_notice(dynamic_id).await?;
338 let data = resp.into_data()?;
339
340 info!("抽奖状态: {}", data.status);
341 assert_eq!(data.business_id.to_string(), dynamic_id);
342
343 Ok(())
344 }
345
346 #[tokio::test]
347 async fn test_get_dynamic_forwards() -> Result<(), BpiError> {
348 let bpi = BpiClient::new();
349 let dynamic_id = "1099138163191840776";
350 let resp = bpi.dynamic_forwards(dynamic_id, None).await?;
351 let data = resp.into_data()?;
352
353 info!("转发总数: {}", data.total);
354 assert!(!data.items.is_empty());
355
356 Ok(())
357 }
358
359 #[tokio::test]
360 async fn test_get_dynamic_pics() -> Result<(), BpiError> {
361 let bpi = BpiClient::new();
362 let dynamic_id = "1099138163191840776";
363 let resp = bpi.dynamic_pics(dynamic_id).await?;
364 let data = resp.into_data()?;
365
366 info!("图片数量: {}", data.len());
367 assert!(!data.is_empty());
368
369 Ok(())
370 }
371
372 #[tokio::test]
373 async fn test_get_forward_item() -> Result<(), BpiError> {
374 let bpi = BpiClient::new();
375 let dynamic_id = "1110902525317349376";
376 let resp = bpi.dynamic_forward_item(dynamic_id).await?;
377 let data = resp.into_data()?;
378
379 info!("转发动态详情: {:?}", data.item);
380 assert_eq!(data.item.id_str, dynamic_id);
381
382 Ok(())
383 }
384}