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