1use serde::{Deserialize, Serialize};
2
3use crate::{BilibiliRequest, BpiClient, BpiError, BpiResponse};
4
5#[derive(Debug, Clone, Deserialize, Serialize)]
10pub struct DanmakuPostData {
11 pub colorful_src: Option<serde_json::Value>, pub dmid: u64,
13 pub dmid_str: String,
14}
15
16impl BpiClient {
17 pub async fn danmaku_send(
36 &self,
37 oid: u64,
38 msg: &str,
39 avid: Option<u64>,
40 bvid: Option<&str>,
41 mode: Option<u8>,
42 typ: Option<u8>,
43 progress: Option<u32>,
44 color: Option<u32>,
45 fontsize: Option<u8>,
46 pool: Option<u8>,
47 ) -> Result<BpiResponse<DanmakuPostData>, BpiError> {
48 let csrf = self.csrf()?;
49
50 let mut form = vec![
51 ("oid", oid.to_string()),
52 ("msg", msg.to_string()),
53 ("mode", "1".to_string()),
54 ("fontsize", "25".to_string()),
55 ("color", "16777215".to_string()),
56 ("pool", "0".to_string()),
57 ("progress", "1878".to_string()),
58 ("rnd", "2".to_string()),
59 ("plat", "1".to_string()),
60 ("csrf", csrf),
61 ("checkbox_type", "0".to_string()),
62 ("colorful", "".to_string()),
63 ("gaiasource", "main_web".to_string()),
64 ("polaris_app_id", "100".to_string()),
65 ("polaris_platform", "5".to_string()),
66 ("spmid", "333.788.0.0".to_string()),
67 ("from_spmid", "333.788.0.0".to_string()),
68 ];
69
70 if let Some(m) = mode {
71 form.push(("mode", m.to_string()));
72 }
73 if let Some(t) = typ {
74 form.push(("type", t.to_string()));
75 }
76 if let Some(p) = progress {
77 form.push(("progress", p.to_string()));
78 }
79 if let Some(c) = color {
80 form.push(("color", c.to_string()));
81 }
82 if let Some(f) = fontsize {
83 form.push(("fontsize", f.to_string()));
84 }
85 if let Some(p) = pool {
86 form.push(("pool", p.to_string()));
87 }
88 if let Some(b) = bvid {
89 form.push(("bvid", b.to_string()));
90 }
91 if let Some(a) = avid {
92 form.push(("avid", a.to_string()));
93 }
94
95 let signed_params = self.get_wbi_sign2(form.clone()).await?;
97
98 self.post("https://api.bilibili.com/x/v2/dm/post")
99 .form(&signed_params)
100 .send_bpi("发送视频弹幕")
101 .await
102 }
103
104 pub async fn danmaku_send_default(
117 &self,
118 oid: u64,
119 msg: &str,
120 avid: Option<u64>,
121 bvid: Option<&str>,
122 ) -> Result<BpiResponse<DanmakuPostData>, BpiError> {
123 let csrf = self.csrf()?;
124
125 let mut form = vec![
126 ("type", "1".to_string()),
127 ("oid", oid.to_string()),
128 ("msg", msg.to_string()),
129 ("mode", "1".to_string()),
130 ("csrf", csrf),
131 ];
132
133 if let Some(b) = bvid {
134 form.push(("bvid", b.to_string()));
135 }
136 if let Some(a) = avid {
137 form.push(("avid", a.to_string()));
138 }
139
140 let signed_form = self.get_wbi_sign2(form).await?;
142
143 self.post("https://api.bilibili.com/x/v2/dm/post")
144 .form(&signed_form)
145 .send_bpi("发送视频弹幕")
146 .await
147 }
148}
149
150impl BpiClient {
155 pub async fn danmaku_recall(
168 &self,
169 cid: u64,
170 dmid: u64,
171 ) -> Result<BpiResponse<serde_json::Value>, BpiError> {
172 let csrf = self.csrf()?;
173 self.post("https://api.bilibili.com/x/dm/recall")
174 .form(&[
175 ("cid", &cid.to_string()),
176 ("dmid", &dmid.to_string()),
177 ("type", &"1".to_string()),
178 ("csrf", &csrf),
179 ])
180 .send_bpi("撤回弹幕")
181 .await
182 }
183}
184
185impl BpiClient {
190 pub async fn danmaku_buy_adv(
200 &self,
201 cid: u64,
202 ) -> Result<BpiResponse<serde_json::Value>, BpiError> {
203 let csrf = self.csrf()?;
204 self.post("https://api.bilibili.com/x/dm/adv/buy")
205 .form(&[
206 ("cid", cid.to_string()),
207 ("mode", "sp".to_string()),
208 ("csrf", csrf),
209 ])
210 .send_bpi("购买高级弹幕发送权限")
211 .await
212 }
213}
214
215#[derive(Debug, Clone, Deserialize)]
220#[serde(rename_all = "camelCase")]
221pub struct DanmakuAdvState {
222 pub coins: u8,
223 pub confirm: u8,
224 pub accept: bool,
225 pub has_buy: bool,
226}
227
228impl BpiClient {
229 pub async fn danmaku_adv_state(
239 &self,
240 cid: u64,
241 ) -> Result<BpiResponse<DanmakuAdvState>, BpiError> {
242 self.get("https://api.bilibili.com/x/dm/adv/state")
243 .query(&[("cid", cid.to_string()), ("mode", "sp".to_string())])
244 .send_bpi("检测高级弹幕发送权限")
245 .await
246 }
247}
248
249impl BpiClient {
254 pub async fn danmaku_thumbup(
266 &self,
267 oid: u64,
268 dmid: u64,
269 op: u8,
270 ) -> Result<BpiResponse<serde_json::Value>, BpiError> {
271 let csrf = self.csrf()?;
272 let mut form = vec![
273 ("oid", oid.to_string()),
274 ("dmid", dmid.to_string()),
275 ("op", op.to_string()),
276 ("csrf", csrf),
277 ];
278
279 form.push(("platform", "web_player".to_string()));
280
281 self.post("https://api.bilibili.com/x/v2/dm/thumbup/add")
282 .form(&form)
283 .send_bpi("点赞弹幕")
284 .await
285 }
286}
287
288impl BpiClient {
293 pub async fn danmaku_report(
306 &self,
307 cid: u64,
308 dmid: u64,
309 reason: u8,
310 content: Option<&str>,
311 ) -> Result<BpiResponse<serde_json::Value>, BpiError> {
312 let csrf = self.csrf()?;
313 let mut form = vec![
314 ("cid", cid.to_string()),
315 ("dmid", dmid.to_string()),
316 ("reason", reason.to_string()),
317 ("csrf", csrf),
318 ];
319
320 if let Some(c) = content {
321 form.push(("content", c.to_string()));
322 }
323
324 self.post("https://api.bilibili.com/x/dm/report/add")
325 .form(&form)
326 .send_bpi("举报弹幕")
327 .await
328 }
329}
330
331impl BpiClient {
336 pub async fn danmaku_edit_state(
348 &self,
349 oid: u64,
350 dmids: &[u64],
351 state: u8,
352 ) -> Result<BpiResponse<serde_json::Value>, BpiError> {
353 let csrf = self.csrf()?;
354 let dmids_str = dmids
355 .iter()
356 .map(|id| id.to_string())
357 .collect::<Vec<_>>()
358 .join(",");
359
360 self.post("https://api.bilibili.com/x/v2/dm/edit/state")
361 .form(&[
362 ("type", "1"),
363 ("oid", &oid.to_string()),
364 ("dmids", &dmids_str),
365 ("state", &state.to_string()),
366 ("csrf", &csrf),
367 ])
368 .send_bpi("保护&删除弹幕")
369 .await
370 }
371}
372
373impl BpiClient {
378 pub async fn danmaku_edit_pool(
390 &self,
391 oid: u64,
392 dmids: &[u64],
393 pool: u8,
394 ) -> Result<BpiResponse<serde_json::Value>, BpiError> {
395 let csrf = self.csrf()?;
396 let dmids_str = dmids
397 .iter()
398 .map(|id| id.to_string())
399 .collect::<Vec<_>>()
400 .join(",");
401
402 self.post("https://api.bilibili.com/x/v2/dm/edit/pool")
403 .form(&[
404 ("type", "1"),
405 ("oid", &oid.to_string()),
406 ("dmids", &dmids_str),
407 ("pool", &pool.to_string()),
408 ("csrf", &csrf),
409 ])
410 .send_bpi("修改字幕池")
411 .await
412 }
413}
414
415#[cfg(test)]
416mod tests {
417 use super::*;
418 use tracing::info;
419
420 #[tokio::test]
421 #[ignore]
422 async fn test_danmaku_post() {
423 let bpi = BpiClient::new();
424
425 let resp = bpi
426 .danmaku_send(
427 413195701,
428 "测试22",
429 Some(590635620),
430 None,
431 None,
432 None,
433 None,
434 None,
435 None,
436 None,
437 )
438 .await;
439 info!("{:#?}", resp);
440 assert!(resp.is_ok());
441 info!("dmid{}", resp.unwrap().data.unwrap().dmid);
442 }
443
444 #[tokio::test]
445 #[ignore]
446
447 async fn test_danmaku_recall() {
448 let bpi = BpiClient::new();
449
450 let resp = bpi.danmaku_recall(413195701, 1932013422544416768).await;
451 info!("{:#?}", resp);
452 assert!(resp.is_ok());
453 }
454
455 #[tokio::test]
456 #[ignore]
457
458 async fn test_danmaku_buy_adv() {
459 let bpi = BpiClient::new();
460
461 let resp = bpi.danmaku_buy_adv(413195701).await;
462 info!("{:#?}", resp);
463 assert!(resp.is_ok());
464 }
465
466 #[tokio::test]
467 #[ignore]
468
469 async fn test_danmaku_get_adv_state() {
470 let bpi = BpiClient::new();
471
472 let resp = bpi.danmaku_adv_state(413195701).await;
473 info!("{:#?}", resp);
474 assert!(resp.is_ok());
475 }
476
477 #[tokio::test]
478 #[ignore]
479
480 async fn test_danmaku_thumbup() {
481 let bpi = BpiClient::new();
482
483 let resp = bpi.danmaku_thumbup(413195701, 1932011031958944000, 1).await;
484 info!("{:#?}", resp);
485 assert!(resp.is_ok());
486 }
487
488 #[tokio::test]
489 #[ignore]
490
491 async fn test_danmaku_edit_state() {
492 let bpi = BpiClient::new();
493
494 let dmids = vec![1932011031958944000];
495 let resp = bpi.danmaku_edit_state(413195701, &dmids, 1).await;
496 info!("{:#?}", resp);
497 assert!(resp.is_ok());
498 }
499
500 #[tokio::test]
501 #[ignore]
502
503 async fn test_danmaku_edit_pool() {
504 let bpi = BpiClient::new();
505
506 let dmids = vec![1932011031958944000];
507 let resp = bpi.danmaku_edit_pool(413195701, &dmids, 1).await;
508 info!("{:#?}", resp);
509 assert!(resp.is_ok());
510 }
511}