bpi_rs/user/relation/
group.rs

1//! B站用户分组相关接口
2//!
3//! 文档: https://github.com/SocialSisterYi/bilibili-API-collect/tree/master/docs/user
4use crate::{ BilibiliRequest, BpiClient, BpiError, BpiResponse };
5use serde::{ Deserialize, Serialize };
6
7// --- 响应数据结构体 ---
8
9/// 创建分组响应数据
10#[derive(Debug, Clone, Deserialize, Serialize)]
11pub struct CreateTagResponseData {
12    /// 创建的分组的 ID
13    pub tagid: i64,
14}
15
16// --- API 实现 ---
17
18impl BpiClient {
19    /// 创建分组
20    ///
21    /// 文档: https://github.com/SocialSisterYi/bilibili-API-collect/tree/master/docs/user/relation/group#创建分组
22    ///
23    /// # 参数
24    /// | 名称      | 类型         | 说明           |
25    /// | --------- | ------------| -------------- |
26    /// | `group_name` | &str      | 分组名,最长16字|
27    pub async fn user_group_create_tag(
28        &self,
29        group_name: &str
30    ) -> Result<BpiResponse<CreateTagResponseData>, BpiError> {
31        let csrf = self.csrf()?;
32        let form = reqwest::multipart::Form
33            ::new()
34            .text("tag", group_name.to_string())
35            .text("csrf", csrf.to_string());
36
37        self
38            .post("https://api.bilibili.com/x/relation/tag/create")
39            .multipart(form)
40            .send_bpi("创建分组").await
41    }
42
43    /// 重命名分组
44    ///
45    /// 文档: https://github.com/SocialSisterYi/bilibili-API-collect/tree/master/docs/user/relation/group#重命名分组
46    ///
47    /// # 参数
48    /// | 名称      | 类型         | 说明           |
49    /// | --------- | ------------| -------------- |
50    /// | `tag_id`  | i64         | 分组ID         |
51    /// | `new_name`| &str        | 新名称,最长16字|
52    pub async fn user_group_update_tag(
53        &self,
54        tag_id: i64,
55        new_name: &str
56    ) -> Result<BpiResponse<serde_json::Value>, BpiError> {
57        let csrf = self.csrf()?;
58        let form = reqwest::multipart::Form
59            ::new()
60            .text("tagid", tag_id.to_string())
61            .text("name", new_name.to_string())
62            .text("csrf", csrf.to_string());
63
64        self
65            .post("https://api.bilibili.com/x/relation/tag/update")
66            .multipart(form)
67            .send_bpi("重命名分组").await
68    }
69
70    /// 删除分组
71    ///
72    /// 文档: https://github.com/SocialSisterYi/bilibili-API-collect/tree/master/docs/user/relation/group#删除分组
73    ///
74    /// # 参数
75    /// | 名称      | 类型         | 说明           |
76    /// | --------- | ------------| -------------- |
77    /// | `tag_id`  | i64         | 分组ID         |
78    pub async fn user_group_delete_tag(
79        &self,
80        tag_id: i64
81    ) -> Result<BpiResponse<serde_json::Value>, BpiError> {
82        let csrf = self.csrf()?;
83        let form = reqwest::multipart::Form
84            ::new()
85            .text("tagid", tag_id.to_string())
86            .text("csrf", csrf.to_string());
87
88        self
89            .post("https://api.bilibili.com/x/relation/tag/del")
90            .multipart(form)
91            .send_bpi("删除分组").await
92    }
93
94    /// 修改分组成员(添加)
95    ///
96    /// 文档: https://github.com/SocialSisterYi/bilibili-API-collect/tree/master/docs/user/relation/group#修改分组成员
97    ///
98    /// # 参数
99    /// | 名称      | 类型         | 说明           |
100    /// | --------- | ------------| -------------- |
101    /// | `fids`    | &[u64]      | 目标用户 mid 列表|
102    /// | `tagids`  | &[i64]      | 分组ID列表      |
103    pub async fn user_group_add_users_to_tags(
104        &self,
105        fids: &[u64],
106        tagids: &[i64]
107    ) -> Result<BpiResponse<serde_json::Value>, BpiError> {
108        let csrf = self.csrf()?;
109        let fids_str = fids
110            .iter()
111            .map(|id| id.to_string())
112            .collect::<Vec<_>>()
113            .join(",");
114        let tagids_str = tagids
115            .iter()
116            .map(|id| id.to_string())
117            .collect::<Vec<_>>()
118            .join(",");
119
120        let form = reqwest::multipart::Form
121            ::new()
122            .text("fids", fids_str)
123            .text("tagids", tagids_str)
124            .text("csrf", csrf.to_string());
125
126        self
127            .post("https://api.bilibili.com/x/relation/tags/addUsers")
128            .multipart(form)
129            .send_bpi("修改分组成员").await
130    }
131
132    // 修改分组成员(删除)
133    ///
134    /// 文档: https://github.com/SocialSisterYi/bilibili-API-collect/tree/master/docs/user/relation/group#修改分组成员
135    ///
136    /// # 参数
137    /// | 名称      | 类型         | 说明           |
138    /// | --------- | ------------| -------------- |
139    /// | `fids`    | &[u64]      | 目标用户 mid 列表|
140    pub async fn user_group_remove_users_(
141        &self,
142        fids: &[u64]
143    ) -> Result<BpiResponse<serde_json::Value>, BpiError> {
144        let csrf = self.csrf()?;
145        let fids_str = fids
146            .iter()
147            .map(|id| id.to_string())
148            .collect::<Vec<_>>()
149            .join(",");
150
151        let form = reqwest::multipart::Form
152            ::new()
153            .text("fids", fids_str)
154            .text("tagids", "0".to_string())
155            .text("csrf", csrf.to_string());
156
157        self
158            .post("https://api.bilibili.com/x/relation/tags/addUsers")
159            .multipart(form)
160            .send_bpi("修改分组成员").await
161    }
162
163    /// 复制关注到分组
164    ///
165    /// 文档: https://github.com/SocialSisterYi/bilibili-API-collect/tree/master/docs/user/relation/group#复制关注到分组
166    ///
167    /// # 参数
168    /// | 名称      | 类型         | 说明           |
169    /// | --------- | ------------| -------------- |
170    /// | `fids`    | &[u64]      | 用户 mid 列表   |
171    /// | `tagids`  | &[i64]      | 目标分组ID列表  |
172    pub async fn user_group_copy_users_to_tags(
173        &self,
174        fids: &[u64],
175        tagids: &[i64]
176    ) -> Result<BpiResponse<serde_json::Value>, BpiError> {
177        let csrf = self.csrf()?;
178        let fids_str = fids
179            .iter()
180            .map(|id| id.to_string())
181            .collect::<Vec<_>>()
182            .join(",");
183        let tagids_str = tagids
184            .iter()
185            .map(|id| id.to_string())
186            .collect::<Vec<_>>()
187            .join(",");
188
189        let form = reqwest::multipart::Form
190            ::new()
191            .text("fids", fids_str)
192            .text("tagids", tagids_str)
193            .text("csrf", csrf.to_string());
194
195        self
196            .post("https://api.bilibili.com/x/relation/tags/copyUsers")
197            .multipart(form)
198            .send_bpi("复制关注到分组").await
199    }
200
201    /// 移动关注到分组
202    ///
203    /// 文档: https://github.com/SocialSisterYi/bilibili-API-collect/tree/master/docs/user/relation/group#移动关注到分组
204    ///
205    /// # 参数
206    /// | 名称            | 类型         | 说明           |
207    /// | --------------- | ------------| -------------- |
208    /// | `fids`          | &[u64]      | 用户 mid 列表   |
209    /// | `before_tag_ids`| &[i64]      | 原分组ID列表    |
210    /// | `after_tag_ids` | &[i64]      | 新分组ID列表    |
211    pub async fn user_group_move_users_to_tags(
212        &self,
213        fids: &[u64],
214        before_tag_ids: &[i64],
215        after_tag_ids: &[i64]
216    ) -> Result<BpiResponse<serde_json::Value>, BpiError> {
217        let csrf = self.csrf()?;
218        let fids_str = fids
219            .iter()
220            .map(|id| id.to_string())
221            .collect::<Vec<_>>()
222            .join(",");
223        let before_tag_ids_str = before_tag_ids
224            .iter()
225            .map(|id| id.to_string())
226            .collect::<Vec<_>>()
227            .join(",");
228        let after_tag_ids_str = after_tag_ids
229            .iter()
230            .map(|id| id.to_string())
231            .collect::<Vec<_>>()
232            .join(",");
233
234        let form = reqwest::multipart::Form
235            ::new()
236            .text("fids", fids_str)
237            .text("beforeTagids", before_tag_ids_str)
238            .text("afterTagids", after_tag_ids_str)
239            .text("csrf", csrf.to_string());
240
241        self
242            .post("https://api.bilibili.com/x/relation/tags/moveUsers")
243            .multipart(form)
244            .send_bpi("移动关注到分组").await
245    }
246}
247
248// --- 测试模块 ---
249
250#[cfg(test)]
251mod tests {
252    use super::*;
253    use tracing::info;
254
255    const TEST_FID: u64 = 107997089;
256
257    const DEFAULT_GROUP_FID: u64 = 3493257409464519;
258
259    #[tokio::test]
260    async fn test_tag_operations() -> Result<(), BpiError> {
261        let bpi = BpiClient::new();
262        let test_tag_name = "测试分组1";
263        let new_tag_name = "新测试分组2";
264
265        // 1. 创建分组
266        info!("正在创建分组...");
267        let create_resp = bpi.user_group_create_tag(&test_tag_name).await?;
268        let tag_id = create_resp.into_data()?.tagid;
269        info!("创建分组成功,ID: {}", tag_id);
270
271        // 2. 重命名分组
272        info!("正在重命名分组...");
273        let update_resp = bpi.user_group_update_tag(tag_id, &new_tag_name).await?;
274        info!("重命名分组成功");
275        assert_eq!(update_resp.code, 0);
276
277        // 3. 修改分组成员
278        info!("正在将用户添加到默认分组...");
279        let add_resp = bpi.user_group_add_users_to_tags(&[TEST_FID], &[-10]).await?;
280        info!("添加用户到默认分组成功");
281        assert_eq!(add_resp.code, 0);
282
283        // 5. 移动关注到分组
284        // 假设存在一个默认分组(tagid=-10)
285        info!("正在将用户从默认分组移动到新分组...");
286        let move_resp = bpi.user_group_move_users_to_tags(
287            &[DEFAULT_GROUP_FID],
288            &[-10],
289            &[tag_id]
290        ).await?;
291        info!("移动用户到分组成功");
292        assert_eq!(move_resp.code, 0);
293
294        // 4. 复制关注到分组
295        info!("正在将用户复制到分组...");
296        let copy_resp = bpi.user_group_copy_users_to_tags(&[TEST_FID], &[tag_id]).await?;
297        info!("复制用户到分组成功");
298        assert_eq!(copy_resp.code, 0);
299
300        // 6. 删除分组
301        info!("正在删除分组...");
302        let delete_resp = bpi.user_group_delete_tag(tag_id).await?;
303        info!("删除分组成功");
304        assert_eq!(delete_resp.code, 0);
305
306        Ok(())
307    }
308}