bpi_rs/video/
action.rs

1//! B站视频交互接口(Web端)
2//!
3//! 文档: https://github.com/SocialSisterYi/bilibili-API-collect/tree/master/docs/video
4//! 文档:
5//! - 点赞视频:https://api.bilibili.com/x/web-interface/archive/like
6//! - 投币视频:https://api.bilibili.com/x/web-interface/coin/add
7//! - 收藏视频:https://api.bilibili.com/x/v3/fav/resource/deal
8
9use std::collections::HashMap;
10
11use crate::{BilibiliRequest, BpiClient, BpiError, BpiResponse};
12use serde::{Deserialize, Serialize};
13
14/// 点赞视频 - 请求参数
15#[derive(Debug, Clone, Serialize)]
16pub struct LikeRequest {
17    /// 稿件 avid (aid 与 bvid 任选一个)
18    pub aid: Option<u64>,
19    /// 稿件 bvid (aid 与 bvid 任选一个)
20    pub bvid: Option<String>,
21    /// 操作方式 (1: 点赞, 2: 取消赞)
22    pub like: u8,
23}
24
25/// 投币视频 - 请求参数
26#[derive(Debug, Clone, Serialize)]
27pub struct CoinRequest {
28    /// 稿件 avid
29    pub aid: Option<u64>,
30    /// 稿件 bvid
31    pub bvid: Option<String>,
32    /// 投币数量 (上限为 2)
33    pub multiply: u8,
34    /// 是否附加点赞 (0: 不点赞, 1: 点赞),默认为 0
35    pub select_like: Option<u8>,
36}
37
38/// 投币视频 - 响应结构体
39#[derive(Debug, Serialize, Clone, Deserialize)]
40pub struct CoinData {
41    /// 是否点赞成功
42    pub like: bool,
43}
44
45/// 收藏视频 - 响应结构体
46#[derive(Debug, Serialize, Clone, Deserialize)]
47pub struct FavoriteData {
48    /// 是否为未关注用户收藏
49    pub prompt: bool,
50    /// 作用不明确
51    pub ga_data: Option<serde_json::Value>,
52    /// 提示消息
53    pub toast_msg: Option<String>,
54    /// 成功数
55    pub success_num: u32,
56}
57
58pub type FavoriteResponse = BpiResponse<FavoriteData>;
59
60impl BpiClient {
61    /// 点赞/取消点赞
62    ///
63    /// 文档: https://github.com/SocialSisterYi/bilibili-API-collect/blob/master/docs/video/action.md
64    ///
65    /// # 参数
66    /// | 名称   | 类型           | 说明                 |
67    /// | ------ | --------------| -------------------- |
68    /// | `aid`  | Option<u64>   | 稿件 avid,可选      |
69    /// | `bvid` | Option<String>| 稿件 bvid,可选      |
70    /// | `like` | u8            | 操作方式 (1:点赞, 2:取消) |
71    pub async fn video_like(
72        &self,
73        aid: Option<u64>,
74        bvid: Option<String>,
75        like: u8,
76    ) -> Result<BpiResponse<CoinData>, BpiError> {
77        let csrf = self.csrf()?;
78
79        let result = self
80            .post("https://api.bilibili.com/x/web-interface/archive/like")
81            .with_bilibili_headers()
82            .form(&[
83                ("aid", aid.unwrap_or(0).to_string()),
84                ("bvid", bvid.unwrap_or("".to_string())),
85                ("like", like.to_string()),
86                ("csrf", csrf),
87            ])
88            .send_bpi("点赞")
89            .await?;
90
91        Ok(result)
92    }
93
94    /// 投币视频
95    ///
96    /// 文档: https://github.com/SocialSisterYi/bilibili-API-collect/blob/master/docs/video/action.md
97    ///
98    /// # 参数
99    /// | 名称         | 类型           | 说明                 |
100    /// | ------------ | --------------| -------------------- |
101    /// | `aid`        | Option<u64>   | 稿件 avid,可选      |
102    /// | `bvid`       | Option<String>| 稿件 bvid,可选      |
103    /// | `multiply`   | u8            | 投币数量(上限2)    |
104    /// | `select_like`| Option<u8>    | 是否附加点赞,0:否,1:是,默认0 |
105    pub async fn video_coin(
106        &self,
107        aid: Option<u64>,
108        bvid: Option<String>,
109        multiply: u8,
110        select_like: Option<u8>,
111    ) -> Result<BpiResponse<CoinData>, BpiError> {
112        let csrf = self.csrf()?;
113
114        self.post("https://api.bilibili.com/x/web-interface/coin/add")
115            .with_bilibili_headers()
116            .form(&[
117                ("aid", aid.unwrap_or(0).to_string()),
118                ("bvid", bvid.unwrap_or("".to_string())),
119                ("multiply", multiply.to_string()),
120                ("select_like", select_like.unwrap_or(0).to_string()),
121                ("csrf", csrf),
122            ])
123            .send_bpi("投币")
124            .await
125    }
126
127    /// 收藏视频
128    ///
129    /// 文档: https://github.com/SocialSisterYi/bilibili-API-collect/blob/master/docs/video/action.md
130    ///
131    /// # 参数
132    /// | 名称           | 类型                 | 说明                 |
133    /// | -------------- | --------------------| -------------------- |
134    /// | `rid`          | u64                 | 资源ID(视频avid)   |
135    /// | `add_media_ids`| Option<Vec<&str>>   | 要添加的收藏夹ID列表,可选 |
136    /// | `del_media_ids`| Option<Vec<&str>>   | 要删除的收藏夹ID列表,可选 |
137    pub async fn video_favorite(
138        &self,
139        rid: u64,
140        add_media_ids: Option<Vec<&str>>,
141        del_media_ids: Option<Vec<&str>>,
142    ) -> Result<FavoriteResponse, BpiError> {
143        if add_media_ids.is_none() && del_media_ids.is_none() {
144            return Err(BpiError::InvalidParameter {
145                field: "media_ids",
146                message: "请至少指定一个操作",
147            });
148        }
149
150        let csrf = self.csrf()?;
151
152        let mut params = HashMap::new();
153
154        params.extend([
155            ("rid", rid.to_string()),
156            ("type", "2".to_string()),
157            ("csrf", csrf),
158        ]);
159
160        if let Some(ids) = add_media_ids {
161            let s = ids
162                .into_iter()
163                .map(|id| id.to_string())
164                .collect::<Vec<_>>()
165                .join(",");
166            params.insert("add_media_ids", s);
167        }
168        if let Some(ids) = del_media_ids {
169            let s = ids
170                .into_iter()
171                .map(|id| id.to_string())
172                .collect::<Vec<_>>()
173                .join(",");
174            params.insert("del_media_ids", s);
175        }
176
177        self.post("https://api.bilibili.com/x/v3/fav/resource/deal")
178            .with_bilibili_headers()
179            .form(&params)
180            .send_bpi("收藏视频")
181            .await
182    }
183}
184
185#[cfg(test)]
186mod tests {
187    use super::*;
188
189    #[tokio::test]
190    async fn test_like_video() {
191        let bpi = BpiClient::new();
192
193        match bpi.video_like(Some(10001), None, 1).await {
194            Ok(resp) => tracing::info!("点赞响应: {:?}", resp),
195            Err(e) => match e.code() {
196                Some(65006) => {
197                    tracing::info!("已赞过")
198                }
199                _ => {
200                    panic!("请求失败: {}", e)
201                }
202            },
203        }
204    }
205
206    #[tokio::test]
207    async fn test_coin_video() {
208        let bpi = BpiClient::new();
209
210        match bpi.video_coin(Some(10001), None, 1, Some(1)).await {
211            Ok(resp) => tracing::info!("投币响应: {:?}", resp),
212            Err(e) => match e.code() {
213                Some(34005) => {
214                    tracing::info!("超过投币上限")
215                }
216                _ => {
217                    panic!("请求失败: {}", e)
218                }
219            },
220        }
221    }
222
223    #[tokio::test]
224    async fn test_favorite_video() {
225        let bpi = BpiClient::new();
226
227        match bpi
228            .video_favorite(10001, Some(vec!["44717370"]), None)
229            .await
230        {
231            Ok(resp) => tracing::info!("收藏响应: {:?}", resp),
232            Err(e) => panic!("请求失败: {}", e),
233        }
234    }
235}