bpi_rs/dynamic/
detail.rs

1use crate::models::{Official, Pendant, Vip};
2use crate::{BilibiliRequest, BpiClient, BpiError, BpiResponse};
3use serde::{Deserialize, Serialize};
4// --- 动态详情 API 结构体 ---
5
6/// 动态详情响应数据
7#[derive(Debug, Clone, Deserialize, Serialize)]
8pub struct DynamicDetailData {
9    pub item: DynamicDetailItem,
10}
11
12/// 动态卡片内容,作为多个 API 的共享结构体
13#[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/// 动态卡片内容,作为多个 API 的共享结构体
26#[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// --- 动态点赞与转发列表 API 结构体 ---
70
71/// 点赞或转发的用户列表项
72#[derive(Debug, Clone, Deserialize, Serialize)]
73pub struct DynamicReactionItem {
74    pub action: String,
75    /// 1: 对方仅关注了发送者    2: 发送者关注了对方
76    pub attend: u8,
77    pub desc: String,
78    pub face: String,
79    pub mid: String,
80    pub name: String,
81}
82
83/// 动态点赞与转发列表响应数据
84#[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// --- 动态抽奖详情 API 结构体 ---
93
94/// 抽奖结果
95#[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/// 动态抽奖详情响应数据
104#[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>, // 使用 Value 以应对可选的嵌套对象
117                                                   // ... 其他字段
118}
119
120// --- 动态转发列表 API 结构体 ---
121
122/// 动态转发列表响应数据
123#[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// --- 获取动态图片 API 结构体 ---
137
138/// 动态图片信息
139#[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/// 动态图片列表响应数据
148#[derive(Debug, Clone, Deserialize, Serialize)]
149pub struct DynamicPicsData {
150    pub data: Vec<DynamicPic>,
151}
152
153impl BpiClient {
154    /// 获取动态详情
155    ///
156    /// 文档: https://github.com/SocialSisterYi/bilibili-API-collect/tree/master/docs/dynamic
157    ///
158    /// 参数
159    ///
160    /// | 名称 | 类型 | 说明 |
161    /// | ---- | ---- | ---- |
162    /// | `id` | &str | 动态 ID |
163    /// | `features` | Option<&str> | 功能特性,如 `itemOpusStyle,opusBigCover,onlyfansVote...` |
164    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            // 默认值处理
177            req = req.query(&[("features", "htmlNewStyle,itemOpusStyle,decorationCard")]);
178        }
179
180        req.send_bpi("获取动态详情").await
181    }
182
183    /// 获取动态点赞与转发列表
184    ///
185    /// 文档: https://github.com/SocialSisterYi/bilibili-API-collect/tree/master/docs/dynamic
186    ///
187    /// 参数
188    ///
189    /// | 名称 | 类型 | 说明 |
190    /// | ---- | ---- | ---- |
191    /// | `id` | &str | 动态 ID |
192    /// | `offset` | Option<&str> | 偏移量,用于翻页 |
193    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    /// 获取动态抽奖详情
210    ///
211    /// 文档: https://github.com/SocialSisterYi/bilibili-API-collect/tree/master/docs/dynamic
212    ///
213    /// 参数
214    ///
215    /// | 名称 | 类型 | 说明 |
216    /// | ---- | ---- | ---- |
217    /// | `business_id` | &str | 动态 ID |
218    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    /// 获取动态转发列表
234    ///
235    /// 文档: https://github.com/SocialSisterYi/bilibili-API-collect/tree/master/docs/dynamic
236    ///
237    /// 参数
238    ///
239    /// | 名称 | 类型 | 说明 |
240    /// | ---- | ---- | ---- |
241    /// | `id` | &str | 动态 ID |
242    /// | `offset` | Option<&str> | 偏移量,用于翻页 |
243    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    /// 获取动态中图片列表
260    ///
261    /// 文档: https://github.com/SocialSisterYi/bilibili-API-collect/tree/master/docs/dynamic
262    ///
263    /// 参数
264    ///
265    /// | 名称 | 类型 | 说明 |
266    /// | ---- | ---- | ---- |
267    /// | `id` | &str | 动态 ID |
268    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    /// 获取转发动态信息
276    ///
277    /// 文档: https://github.com/SocialSisterYi/bilibili-API-collect/tree/master/docs/dynamic
278    ///
279    /// 参数
280    ///
281    /// | 名称 | 类型 | 说明 |
282    /// | ---- | ---- | ---- |
283    /// | `id` | &str | 动态 ID |
284    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}