1use crate::{BilibiliRequest, BpiClient, BpiError, BpiResponse};
6use serde::{Deserialize, Serialize};
7
8#[derive(Debug, Clone, Copy, Serialize)]
10#[serde(rename_all = "lowercase")]
11pub enum CommentType {
12 Video = 1, Article = 12, Dynamic = 17, Unknown = 0,
16}
17
18#[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#[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
55impl BpiClient {
57 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()), ("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.post("https://api.bilibili.com/x/v2/reply/add")
96 .form(¶ms)
97 .send_bpi("发表评论")
98 .await
99 }
100 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()), ("csrf", csrf.to_string()),
127 ];
128
129 self.post("https://api.bilibili.com/x/v2/reply/action")
130 .form(¶ms)
131 .send_bpi("点赞评论")
132 .await
133 }
134
135 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()), ("csrf", csrf.to_string()),
162 ];
163
164 self.post("https://api.bilibili.com/x/v2/reply/hate")
165 .form(¶ms)
166 .send_bpi("点踩评论")
167 .await
168 }
169 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.post("https://api.bilibili.com/x/v2/reply/del")
196 .form(¶ms)
197 .send_bpi("删除评论")
198 .await
199 }
200 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()), ("csrf", csrf.to_string()),
227 ];
228
229 self.post("https://api.bilibili.com/x/v2/reply/top")
230 .form(¶ms)
231 .send_bpi("置顶评论")
232 .await
233 }
234
235 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.post("https://api.bilibili.com/x/v2/reply/report")
269 .form(¶ms)
270 .send_bpi("举报评论")
271 .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 async fn add_test_comment() -> Result<u64, BpiError> {
286 let bpi = BpiClient::new();
287 let now = SystemTime::now()
288 .duration_since(UNIX_EPOCH)
289 .unwrap()
290 .as_secs();
291
292 let random_secs = (now % 1_000_000) + 1_600_000_000;
294 let resp = bpi
295 .comment_add(
296 CommentType::Video,
297 TEST_AID,
298 &random_secs.to_string(),
299 None,
300 None,
301 )
302 .await?;
303
304 let data = resp.into_data()?;
305 Ok(data.rpid)
306 }
307
308 async fn delete_test_comment(rpid: u64) -> Result<(), BpiError> {
310 let bpi = BpiClient::new();
311 bpi.comment_delete(CommentType::Video, TEST_AID, rpid)
312 .await?;
313 Ok(())
314 }
315
316 #[tokio::test]
317 async fn test_comment_like() -> Result<(), BpiError> {
318 let rpid = add_test_comment().await?;
319 time::sleep(Duration::from_secs(3)).await;
320
321 let bpi = BpiClient::new();
322 let resp = bpi
323 .comment_like(CommentType::Video, TEST_AID, rpid, 1)
324 .await?;
325 assert_eq!(resp.code, 0);
326
327 time::sleep(Duration::from_secs(3)).await;
328 delete_test_comment(rpid).await?;
329
330 Ok(())
331 }
332
333 #[tokio::test]
334 async fn test_comment_dislike() -> Result<(), BpiError> {
335 let rpid = add_test_comment().await?;
336 time::sleep(Duration::from_secs(3)).await;
337
338 let bpi = BpiClient::new();
339 let resp = bpi
340 .comment_dislike(CommentType::Video, TEST_AID, rpid, 1)
341 .await?;
342
343 assert_eq!(resp.code, 0);
344
345 time::sleep(Duration::from_secs(3)).await;
346 delete_test_comment(rpid).await?;
347
348 Ok(())
349 }
350}