koishi-plugin-bind-bot 2.2.4 → 2.2.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -21,13 +21,9 @@ export declare class BuidHandler extends BaseHandler {
|
|
|
21
21
|
*/
|
|
22
22
|
private handleFindUser;
|
|
23
23
|
/**
|
|
24
|
-
* 处理
|
|
24
|
+
* 处理 buid.unbind 命令
|
|
25
25
|
*/
|
|
26
|
-
private
|
|
27
|
-
/**
|
|
28
|
-
* 处理 mcid.unbindbuid 命令
|
|
29
|
-
*/
|
|
30
|
-
private handleUnbindBuid;
|
|
26
|
+
private handleUnbind;
|
|
31
27
|
/**
|
|
32
28
|
* 解析UID输入(支持多种格式)
|
|
33
29
|
*/
|
|
@@ -37,17 +37,11 @@ class BuidHandler extends base_handler_1.BaseHandler {
|
|
|
37
37
|
.action(async ({ session }, uid) => {
|
|
38
38
|
return this.handleFindUser(session, uid);
|
|
39
39
|
});
|
|
40
|
-
//
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
.
|
|
45
|
-
.action(async ({ session }, buid) => {
|
|
46
|
-
return this.handleBindBuid(session, buid);
|
|
47
|
-
});
|
|
48
|
-
// 解绑B站账号(mcid.unbindbuid)
|
|
49
|
-
mcidCmd.subcommand('.unbindbuid', '解绑B站账号').action(async ({ session }) => {
|
|
50
|
-
return this.handleUnbindBuid(session);
|
|
40
|
+
// 解绑BUID
|
|
41
|
+
buidCmd
|
|
42
|
+
.subcommand('.unbind', '解绑B站账号')
|
|
43
|
+
.action(async ({ session }) => {
|
|
44
|
+
return this.handleUnbind(session);
|
|
51
45
|
});
|
|
52
46
|
}
|
|
53
47
|
/**
|
|
@@ -71,12 +65,16 @@ class BuidHandler extends base_handler_1.BaseHandler {
|
|
|
71
65
|
: `您尚未绑定B站账号,请使用 ${this.deps.formatCommand('buid bind <UID>')} 进行绑定`)
|
|
72
66
|
]);
|
|
73
67
|
}
|
|
74
|
-
//
|
|
68
|
+
// 每次查询都必须从API获取最新数据
|
|
75
69
|
const buidUser = await this.validateBUID(bind.buidUid);
|
|
76
|
-
if (buidUser) {
|
|
77
|
-
|
|
78
|
-
|
|
70
|
+
if (!buidUser) {
|
|
71
|
+
return this.deps.sendMessage(session, [
|
|
72
|
+
koishi_1.h.text(`无法从API获取B站UID ${bind.buidUid} 的最新数据,该用户可能不存在或API服务暂时不可用`)
|
|
73
|
+
]);
|
|
79
74
|
}
|
|
75
|
+
// 更新本地数据库中的信息
|
|
76
|
+
await this.updateBuidInfoOnly(bind.qqId, buidUser);
|
|
77
|
+
bind = await this.repos.mcidbind.findByQQId(bind.qqId);
|
|
80
78
|
const userInfo = `${target ? `用户 ${bind.qqId} 的` : '您的'}B站账号信息:\nB站UID: ${bind.buidUid}\n用户名: ${bind.buidUsername}`;
|
|
81
79
|
let detailInfo = '';
|
|
82
80
|
if (bind.guardLevel > 0) {
|
|
@@ -192,68 +190,9 @@ class BuidHandler extends base_handler_1.BaseHandler {
|
|
|
192
190
|
}
|
|
193
191
|
}
|
|
194
192
|
/**
|
|
195
|
-
* 处理
|
|
196
|
-
*/
|
|
197
|
-
async handleBindBuid(session, buid) {
|
|
198
|
-
try {
|
|
199
|
-
const normalizedUserId = this.deps.normalizeQQId(session.userId);
|
|
200
|
-
this.logger.info('绑定', `QQ(${normalizedUserId})尝试绑定B站UID(${buid})`);
|
|
201
|
-
// 验证格式
|
|
202
|
-
if (!buid || !/^\d+$/.test(buid)) {
|
|
203
|
-
this.logger.warn('绑定', `QQ(${normalizedUserId})尝试绑定无效的B站UID格式: ${buid}`);
|
|
204
|
-
return this.deps.sendMessage(session, [koishi_1.h.text('无效的B站UID格式,请输入正确的B站UID')]);
|
|
205
|
-
}
|
|
206
|
-
// 检查是否已被他人绑定
|
|
207
|
-
const existingBind = await this.repos.mcidbind.findByBuidUid(buid);
|
|
208
|
-
if (existingBind) {
|
|
209
|
-
const existingQQId = existingBind.qqId;
|
|
210
|
-
this.logger.warn('绑定', `QQ(${normalizedUserId})尝试绑定已被QQ(${existingQQId})绑定的B站UID(${buid})`);
|
|
211
|
-
return this.deps.sendMessage(session, [koishi_1.h.text('该B站UID已被其他用户绑定')]);
|
|
212
|
-
}
|
|
213
|
-
// 验证B站UID
|
|
214
|
-
const buidUser = await this.validateBUID(buid);
|
|
215
|
-
if (!buidUser) {
|
|
216
|
-
this.logger.warn('绑定', `QQ(${normalizedUserId})尝试绑定不存在的B站UID(${buid})`);
|
|
217
|
-
return this.deps.sendMessage(session, [koishi_1.h.text('无法验证B站UID,请确认输入正确')]);
|
|
218
|
-
}
|
|
219
|
-
// 创建或更新绑定
|
|
220
|
-
const success = await this.createOrUpdateBuidBind(normalizedUserId, buidUser);
|
|
221
|
-
if (success) {
|
|
222
|
-
this.logger.info('绑定', `QQ(${normalizedUserId})成功绑定B站UID(${buid})`);
|
|
223
|
-
return this.deps.sendMessage(session, [
|
|
224
|
-
koishi_1.h.text('成功绑定B站账号!\n'),
|
|
225
|
-
koishi_1.h.text(`B站UID: ${buidUser.uid}\n`),
|
|
226
|
-
koishi_1.h.text(`用户名: ${buidUser.username}\n`),
|
|
227
|
-
buidUser.guard_level > 0
|
|
228
|
-
? koishi_1.h.text(`舰长等级: ${buidUser.guard_level_text} (${buidUser.guard_level})\n`)
|
|
229
|
-
: null,
|
|
230
|
-
buidUser.medal
|
|
231
|
-
? koishi_1.h.text(`粉丝牌: ${buidUser.medal.name} Lv.${buidUser.medal.level}\n`)
|
|
232
|
-
: null,
|
|
233
|
-
buidUser.wealthMedalLevel > 0
|
|
234
|
-
? koishi_1.h.text(`荣耀等级: ${buidUser.wealthMedalLevel}\n`)
|
|
235
|
-
: null,
|
|
236
|
-
...(this.config?.showAvatar
|
|
237
|
-
? [koishi_1.h.image(`https://workers.vrp.moe/bilibili/avatar/${buidUser.uid}?size=160`)]
|
|
238
|
-
: [])
|
|
239
|
-
].filter(Boolean));
|
|
240
|
-
}
|
|
241
|
-
else {
|
|
242
|
-
this.logger.error('绑定', normalizedUserId, `QQ(${normalizedUserId})绑定B站UID(${buid})失败`);
|
|
243
|
-
return this.deps.sendMessage(session, [koishi_1.h.text('绑定失败,请稍后重试')]);
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
catch (error) {
|
|
247
|
-
this.logger.error('绑定', session.userId, error);
|
|
248
|
-
return this.deps.sendMessage(session, [
|
|
249
|
-
koishi_1.h.text(`绑定失败:${this.getFriendlyErrorMessage(error)}`)
|
|
250
|
-
]);
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
/**
|
|
254
|
-
* 处理 mcid.unbindbuid 命令
|
|
193
|
+
* 处理 buid.unbind 命令
|
|
255
194
|
*/
|
|
256
|
-
async
|
|
195
|
+
async handleUnbind(session) {
|
|
257
196
|
try {
|
|
258
197
|
const normalizedUserId = this.deps.normalizeQQId(session.userId);
|
|
259
198
|
this.logger.info('解绑', `QQ(${normalizedUserId})尝试解绑B站账号`);
|
|
@@ -175,21 +175,37 @@ class GroupRequestReviewHandler extends base_handler_1.BaseHandler {
|
|
|
175
175
|
this.logger.debug('入群审批', `[DEBUG] parseUID结果: ${parsedUid ? parsedUid : 'null'}`);
|
|
176
176
|
if (parsedUid) {
|
|
177
177
|
buidUid = parsedUid;
|
|
178
|
-
//
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
178
|
+
// 尝试使用强制绑定模式获取完整信息(避免频率限制)
|
|
179
|
+
let zminfoData = null;
|
|
180
|
+
if (this.config.forceBindSessdata) {
|
|
181
|
+
try {
|
|
182
|
+
this.logger.debug('入群审批', '使用强制绑定模式获取用户信息...');
|
|
183
|
+
const enhancedUser = await this.deps.forceBinder.forceBindUser(parsedUid);
|
|
184
|
+
const standardUser = this.deps.forceBinder.convertToZminfoUser(enhancedUser);
|
|
185
|
+
buidUsername = standardUser.username;
|
|
186
|
+
zminfoData = standardUser;
|
|
187
|
+
this.logger.debug('入群审批', `✅ 强制绑定模式获取成功: ${buidUsername}`);
|
|
188
|
+
}
|
|
189
|
+
catch (error) {
|
|
190
|
+
this.logger.warn('入群审批', `强制绑定获取用户信息失败: ${error.message},降级到ZMINFO`);
|
|
191
|
+
// 降级:使用ZMINFO
|
|
192
|
+
zminfoData = await this.deps.apiService.validateBUID(parsedUid).catch(() => null);
|
|
193
|
+
if (zminfoData) {
|
|
194
|
+
buidUsername = zminfoData.username;
|
|
195
|
+
this.logger.debug('入群审批', `⚠️ 降级到ZMINFO用户名: ${buidUsername}`);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
else {
|
|
200
|
+
// 未配置Cookie,直接使用ZMINFO
|
|
201
|
+
this.logger.debug('入群审批', '未配置强制绑定Cookie,使用ZMINFO获取用户信息...');
|
|
202
|
+
zminfoData = await this.deps.apiService.validateBUID(parsedUid).catch(() => null);
|
|
203
|
+
if (zminfoData) {
|
|
204
|
+
buidUsername = zminfoData.username;
|
|
205
|
+
this.logger.debug('入群审批', `✅ ZMINFO用户名: ${buidUsername}`);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
// 粉丝牌信息:从ZMINFO或强制绑定结果获取
|
|
193
209
|
if (zminfoData) {
|
|
194
210
|
const medalLevel = zminfoData.medal?.level || 0;
|
|
195
211
|
const medalName = zminfoData.medal?.name || '';
|
|
@@ -204,7 +220,7 @@ class GroupRequestReviewHandler extends base_handler_1.BaseHandler {
|
|
|
204
220
|
}
|
|
205
221
|
}
|
|
206
222
|
else {
|
|
207
|
-
this.logger.warn('入群审批', '
|
|
223
|
+
this.logger.warn('入群审批', 'B站用户信息查询失败,无法获取粉丝牌信息');
|
|
208
224
|
}
|
|
209
225
|
// 绑定状态:查询数据库
|
|
210
226
|
if (buidUsername) {
|
|
@@ -222,7 +238,7 @@ class GroupRequestReviewHandler extends base_handler_1.BaseHandler {
|
|
|
222
238
|
}
|
|
223
239
|
}
|
|
224
240
|
else {
|
|
225
|
-
bindStatus = '❌ UID
|
|
241
|
+
bindStatus = '❌ UID 查询失败';
|
|
226
242
|
}
|
|
227
243
|
}
|
|
228
244
|
return { qq, nickname, avatar, answer, buidUid, buidUsername, medalInfo, bindStatus };
|
|
@@ -319,17 +335,7 @@ class GroupRequestReviewHandler extends base_handler_1.BaseHandler {
|
|
|
319
335
|
*/
|
|
320
336
|
async approveAndAutoBind(pendingReq, operatorId, session) {
|
|
321
337
|
try {
|
|
322
|
-
// 1.
|
|
323
|
-
await session.bot.handleGuildMemberRequest(pendingReq.requestFlag, true, '欢迎加入!');
|
|
324
|
-
this.logger.info('入群审批', `已批准入群 - QQ: ${pendingReq.applicantQQ}`, true);
|
|
325
|
-
// 2. 等待用户进群
|
|
326
|
-
const joined = await this.waitForUserJoin(pendingReq.applicantQQ, pendingReq.targetGroupId, 10000);
|
|
327
|
-
if (!joined) {
|
|
328
|
-
await this.notifyAdmin(operatorId, session, `⚠️ 已批准 ${pendingReq.applicantQQ} 入群,但用户未在10秒内进群`);
|
|
329
|
-
pendingReq.status = 'approved';
|
|
330
|
-
return;
|
|
331
|
-
}
|
|
332
|
-
// 3. 解析UID
|
|
338
|
+
// 1. 解析UID(在批准入群前先检查)
|
|
333
339
|
const uid = this.parseUID(pendingReq.answer);
|
|
334
340
|
if (!uid) {
|
|
335
341
|
await this.notifyAdmin(operatorId, session, `⚠️ 无法解析UID"${pendingReq.answer}",请手动处理\n申请人: ${pendingReq.applicantQQ}`);
|
|
@@ -337,10 +343,19 @@ class GroupRequestReviewHandler extends base_handler_1.BaseHandler {
|
|
|
337
343
|
return;
|
|
338
344
|
}
|
|
339
345
|
this.logger.info('入群审批', `开始自动绑定 - QQ: ${pendingReq.applicantQQ}, UID: ${uid}`);
|
|
340
|
-
//
|
|
341
|
-
// 注意:这里需要访问其他 handler,可能需要调整架构
|
|
342
|
-
// 暂时先记录日志,稍后实现具体绑定逻辑
|
|
346
|
+
// 2. 先执行绑定(在批准入群前)
|
|
343
347
|
await this.performAutoBind(pendingReq.applicantQQ, uid, session.bot);
|
|
348
|
+
this.logger.info('入群审批', `预绑定完成 - QQ: ${pendingReq.applicantQQ}, UID: ${uid}`);
|
|
349
|
+
// 3. 批准入群
|
|
350
|
+
await session.bot.handleGuildMemberRequest(pendingReq.requestFlag, true, '欢迎加入!');
|
|
351
|
+
this.logger.info('入群审批', `已批准入群 - QQ: ${pendingReq.applicantQQ}`, true);
|
|
352
|
+
// 4. 等待用户进群
|
|
353
|
+
const joined = await this.waitForUserJoin(pendingReq.applicantQQ, pendingReq.targetGroupId, 10000);
|
|
354
|
+
if (!joined) {
|
|
355
|
+
await this.notifyAdmin(operatorId, session, `⚠️ 已完成绑定并批准 ${pendingReq.applicantQQ} 入群,但用户未在10秒内进群`);
|
|
356
|
+
pendingReq.status = 'approved';
|
|
357
|
+
return;
|
|
358
|
+
}
|
|
344
359
|
// 5. 通知管理员
|
|
345
360
|
await this.notifyAdmin(operatorId, session, `✅ 已批准 ${pendingReq.applicantQQ} 入群并完成自动绑定\nUID: ${uid}`);
|
|
346
361
|
pendingReq.status = 'approved';
|
|
@@ -582,34 +597,16 @@ class GroupRequestReviewHandler extends base_handler_1.BaseHandler {
|
|
|
582
597
|
*/
|
|
583
598
|
async performAutoBind(qq, uid, bot) {
|
|
584
599
|
try {
|
|
585
|
-
// 1.
|
|
600
|
+
// 1. 使用强制绑定模式获取最新用户信息(避免频率限制)
|
|
586
601
|
this.logger.debug('入群审批', `开始获取 B站 UID ${uid} 的信息`);
|
|
587
|
-
//
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
const officialInfo = await this.deps.apiService.getBilibiliOfficialUserInfo(uid);
|
|
592
|
-
if (officialInfo && officialInfo.name) {
|
|
593
|
-
officialUsername = officialInfo.name;
|
|
594
|
-
this.logger.info('入群审批', `[官方API] ✅ 获取到用户名: "${officialUsername}"`, true);
|
|
595
|
-
}
|
|
596
|
-
else {
|
|
597
|
-
this.logger.warn('入群审批', '[官方API] ❌ 查询失败');
|
|
598
|
-
}
|
|
599
|
-
}
|
|
600
|
-
catch (officialError) {
|
|
601
|
-
this.logger.warn('入群审批', `[官方API] ❌ 查询出错: ${officialError.message}`);
|
|
602
|
-
}
|
|
603
|
-
// 获取ZMINFO API的完整用户信息(包含粉丝牌、大航海等数据)
|
|
604
|
-
this.logger.debug('入群审批', '正在查询ZMINFO API...');
|
|
605
|
-
const zminfoUser = await this.deps.apiService.validateBUID(uid);
|
|
602
|
+
// 使用强制绑定模式获取完整信息(与 bind -f 保持一致)
|
|
603
|
+
this.logger.debug('入群审批', '正在使用强制绑定模式获取B站用户信息...');
|
|
604
|
+
const enhancedUser = await this.deps.forceBinder.forceBindUser(uid);
|
|
605
|
+
const zminfoUser = this.deps.forceBinder.convertToZminfoUser(enhancedUser);
|
|
606
606
|
if (!zminfoUser) {
|
|
607
607
|
throw new Error(`无法验证B站UID: ${uid}`);
|
|
608
608
|
}
|
|
609
|
-
this.logger.
|
|
610
|
-
// 使用官方API的用户名(如果可用),否则使用ZMINFO的
|
|
611
|
-
const finalUsername = officialUsername || zminfoUser.username;
|
|
612
|
-
this.logger.info('入群审批', `🎯 最终采用用户名: "${finalUsername}"`, true);
|
|
609
|
+
this.logger.info('入群审批', `✅ 获取到用户名: "${zminfoUser.username}"`, true);
|
|
613
610
|
// 2. 检查是否已被其他人绑定
|
|
614
611
|
const existingBind = await this.repos.mcidbind.findByBuidUid(uid);
|
|
615
612
|
if (existingBind && existingBind.qqId !== qq) {
|
|
@@ -624,7 +621,7 @@ class GroupRequestReviewHandler extends base_handler_1.BaseHandler {
|
|
|
624
621
|
mcUsername: null,
|
|
625
622
|
mcUuid: null,
|
|
626
623
|
buidUid: zminfoUser.uid,
|
|
627
|
-
buidUsername:
|
|
624
|
+
buidUsername: zminfoUser.username,
|
|
628
625
|
guardLevel: zminfoUser.guard_level || 0,
|
|
629
626
|
guardLevelText: zminfoUser.guard_level_text || '',
|
|
630
627
|
maxGuardLevel: zminfoUser.guard_level || 0,
|
|
@@ -643,7 +640,7 @@ class GroupRequestReviewHandler extends base_handler_1.BaseHandler {
|
|
|
643
640
|
// 更新现有绑定
|
|
644
641
|
await this.repos.mcidbind.update(qq, {
|
|
645
642
|
buidUid: zminfoUser.uid,
|
|
646
|
-
buidUsername:
|
|
643
|
+
buidUsername: zminfoUser.username,
|
|
647
644
|
guardLevel: zminfoUser.guard_level || 0,
|
|
648
645
|
guardLevelText: zminfoUser.guard_level_text || '',
|
|
649
646
|
maxGuardLevel: Math.max(bind.maxGuardLevel || 0, zminfoUser.guard_level || 0),
|
|
@@ -663,7 +660,7 @@ class GroupRequestReviewHandler extends base_handler_1.BaseHandler {
|
|
|
663
660
|
try {
|
|
664
661
|
const groupId = this.reviewConfig.targetGroupId;
|
|
665
662
|
const mcInfo = bind_status_1.BindStatus.getDisplayMcUsername(bind, '未绑定');
|
|
666
|
-
const nickname = `${
|
|
663
|
+
const nickname = `${zminfoUser.username}(ID:${mcInfo})`;
|
|
667
664
|
await bot.internal.setGroupCard(groupId, qq, nickname);
|
|
668
665
|
this.logger.info('入群审批', `已更新群昵称 - QQ: ${qq}, 昵称: ${nickname}`);
|
|
669
666
|
}
|
|
@@ -671,7 +668,7 @@ class GroupRequestReviewHandler extends base_handler_1.BaseHandler {
|
|
|
671
668
|
this.logger.warn('入群审批', `更新群昵称失败: ${error.message}`);
|
|
672
669
|
// 昵称更新失败不影响绑定
|
|
673
670
|
}
|
|
674
|
-
this.logger.info('入群审批', `自动绑定完成 - QQ: ${qq}, UID: ${uid}, 用户名: ${
|
|
671
|
+
this.logger.info('入群审批', `自动绑定完成 - QQ: ${qq}, UID: ${uid}, 用户名: ${zminfoUser.username}`, true);
|
|
675
672
|
}
|
|
676
673
|
catch (error) {
|
|
677
674
|
this.logger.error('入群审批', `自动绑定失败: ${error.message}`, error);
|
package/lib/index.js
CHANGED
|
@@ -428,6 +428,18 @@ function apply(ctx, config) {
|
|
|
428
428
|
logger.info(`[新人绑定] 用户QQ(${normalizedUserId})加入群聊,准备发送绑定提醒`);
|
|
429
429
|
// 检查用户是否已有绑定记录
|
|
430
430
|
const existingBind = await services.database.getMcBindByQQId(normalizedUserId);
|
|
431
|
+
// 检查是否刚刚通过入群审批的自动绑定完成(lastModified 在最近 15 秒内且已绑定 B 站)
|
|
432
|
+
const isRecentAutoBind = existingBind &&
|
|
433
|
+
existingBind.lastModified &&
|
|
434
|
+
Date.now() - new Date(existingBind.lastModified).getTime() < 15000 &&
|
|
435
|
+
bind_status_1.BindStatus.hasValidBuidBind(existingBind);
|
|
436
|
+
if (isRecentAutoBind) {
|
|
437
|
+
// 刚刚完成自动绑定,只发送欢迎消息,不启动交互式绑定
|
|
438
|
+
logger.info(`[新人绑定] 用户QQ(${normalizedUserId})刚刚通过入群审批完成自动绑定,仅发送欢迎消息`);
|
|
439
|
+
const welcomeMessage = `🎉 欢迎 ${koishi_1.h.at(session.userId)} 加入群聊!\n\n✅ 您的B站账号已自动绑定完成\nB站: ${existingBind.buidUsername}\n\n💡 使用 ${formatCommand('mcid bind <用户名>')} 可绑定MC账号`;
|
|
440
|
+
await session.bot.sendMessage(session.channelId, welcomeMessage);
|
|
441
|
+
return;
|
|
442
|
+
}
|
|
431
443
|
// 如果用户已完成全部绑定,不需要提醒
|
|
432
444
|
if (bind_status_1.BindStatus.hasCompletedAllBinds(existingBind)) {
|
|
433
445
|
logger.info(`[新人绑定] 用户QQ(${normalizedUserId})已完成全部绑定,跳过提醒`);
|