1use std::collections::HashMap;
6
7use crate::{ BilibiliRequest, BpiClient, BpiError, BpiResponse };
8use serde::{ Deserialize, Serialize };
9
10#[derive(Debug, Clone, Serialize)]
12pub struct LikeRequest {
13 pub aid: Option<u64>,
15 pub bvid: Option<String>,
17 pub like: u8,
19}
20
21#[derive(Debug, Clone, Serialize)]
23pub struct CoinRequest {
24 pub aid: Option<u64>,
26 pub bvid: Option<String>,
28 pub multiply: u8,
30 pub select_like: Option<u8>,
32}
33
34#[derive(Debug, Serialize, Clone, Deserialize)]
36pub struct CoinData {
37 pub like: bool,
39}
40
41#[derive(Debug, Serialize, Clone, Deserialize)]
43pub struct FavoriteData {
44 pub prompt: bool,
46 pub ga_data: Option<serde_json::Value>,
48 pub toast_msg: Option<String>,
50 pub success_num: u32,
52}
53
54pub type FavoriteResponse = BpiResponse<FavoriteData>;
55
56impl BpiClient {
57 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 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 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(¶ms)
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}