Skip to main content

bpi_rs/video/
action.rs

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