1use super::info::FavFolderInfo;
2use crate::{BilibiliRequest, BpiClient, BpiError, BpiResponse};
3
4impl BpiClient {
5 pub async fn fav_folder_add(
18 &self,
19 title: &str,
20 intro: Option<&str>,
21 privacy: Option<u8>,
22 cover: Option<&str>,
23 ) -> Result<BpiResponse<FavFolderInfo>, BpiError> {
24 let csrf = self.csrf()?;
25
26 let mut form = vec![("title", title.to_string()), ("csrf", csrf)];
27 if let Some(intro) = intro {
28 form.push(("intro", intro.to_string()));
29 }
30 if let Some(privacy) = privacy {
31 form.push(("privacy", privacy.to_string()));
32 }
33 if let Some(cover) = cover {
34 form.push(("cover", cover.to_string()));
35 }
36
37 self.post("https://api.bilibili.com/x/v3/fav/folder/add")
38 .form(&form)
39 .send_bpi("新建收藏夹")
40 .await
41 }
42
43 pub async fn fav_folder_edit(
57 &self,
58 media_id: u64,
59 title: &str,
60 intro: Option<&str>,
61 privacy: Option<u8>,
62 cover: Option<&str>,
63 ) -> Result<BpiResponse<FavFolderInfo>, BpiError> {
64 let csrf = self.csrf()?;
65
66 let mut form = vec![
67 ("media_id", media_id.to_string()),
68 ("title", title.to_string()),
69 ("csrf", csrf),
70 ];
71 if let Some(intro) = intro {
72 form.push(("intro", intro.to_string()));
73 }
74 if let Some(privacy) = privacy {
75 form.push(("privacy", privacy.to_string()));
76 }
77 if let Some(cover) = cover {
78 form.push(("cover", cover.to_string()));
79 }
80
81 self.post("https://api.bilibili.com/x/v3/fav/folder/edit")
82 .form(&form)
83 .send_bpi("修改收藏夹")
84 .await
85 }
86
87 pub async fn fav_folder_del(&self, media_ids: &[u64]) -> Result<BpiResponse<i32>, BpiError> {
97 let csrf = self.csrf()?;
98 let ids_str = media_ids
99 .iter()
100 .map(|&id| id.to_string())
101 .collect::<Vec<_>>()
102 .join(",");
103
104 let form = [("media_ids", ids_str), ("csrf", csrf)];
105
106 self.post("https://api.bilibili.com/x/v3/fav/folder/del")
107 .form(&form)
108 .send_bpi("删除收藏夹")
109 .await
110 }
111
112 pub async fn fav_resource_copy(
126 &self,
127 src_media_id: u64,
128 tar_media_id: u64,
129 mid: u64,
130 resources: &str,
131 ) -> Result<BpiResponse<i32>, BpiError> {
132 let csrf = self.csrf()?;
133
134 let form = [
135 ("src_media_id", src_media_id.to_string()),
136 ("tar_media_id", tar_media_id.to_string()),
137 ("mid", mid.to_string()),
138 ("resources", resources.to_string()),
139 ("platform", "web".to_string()),
140 ("csrf", csrf),
141 ];
142
143 self.post("https://api.bilibili.com/x/v3/fav/resource/copy")
144 .form(&form)
145 .send_bpi("批量复制内容")
146 .await
147 }
148
149 pub async fn fav_resource_move(
163 &self,
164 src_media_id: u64,
165 tar_media_id: u64,
166 mid: u64,
167 resources: &str,
168 ) -> Result<BpiResponse<i32>, BpiError> {
169 let csrf = self.csrf()?;
170
171 let form = [
172 ("src_media_id", src_media_id.to_string()),
173 ("tar_media_id", tar_media_id.to_string()),
174 ("mid", mid.to_string()),
175 ("resources", resources.to_string()),
176 ("platform", "web".to_string()),
177 ("csrf", csrf),
178 ];
179
180 self.post("https://api.bilibili.com/x/v3/fav/resource/move")
181 .form(&form)
182 .send_bpi("批量移动内容")
183 .await
184 }
185
186 pub async fn fav_resource_batch_del(
198 &self,
199 media_id: u64,
200 resources: &str,
201 ) -> Result<BpiResponse<i32>, BpiError> {
202 let csrf = self.csrf()?;
203
204 let form = [
205 ("media_id", media_id.to_string()),
206 ("resources", resources.to_string()),
207 ("platform", "web".to_string()),
208 ("csrf", csrf),
209 ];
210
211 self.post("https://api.bilibili.com/x/v3/fav/resource/batch-del")
212 .form(&form)
213 .send_bpi("批量删除内容")
214 .await
215 }
216
217 pub async fn fav_resource_clean(&self, media_id: u64) -> Result<BpiResponse<i32>, BpiError> {
227 let csrf = self.csrf()?;
228
229 let form = [("media_id", media_id.to_string()), ("csrf", csrf)];
230
231 self.post("https://api.bilibili.com/x/v3/fav/resource/clean")
232 .form(&form)
233 .send_bpi("清空所有失效内容")
234 .await
235 }
236}
237
238#[cfg(test)]
239mod tests {
240 use super::*;
241 use tracing::info;
242
243 #[tokio::test]
244 async fn test_fav_folder_add_and_del() {
245 let bpi = BpiClient::new();
246 let title = "Test_Fav_Folder_Add2";
247 let intro = "This is a test folder2.";
248
249 let add_resp = bpi.fav_folder_add(title, Some(intro), Some(1), None).await;
251 info!("Add folder result: {:?}", add_resp);
252 assert!(add_resp.is_ok());
253
254 let new_folder_id = add_resp.unwrap().data.unwrap().id;
255 info!("New folder ID: {}", new_folder_id);
256
257 tokio::time::sleep(tokio::time::Duration::from_secs(3)).await;
258
259 let del_resp = bpi.fav_folder_del(&[new_folder_id]).await;
261 info!("Delete folder result: {:?}", del_resp);
262 assert!(del_resp.is_ok());
263 }
264
265 #[tokio::test]
266 async fn test_fav_folder_edit() {
267 let bpi = BpiClient::new();
268 let media_id = 3717139570;
270 let title = "Edited Title";
271 let intro = "Edited Intro";
272
273 let resp = bpi
274 .fav_folder_edit(media_id, title, Some(intro), Some(0), None)
275 .await;
276 info!("Edit folder result: {:?}", resp);
277 assert!(resp.is_ok());
278 }
279
280 #[tokio::test]
281 async fn test_fav_resource_operations() {
282 let bpi = BpiClient::new();
283 let src_media_id = 3717139570;
285 let tar_media_id = 3641682570;
286 let mid = 4279370;
287 let resources_copy = "115087859779103:2";
288 let resources_del = "442608504:2";
289 let resources_move = "739661210:2";
290
291 let copy_resp = bpi
293 .fav_resource_copy(src_media_id, tar_media_id, mid, resources_copy)
294 .await;
295 info!("Copy resources result: {:?}", copy_resp);
296 assert!(copy_resp.is_ok());
297
298 let move_resp = bpi
300 .fav_resource_move(src_media_id, tar_media_id, mid, resources_move)
301 .await;
302 info!("Move resources result: {:?}", move_resp);
303 assert!(move_resp.is_ok());
304
305 let del_resp = bpi
307 .fav_resource_batch_del(tar_media_id, resources_del)
308 .await;
309 info!("Batch delete resources result: {:?}", del_resp);
310 assert!(del_resp.is_ok());
311 }
312
313 #[tokio::test]
314 async fn test_fav_resource_clean() {
315 let bpi = BpiClient::new();
316 let media_id = 3717139570;
318 let resp = bpi.fav_resource_clean(media_id).await;
319 info!("Clean invalid resources result: {:?}", resp);
320 assert!(resp.is_ok());
321 }
322}