Skip to main content

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)]
131pub struct DynamicForwardInfoData {
132    pub item: DynamicForwardItem,
133}
134
135// --- 获取动态图片 API 结构体 ---
136
137/// 动态图片信息
138#[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/// 动态图片列表响应数据
147#[derive(Debug, Clone, Deserialize, Serialize)]
148pub struct DynamicPicsData {
149    pub data: Vec<DynamicPic>,
150}
151
152impl BpiClient {
153    /// 获取动态详情
154    ///
155    /// # 文档
156    /// [查看API文档](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    /// # 文档
186    /// [查看API文档](https://github.com/SocialSisterYi/bilibili-API-collect/tree/master/docs/dynamic)
187    ///
188    /// # 参数
189    ///
190    /// | 名称 | 类型 | 说明 |
191    /// | ---- | ---- | ---- |
192    /// | `id` | &str | 动态 ID |
193    /// | `offset` | `Option<&str>` | 偏移量,用于翻页 |
194    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    /// 获取动态抽奖详情
211    ///
212    /// # 文档
213    /// [查看API文档](https://github.com/SocialSisterYi/bilibili-API-collect/tree/master/docs/dynamic)
214    ///
215    /// # 参数
216    ///
217    /// | 名称 | 类型 | 说明 |
218    /// | ---- | ---- | ---- |
219    /// | `business_id` | &str | 动态 ID |
220    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    /// 获取动态转发列表
238    ///
239    /// # 文档
240    /// [查看API文档](https://github.com/SocialSisterYi/bilibili-API-collect/tree/master/docs/dynamic)
241    ///
242    /// # 参数
243    ///
244    /// | 名称 | 类型 | 说明 |
245    /// | ---- | ---- | ---- |
246    /// | `id` | &str | 动态 ID |
247    /// | `offset` | `Option<&str>` | 偏移量,用于翻页 |
248    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    /// 获取动态中图片列表
265    ///
266    /// # 文档
267    /// [查看API文档](https://github.com/SocialSisterYi/bilibili-API-collect/tree/master/docs/dynamic)
268    ///
269    /// # 参数
270    ///
271    /// | 名称 | 类型 | 说明 |
272    /// | ---- | ---- | ---- |
273    /// | `id` | &str | 动态 ID |
274    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    /// 获取转发动态信息
282    ///
283    /// # 文档
284    /// [查看API文档](https://github.com/SocialSisterYi/bilibili-API-collect/tree/master/docs/dynamic)
285    ///
286    /// # 参数
287    ///
288    /// | 名称 | 类型 | 说明 |
289    /// | ---- | ---- | ---- |
290    /// | `id` | &str | 动态 ID |
291    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}