Skip to main content

bpi_rs/comment/
list.rs

1//! 评论查询 API
2//!
3//! [参考文档](https://github.com/Yuelioi/bilibili-API-collect/tree/cfc5fddcc8a94b74d91970bb5b4eaeb349addc47/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/Yuelioi/bilibili-API-collect/tree/cfc5fddcc8a94b74d91970bb5b4eaeb349addc47/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
120            .get("https://api.bilibili.com/x/v2/reply")
121            .query(&params)
122            .send_bpi("获取评论主列表").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/Yuelioi/bilibili-API-collect/tree/cfc5fddcc8a94b74d91970bb5b4eaeb349addc47/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
161            .get("https://api.bilibili.com/x/v2/reply/reply")
162            .query(&params)
163            .send_bpi("获取子评论列表").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/Yuelioi/bilibili-API-collect/tree/cfc5fddcc8a94b74d91970bb5b4eaeb349addc47/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
202            .get("https://api.bilibili.com/x/v2/reply/hot")
203            .query(&params)
204            .send_bpi("获取评论区热评列表").await
205    }
206    /// 获取评论区评论总数
207    ///
208    /// # 参数
209    /// | 名称 | 类型 | 说明 |
210    /// | ---- | ---- | ---- |
211    /// | `type` | i32 | 评论区类型 |
212    /// | `oid` | i64 | 对象 ID |
213    ///
214    /// # 文档
215    /// [获取评论总数](https://github.com/Yuelioi/bilibili-API-collect/tree/cfc5fddcc8a94b74d91970bb5b4eaeb349addc47/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 = [
222            ("type", r#type.to_string()),
223            ("oid", oid.to_string()),
224        ];
225        self
226            .get("https://api.bilibili.com/x/v2/reply/count")
227            .query(&params)
228            .send_bpi("获取评论区评论总数").await
229    }
230}
231
232#[cfg(test)]
233mod tests {
234    use super::*;
235    use tracing::info;
236
237    const TEST_TYPE: i32 = 1;
238    const TEST_OID: i64 = 23199;
239    const TEST_ROOT_RPID: i64 = 2554491176;
240
241    #[tokio::test]
242    async fn test_comment_list() -> Result<(), Box<BpiError>> {
243        let bpi = BpiClient::new();
244
245        let result = bpi.comment_list(
246            TEST_TYPE,
247            TEST_OID,
248            Some(1),
249            Some(5),
250            Some(0),
251            Some(0)
252        ).await?;
253        let data = result.into_data()?;
254        info!("总评论数: {}", data.replies.unwrap().len());
255
256        Ok(())
257    }
258
259    #[tokio::test]
260    async fn test_comment_replies() -> Result<(), Box<BpiError>> {
261        let bpi = BpiClient::new();
262
263        let result = bpi.comment_replies(
264            TEST_TYPE,
265            TEST_OID,
266            TEST_ROOT_RPID,
267            Some(1),
268            Some(5)
269        ).await?;
270        let data = result.into_data()?;
271        info!("总评论数: {}", data.replies.unwrap().len());
272
273        Ok(())
274    }
275
276    #[tokio::test]
277    async fn test_comment_hot() -> Result<(), Box<BpiError>> {
278        let bpi = BpiClient::new();
279        let root_rpid = 654321;
280
281        let result = bpi.comment_hot(TEST_TYPE, TEST_OID, root_rpid, Some(1), Some(5)).await?;
282        let data = result.into_data()?;
283
284        info!("热评数量: {}", data.replies.len());
285        for comment in data.replies.iter() {
286            info!("热评内容: {}", comment.content.message);
287        }
288
289        Ok(())
290    }
291
292    #[tokio::test]
293    async fn test_comment_count() -> Result<(), Box<BpiError>> {
294        let bpi = BpiClient::new();
295
296        let result = bpi.comment_count(TEST_TYPE, TEST_OID).await?;
297
298        let data = result.into_data()?;
299        info!("评论总数: {}", data.count);
300
301        Ok(())
302    }
303}