bpi_rs/comment/
list.rs

1//! 评论查询 API
2//!
3//! 参考文档:https://github.com/SocialSisterYi/bilibili-API-collect/blob/master/docs/comment/list.md
4
5use crate::{BilibiliRequest, BpiClient, BpiError, BpiResponse};
6use serde::{Deserialize, Serialize};
7
8use super::types::{
9    Comment, // 评论条目对象,包含评论内容、发送者信息、回复等
10    Config,
11    Control,
12    Cursor,
13    PageInfo,
14    Top,
15    Upper,
16};
17
18/// 通用的评论列表响应
19pub type CommentListResponse = BpiResponse<CommentListData>;
20
21#[derive(Debug, Clone, Serialize, Deserialize)]
22pub struct CommentListData {
23    pub page: Option<PageInfo>,
24    pub cursor: Option<Cursor>,        // 评论列表游标
25    pub replies: Option<Vec<Comment>>, // 评论列表,禁用时为 null
26    pub top: Option<Top>,              // 评论列表顶部信息
27    pub top_replies: Option<Vec<Comment>>,
28    pub effects: Option<serde_json::Value>,
29    pub assist: Option<u64>,    // 待确认
30    pub blacklist: Option<u64>, // 待确认
31    pub vote: Option<u64>,      // 投票评论?
32    pub config: Option<Config>, // 评论区显示控制
33    pub upper: Option<Upper>,   // 置顶评论
34
35    pub control: Option<Control>, // 评论区输入属性
36    pub note: Option<u32>,
37    pub cm_info: Option<serde_json::Value>, // 评论区相关信息
38
39                                            // pub page: Option<PageInfo>, // 页信息
40                                            // pub hots: Option<Vec<Comment>>, // 热评列表,禁用时为 null
41                                            // pub notice: Option<Notice>, // 评论区公告信息,无效时为 null
42                                            // pub mode: Option<u64>, // 评论区类型 id
43                                            // pub support_mode: Option<Vec<u64>>, // 评论区支持的类型 id
44                                            // pub folder: Option<Folder>, // 折叠相关信息
45                                            // pub lottery_card: Option<()>, // 待确认
46                                            // pub show_bvid: Option<bool>, // 是否显示 bvid
47}
48
49/// 公告信息
50#[derive(Debug, Clone, Serialize, Deserialize)]
51pub struct Notice {
52    pub content: Option<String>,
53    pub id: Option<u64>,
54    pub link: Option<String>,
55    pub title: Option<String>,
56}
57
58type HotCommentResponse = BpiResponse<HotCommentData>;
59
60#[derive(Debug, Clone, Deserialize, Serialize)]
61pub struct HotCommentData {
62    pub page: HotCommentPage,
63    pub replies: Vec<Comment>, // 热评列表
64}
65
66#[derive(Debug, Clone, Deserialize, Serialize)]
67pub struct HotCommentPage {
68    pub acount: i64, // 总评论数
69    pub count: i64,  // 热评数
70    pub num: i32,    // 当前页码
71    pub size: i32,   // 每页项数
72}
73
74#[derive(Debug, Clone, Deserialize, Serialize)]
75pub struct CountData {
76    count: u64,
77}
78
79impl BpiClient {
80    /// 获取评论主列表
81    ///
82    /// 获取指定评论区的评论列表,支持分页和排序。
83    ///
84    /// # 参数
85    /// | 名称 | 类型 | 说明 |
86    /// | ---- | ---- | ---- |
87    /// | `type` | i32 | 评论区类型 |
88    /// | `oid` | i64 | 对象 ID |
89    /// | `pn` | Option<i32> | 页码,可选,默认为 1 |
90    /// | `ps` | Option<i32> | 每页条数,可选,范围 1-20 |
91    /// | `sort` | Option<i32> | 排序方式,可选:0 按时间,1 按点赞,2 按回复数 |
92    /// | `nohot` | Option<i32> | 是否不显示热评,可选:0 显示,1 不显示 |
93    ///
94    /// # 文档
95    /// [获取评论主列表](https://github.com/SocialSisterYi/bilibili-API-collect/blob/master/docs/comment/list.md#获取评论主列表)
96    pub async fn comment_list(
97        &self,
98        r#type: i32,
99        oid: i64,
100        pn: Option<i32>,
101        ps: Option<i32>,
102        sort: Option<i32>,
103        nohot: Option<i32>,
104    ) -> Result<CommentListResponse, BpiError> {
105        let mut params = vec![("type", r#type.to_string()), ("oid", oid.to_string())];
106        if let Some(pn) = pn {
107            params.push(("pn", pn.to_string()));
108        }
109        if let Some(ps) = ps {
110            params.push(("ps", ps.to_string()));
111        }
112        if let Some(sort) = sort {
113            params.push(("sort", sort.to_string()));
114        }
115        if let Some(nohot) = nohot {
116            params.push(("nohot", nohot.to_string()));
117        }
118
119        self.get("https://api.bilibili.com/x/v2/reply")
120            .query(&params)
121            .send_bpi("获取评论主列表")
122            .await
123    }
124
125    /// 获取某条根评论下的子评论列表
126    ///
127    /// 获取指定根评论下的所有子评论,支持分页。
128    ///
129    /// # 参数
130    /// | 名称 | 类型 | 说明 |
131    /// | ---- | ---- | ---- |
132    /// | `type` | i32 | 评论区类型 |
133    /// | `oid` | i64 | 对象 ID |
134    /// | `root` | i64 | 根评论 rpid |
135    /// | `pn` | Option<i32> | 页码,可选,默认为 1 |
136    /// | `ps` | Option<i32> | 每页条数,可选 |
137    ///
138    /// # 文档
139    /// [获取子评论列表](https://github.com/SocialSisterYi/bilibili-API-collect/blob/master/docs/comment/list.md#获取子评论列表)
140    pub async fn comment_replies(
141        &self,
142        r#type: i32,
143        oid: i64,
144        root: i64,
145        pn: Option<i32>,
146        ps: Option<i32>,
147    ) -> Result<CommentListResponse, BpiError> {
148        let mut params = vec![
149            ("type", r#type.to_string()),
150            ("oid", oid.to_string()),
151            ("root", root.to_string()),
152        ];
153        if let Some(pn) = pn {
154            params.push(("pn", pn.to_string()));
155        }
156        if let Some(ps) = ps {
157            params.push(("ps", ps.to_string()));
158        }
159
160        self.get("https://api.bilibili.com/x/v2/reply/reply")
161            .query(&params)
162            .send_bpi("获取子评论列表")
163            .await
164    }
165
166    /// 获取评论区热评列表
167    ///
168    /// 获取指定根评论下的热评列表,支持分页。
169    ///
170    /// # 参数
171    /// | 名称 | 类型 | 说明 |
172    /// | ---- | ---- | ---- |
173    /// | `type` | i32 | 评论区类型 |
174    /// | `oid` | i64 | 对象 ID |
175    /// | `root` | i64 | 根评论 rpid |
176    /// | `pn` | Option<i32> | 页码,可选,默认为 1 |
177    /// | `ps` | Option<i32> | 每页条数,可选 |
178    ///
179    /// # 文档
180    /// [获取热评列表](https://github.com/SocialSisterYi/bilibili-API-collect/blob/master/docs/comment/list.md#获取热评列表)
181    pub async fn comment_hot(
182        &self,
183        r#type: i32,
184        oid: i64,
185        root: i64,
186        pn: Option<i32>,
187        ps: Option<i32>,
188    ) -> Result<HotCommentResponse, BpiError> {
189        let mut params = vec![
190            ("type", r#type.to_string()),
191            ("oid", oid.to_string()),
192            ("root", root.to_string()),
193        ];
194        if let Some(pn) = pn {
195            params.push(("pn", pn.to_string()));
196        }
197        if let Some(ps) = ps {
198            params.push(("ps", ps.to_string()));
199        }
200
201        self.get("https://api.bilibili.com/x/v2/reply/hot")
202            .query(&params)
203            .send_bpi("获取评论区热评列表")
204            .await
205    }
206    /// 获取评论区评论总数
207    ///
208    /// # 参数
209    /// | 名称 | 类型 | 说明 |
210    /// | ---- | ---- | ---- |
211    /// | `type` | i32 | 评论区类型 |
212    /// | `oid` | i64 | 对象 ID |
213    ///
214    /// # 文档
215    /// [获取评论总数](https://github.com/SocialSisterYi/bilibili-API-collect/blob/master/docs/comment/list.md#获取评论总数)
216    pub async fn comment_count(
217        &self,
218        r#type: i32,
219        oid: i64,
220    ) -> Result<BpiResponse<CountData>, BpiError> {
221        let params = [("type", r#type.to_string()), ("oid", oid.to_string())];
222        self.get("https://api.bilibili.com/x/v2/reply/count")
223            .query(&params)
224            .send_bpi("获取评论区评论总数")
225            .await
226    }
227}
228
229#[cfg(test)]
230mod tests {
231    use super::*;
232    use tracing::info;
233
234    const TEST_TYPE: i32 = 1;
235    const TEST_OID: i64 = 23199;
236    const TEST_ROOT_RPID: i64 = 2554491176;
237
238    #[tokio::test]
239    async fn test_comment_list() -> Result<(), Box<BpiError>> {
240        let bpi = BpiClient::new();
241
242        let result = bpi
243            .comment_list(TEST_TYPE, TEST_OID, Some(1), Some(5), Some(0), Some(0))
244            .await?;
245        let data = result.into_data()?;
246        info!("总评论数: {}", data.replies.unwrap().len());
247
248        Ok(())
249    }
250
251    #[tokio::test]
252    async fn test_comment_replies() -> Result<(), Box<BpiError>> {
253        let bpi = BpiClient::new();
254
255        let result = bpi
256            .comment_replies(TEST_TYPE, TEST_OID, TEST_ROOT_RPID, Some(1), Some(5))
257            .await?;
258        let data = result.into_data()?;
259        info!("总评论数: {}", data.replies.unwrap().len());
260
261        Ok(())
262    }
263
264    #[tokio::test]
265    async fn test_comment_hot() -> Result<(), Box<BpiError>> {
266        let bpi = BpiClient::new();
267        let root_rpid = 654321;
268
269        let result = bpi
270            .comment_hot(TEST_TYPE, TEST_OID, root_rpid, Some(1), Some(5))
271            .await?;
272        let data = result.into_data()?;
273
274        info!("热评数量: {}", data.replies.len());
275        for comment in data.replies.iter() {
276            info!("热评内容: {}", comment.content.message);
277        }
278
279        Ok(())
280    }
281
282    #[tokio::test]
283    async fn test_comment_count() -> Result<(), Box<BpiError>> {
284        let bpi = BpiClient::new();
285
286        let result = bpi.comment_count(TEST_TYPE, TEST_OID).await?;
287
288        let data = result.into_data()?;
289        info!("评论总数: {}", data.count);
290
291        Ok(())
292    }
293}