Skip to main content

bpi_rs/comment/
action.rs

1//! 评论区相关操作 API
2//!
3//! [参考文档](https://github.com/Yuelioi/bilibili-API-collect/tree/cfc5fddcc8a94b74d91970bb5b4eaeb349addc47/docs/comment/action)
4
5use crate::{ BilibiliRequest, BpiClient, BpiError, BpiResponse };
6use serde::{ Deserialize, Serialize };
7
8/// 评论区类型枚举(部分示例,需按需求扩展)
9#[derive(Debug, Clone, Copy, Serialize)]
10#[serde(rename_all = "lowercase")]
11pub enum CommentType {
12    Video = 1, // 视频
13    Article = 12, // 专栏
14    Dynamic = 17, // 动态
15    Unknown = 0,
16}
17
18/// 举报原因枚举
19#[derive(Debug, Clone, Copy, Serialize)]
20pub enum ReportReason {
21    Other = 0,
22    Ad = 1,
23    Porn = 2,
24    Spam = 3,
25    Flame = 4,
26    Spoiler = 5,
27    Politics = 6,
28    Abuse = 7,
29    Irrelevant = 8,
30    Illegal = 9,
31    Vulgar = 10,
32    Phishing = 11,
33    Scam = 12,
34    Rumor = 13,
35    Incitement = 14,
36    Privacy = 15,
37    FloorSnatching = 16,
38    HarmfulToYouth = 17,
39}
40
41/// 评论成功返回数据
42#[derive(Debug, Serialize, Clone, Deserialize)]
43pub struct CommentData {
44    pub rpid: u64,
45    pub rpid_str: String,
46    pub root: u64,
47    pub root_str: String,
48    pub parent: u64,
49    pub parent_str: String,
50    pub dialog: u64,
51    pub dialog_str: String,
52    pub success_toast: Option<String>,
53}
54
55/// 点赞评论
56impl BpiClient {
57    /// 发表评论
58    ///
59    /// 在指定评论区发表评论,支持回复根评论或父评论。
60    ///
61    /// # 参数
62    /// | 名称 | 类型 | 说明 |
63    /// | ---- | ---- | ---- |
64    /// | `type` | CommentType | 评论区类型 |
65    /// | `oid` | u64 | 对象 ID |
66    /// | `message` | &str | 评论内容 |
67    /// | `root` | `Option<u64>` | 根评论 rpid,可选 |
68    /// | `parent` | `Option<u64>` | 父评论 rpid,可选 |
69    ///
70    /// # 文档
71    /// [发表评论](https://github.com/Yuelioi/bilibili-API-collect/tree/cfc5fddcc8a94b74d91970bb5b4eaeb349addc47/docs/comment/action.md#发表评论)
72    pub async fn comment_add(
73        &self,
74        r#type: CommentType,
75        oid: u64,
76        message: &str,
77        root: Option<u64>,
78        parent: Option<u64>
79    ) -> Result<BpiResponse<CommentData>, BpiError> {
80        let csrf = self.csrf()?;
81        let mut params = vec![
82            ("type", (r#type as u32).to_string()),
83            ("oid", oid.to_string()),
84            ("message", message.to_string()),
85            ("plat", "1".to_string()), // 默认 web
86            ("csrf", csrf.to_string())
87        ];
88        if let Some(r) = root {
89            params.push(("root", r.to_string()));
90        }
91        if let Some(p) = parent {
92            params.push(("parent", p.to_string()));
93        }
94
95        self
96            .post("https://api.bilibili.com/x/v2/reply/add")
97            .form(&params)
98            .send_bpi("发表评论").await
99    }
100    /// 点赞评论
101    ///
102    /// # 参数
103    /// | 名称 | 类型 | 说明 |
104    /// | ---- | ---- | ---- |
105    /// | `type` | CommentType | 评论区类型 |
106    /// | `oid` | u64 | 对象 ID |
107    /// | `rpid` | u64 | 评论 rpid |
108    /// | `action` | u8 | 操作:0 取消,1 点赞 |
109    ///
110    /// # 文档
111    /// [点赞评论](https://github.com/Yuelioi/bilibili-API-collect/tree/cfc5fddcc8a94b74d91970bb5b4eaeb349addc47/docs/comment/action.md#点赞评论)
112    pub async fn comment_like(
113        &self,
114        r#type: CommentType,
115        oid: u64,
116        rpid: u64,
117        action: u8
118    ) -> Result<BpiResponse<serde_json::Value>, BpiError> {
119        let csrf = self.csrf()?;
120
121        let params = [
122            ("type", (r#type as u32).to_string()),
123            ("oid", oid.to_string()),
124            ("rpid", rpid.to_string()),
125            ("action", action.to_string()), // 0 取消,1 点赞
126            ("csrf", csrf.to_string()),
127        ];
128
129        self
130            .post("https://api.bilibili.com/x/v2/reply/action")
131            .form(&params)
132            .send_bpi("点赞评论").await
133    }
134
135    /// 点踩评论
136    ///
137    /// # 参数
138    /// | 名称 | 类型 | 说明 |
139    /// | ---- | ---- | ---- |
140    /// | `type` | CommentType | 评论区类型 |
141    /// | `oid` | u64 | 对象 ID |
142    /// | `rpid` | u64 | 评论 rpid |
143    /// | `action` | u8 | 操作:0 取消,1 点踩 |
144    ///
145    /// # 文档
146    /// [点踩评论](https://github.com/Yuelioi/bilibili-API-collect/tree/cfc5fddcc8a94b74d91970bb5b4eaeb349addc47/docs/comment/action.md#点踩评论)
147    pub async fn comment_dislike(
148        &self,
149        r#type: CommentType,
150        oid: u64,
151        rpid: u64,
152        action: u8
153    ) -> Result<BpiResponse<serde_json::Value>, BpiError> {
154        let csrf = self.csrf()?;
155
156        let params = [
157            ("type", (r#type as u32).to_string()),
158            ("oid", oid.to_string()),
159            ("rpid", rpid.to_string()),
160            ("action", action.to_string()), // 0 取消,1 点踩
161            ("csrf", csrf.to_string()),
162        ];
163
164        self
165            .post("https://api.bilibili.com/x/v2/reply/hate")
166            .form(&params)
167            .send_bpi("点踩评论").await
168    }
169    /// 删除评论
170    ///
171    /// # 参数
172    /// | 名称 | 类型 | 说明 |
173    /// | ---- | ---- | ---- |
174    /// | `type` | CommentType | 评论区类型 |
175    /// | `oid` | u64 | 对象 ID |
176    /// | `rpid` | u64 | 评论 rpid |
177    ///
178    /// # 文档
179    /// [删除评论](https://github.com/Yuelioi/bilibili-API-collect/tree/cfc5fddcc8a94b74d91970bb5b4eaeb349addc47/docs/comment/action.md#删除评论)
180    pub async fn comment_delete(
181        &self,
182        r#type: CommentType,
183        oid: u64,
184        rpid: u64
185    ) -> Result<BpiResponse<serde_json::Value>, BpiError> {
186        let csrf = self.csrf()?;
187
188        let params = [
189            ("type", (r#type as u32).to_string()),
190            ("oid", oid.to_string()),
191            ("rpid", rpid.to_string()),
192            ("csrf", csrf.to_string()),
193        ];
194
195        self
196            .post("https://api.bilibili.com/x/v2/reply/del")
197            .form(&params)
198            .send_bpi("删除评论").await
199    }
200    /// 置顶评论
201    ///
202    /// # 参数
203    /// | 名称 | 类型 | 说明 |
204    /// | ---- | ---- | ---- |
205    /// | `type` | CommentType | 评论区类型 |
206    /// | `oid` | u64 | 对象 ID |
207    /// | `rpid` | u64 | 评论 rpid |
208    /// | `action` | u8 | 操作:0 取消置顶,1 置顶 |
209    ///
210    /// # 文档
211    /// [置顶评论](https://github.com/Yuelioi/bilibili-API-collect/tree/cfc5fddcc8a94b74d91970bb5b4eaeb349addc47/docs/comment/action.md#置顶评论)
212    pub async fn comment_top(
213        &self,
214        r#type: CommentType,
215        oid: u64,
216        rpid: u64,
217        action: u8
218    ) -> Result<BpiResponse<serde_json::Value>, BpiError> {
219        let csrf = self.csrf()?;
220
221        let params = [
222            ("type", (r#type as u32).to_string()),
223            ("oid", oid.to_string()),
224            ("rpid", rpid.to_string()),
225            ("action", action.to_string()), // 0 取消置顶,1 置顶
226            ("csrf", csrf.to_string()),
227        ];
228
229        self
230            .post("https://api.bilibili.com/x/v2/reply/top")
231            .form(&params)
232            .send_bpi("置顶评论").await
233    }
234
235    /// 举报评论
236    ///
237    /// # 参数
238    /// | 名称 | 类型 | 说明 |
239    /// | ---- | ---- | ---- |
240    /// | `type` | CommentType | 评论区类型 |
241    /// | `oid` | u64 | 对象 ID |
242    /// | `rpid` | u64 | 评论 rpid |
243    /// | `reason` | ReportReason | 举报原因 |
244    /// | `content` | `Option<&str>` | 举报内容,可选 |
245    ///
246    /// # 文档
247    /// [举报评论](https://github.com/Yuelioi/bilibili-API-collect/tree/cfc5fddcc8a94b74d91970bb5b4eaeb349addc47/docs/comment/action.md#举报评论)
248    pub async fn comment_report(
249        &self,
250        r#type: CommentType,
251        oid: u64,
252        rpid: u64,
253        reason: ReportReason,
254        content: Option<&str>
255    ) -> Result<BpiResponse<serde_json::Value>, BpiError> {
256        let csrf = self.csrf()?;
257        let mut params = vec![
258            ("type", (r#type as u32).to_string()),
259            ("oid", oid.to_string()),
260            ("rpid", rpid.to_string()),
261            ("reason", (reason as u32).to_string()),
262            ("csrf", csrf)
263        ];
264        if let Some(c) = content {
265            params.push(("content", c.to_string()));
266        }
267
268        self
269            .post("https://api.bilibili.com/x/v2/reply/report")
270            .form(&params)
271            .send_bpi("举报评论").await
272    }
273}
274
275#[cfg(test)]
276mod tests {
277    use super::*;
278    use std::time::Duration;
279    use std::time::{ SystemTime, UNIX_EPOCH };
280    use tokio::time;
281
282    const TEST_AID: u64 = 851944245;
283
284    /// 测试辅助函数:添加评论并返回rpid
285    async fn add_test_comment() -> Result<u64, BpiError> {
286        let bpi = BpiClient::new();
287        let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs();
288
289        // 简单伪随机:用当前秒对一个常数取模再加偏移
290        let random_secs = (now % 1_000_000) + 1_600_000_000;
291        let resp = bpi.comment_add(
292            CommentType::Video,
293            TEST_AID,
294            &random_secs.to_string(),
295            None,
296            None
297        ).await?;
298
299        let data = resp.into_data()?;
300        Ok(data.rpid)
301    }
302
303    /// 测试辅助函数:删除评论
304    async fn delete_test_comment(rpid: u64) -> Result<(), BpiError> {
305        let bpi = BpiClient::new();
306        bpi.comment_delete(CommentType::Video, TEST_AID, rpid).await?;
307        Ok(())
308    }
309
310    #[tokio::test]
311    async fn test_comment_like() -> Result<(), BpiError> {
312        let rpid = add_test_comment().await?;
313        time::sleep(Duration::from_secs(3)).await;
314
315        let bpi = BpiClient::new();
316        let resp = bpi.comment_like(CommentType::Video, TEST_AID, rpid, 1).await?;
317        assert_eq!(resp.code, 0);
318
319        time::sleep(Duration::from_secs(3)).await;
320        delete_test_comment(rpid).await?;
321
322        Ok(())
323    }
324
325    #[tokio::test]
326    async fn test_comment_dislike() -> Result<(), BpiError> {
327        let rpid = add_test_comment().await?;
328        time::sleep(Duration::from_secs(3)).await;
329
330        let bpi = BpiClient::new();
331        let resp = bpi.comment_dislike(CommentType::Video, TEST_AID, rpid, 1).await?;
332
333        assert_eq!(resp.code, 0);
334
335        time::sleep(Duration::from_secs(3)).await;
336        delete_test_comment(rpid).await?;
337
338        Ok(())
339    }
340}