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
96 .post("https://api.bilibili.com/x/v2/reply/add")
97 .form(¶ms)
98 .send_bpi("发表评论").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
130 .post("https://api.bilibili.com/x/v2/reply/action")
131 .form(¶ms)
132 .send_bpi("点赞评论").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
165 .post("https://api.bilibili.com/x/v2/reply/hate")
166 .form(¶ms)
167 .send_bpi("点踩评论").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
196 .post("https://api.bilibili.com/x/v2/reply/del")
197 .form(¶ms)
198 .send_bpi("删除评论").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
230 .post("https://api.bilibili.com/x/v2/reply/top")
231 .form(¶ms)
232 .send_bpi("置顶评论").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
269 .post("https://api.bilibili.com/x/v2/reply/report")
270 .form(¶ms)
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 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 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 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}