1use std::collections::HashMap;
10
11use crate::{BilibiliRequest, BpiClient, BpiError, BpiResponse};
12use serde::{Deserialize, Serialize};
13
14#[derive(Debug, Clone, Serialize)]
16pub struct LikeRequest {
17 pub aid: Option<u64>,
19 pub bvid: Option<String>,
21 pub like: u8,
23}
24
25#[derive(Debug, Clone, Serialize)]
27pub struct CoinRequest {
28 pub aid: Option<u64>,
30 pub bvid: Option<String>,
32 pub multiply: u8,
34 pub select_like: Option<u8>,
36}
37
38#[derive(Debug, Serialize, Clone, Deserialize)]
40pub struct CoinData {
41 pub like: bool,
43}
44
45#[derive(Debug, Serialize, Clone, Deserialize)]
47pub struct FavoriteData {
48 pub prompt: bool,
50 pub ga_data: Option<serde_json::Value>,
52 pub toast_msg: Option<String>,
54 pub success_num: u32,
56}
57
58pub type FavoriteResponse = BpiResponse<FavoriteData>;
59
60impl BpiClient {
61 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 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 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(¶ms)
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}