koishi-plugin-bind-bot 2.2.3 → 2.2.5
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.
- package/lib/handlers/buid.handler.js +2 -2
- package/lib/handlers/group-request-review.handler.js +67 -67
- package/lib/handlers/mcid.handler.js +2 -2
- package/lib/handlers/tag.handler.js +4 -4
- package/lib/index.js +14 -2
- package/lib/services/database.service.js +2 -2
- package/lib/types/database.d.ts +4 -4
- package/lib/types/update-data.d.ts +4 -4
- package/package.json +1 -1
|
@@ -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';
|
|
@@ -524,12 +539,14 @@ class GroupRequestReviewHandler extends base_handler_1.BaseHandler {
|
|
|
524
539
|
this.logger.debug('入群审批', `parseUID: 从"答案:"提取 -> ${answerMatch[1]}`);
|
|
525
540
|
return answerMatch[1];
|
|
526
541
|
}
|
|
527
|
-
// 格式3: UID:123456789
|
|
542
|
+
// 格式3: UID:123456789 或 UID:123456789 用户名(参考BuidHandler)
|
|
528
543
|
if (input.toLowerCase().startsWith('uid:')) {
|
|
529
|
-
const
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
544
|
+
const afterPrefix = input.substring(4).trim();
|
|
545
|
+
// 提取第一个连续的数字串(支持后面跟着其他内容)
|
|
546
|
+
const uidMatch = afterPrefix.match(/^(\d+)/);
|
|
547
|
+
if (uidMatch) {
|
|
548
|
+
this.logger.debug('入群审批', `parseUID: 从"UID:"前缀提取 -> ${uidMatch[1]}`);
|
|
549
|
+
return uidMatch[1];
|
|
533
550
|
}
|
|
534
551
|
}
|
|
535
552
|
// 格式4: https://space.bilibili.com/123456789(参考BuidHandler的完善处理)
|
|
@@ -553,10 +570,11 @@ class GroupRequestReviewHandler extends base_handler_1.BaseHandler {
|
|
|
553
570
|
this.logger.warn('入群审批', `parseUID: URL解析失败 - ${error.message}`);
|
|
554
571
|
}
|
|
555
572
|
}
|
|
556
|
-
// 格式5:
|
|
557
|
-
|
|
573
|
+
// 格式5: 从文本中提取第一个数字串(4位及以上,避免误匹配过短的序号)
|
|
574
|
+
// 注:B站UID可以是1-2位,但在混合文本中用4位作为最小值可减少误匹配
|
|
575
|
+
const numberMatch = input.match(/\b(\d{4,})\b/);
|
|
558
576
|
if (numberMatch) {
|
|
559
|
-
this.logger.debug('入群审批', `parseUID:
|
|
577
|
+
this.logger.debug('入群审批', `parseUID: 从文本提取数字串 -> ${numberMatch[1]}`);
|
|
560
578
|
return numberMatch[1];
|
|
561
579
|
}
|
|
562
580
|
this.logger.warn('入群审批', `parseUID: 无法解析 - "${input}"`);
|
|
@@ -579,34 +597,16 @@ class GroupRequestReviewHandler extends base_handler_1.BaseHandler {
|
|
|
579
597
|
*/
|
|
580
598
|
async performAutoBind(qq, uid, bot) {
|
|
581
599
|
try {
|
|
582
|
-
// 1.
|
|
600
|
+
// 1. 使用强制绑定模式获取最新用户信息(避免频率限制)
|
|
583
601
|
this.logger.debug('入群审批', `开始获取 B站 UID ${uid} 的信息`);
|
|
584
|
-
//
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
const officialInfo = await this.deps.apiService.getBilibiliOfficialUserInfo(uid);
|
|
589
|
-
if (officialInfo && officialInfo.name) {
|
|
590
|
-
officialUsername = officialInfo.name;
|
|
591
|
-
this.logger.info('入群审批', `[官方API] ✅ 获取到用户名: "${officialUsername}"`, true);
|
|
592
|
-
}
|
|
593
|
-
else {
|
|
594
|
-
this.logger.warn('入群审批', '[官方API] ❌ 查询失败');
|
|
595
|
-
}
|
|
596
|
-
}
|
|
597
|
-
catch (officialError) {
|
|
598
|
-
this.logger.warn('入群审批', `[官方API] ❌ 查询出错: ${officialError.message}`);
|
|
599
|
-
}
|
|
600
|
-
// 获取ZMINFO API的完整用户信息(包含粉丝牌、大航海等数据)
|
|
601
|
-
this.logger.debug('入群审批', '正在查询ZMINFO API...');
|
|
602
|
-
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);
|
|
603
606
|
if (!zminfoUser) {
|
|
604
607
|
throw new Error(`无法验证B站UID: ${uid}`);
|
|
605
608
|
}
|
|
606
|
-
this.logger.
|
|
607
|
-
// 使用官方API的用户名(如果可用),否则使用ZMINFO的
|
|
608
|
-
const finalUsername = officialUsername || zminfoUser.username;
|
|
609
|
-
this.logger.info('入群审批', `🎯 最终采用用户名: "${finalUsername}"`, true);
|
|
609
|
+
this.logger.info('入群审批', `✅ 获取到用户名: "${zminfoUser.username}"`, true);
|
|
610
610
|
// 2. 检查是否已被其他人绑定
|
|
611
611
|
const existingBind = await this.repos.mcidbind.findByBuidUid(uid);
|
|
612
612
|
if (existingBind && existingBind.qqId !== qq) {
|
|
@@ -621,7 +621,7 @@ class GroupRequestReviewHandler extends base_handler_1.BaseHandler {
|
|
|
621
621
|
mcUsername: null,
|
|
622
622
|
mcUuid: null,
|
|
623
623
|
buidUid: zminfoUser.uid,
|
|
624
|
-
buidUsername:
|
|
624
|
+
buidUsername: zminfoUser.username,
|
|
625
625
|
guardLevel: zminfoUser.guard_level || 0,
|
|
626
626
|
guardLevelText: zminfoUser.guard_level_text || '',
|
|
627
627
|
maxGuardLevel: zminfoUser.guard_level || 0,
|
|
@@ -640,7 +640,7 @@ class GroupRequestReviewHandler extends base_handler_1.BaseHandler {
|
|
|
640
640
|
// 更新现有绑定
|
|
641
641
|
await this.repos.mcidbind.update(qq, {
|
|
642
642
|
buidUid: zminfoUser.uid,
|
|
643
|
-
buidUsername:
|
|
643
|
+
buidUsername: zminfoUser.username,
|
|
644
644
|
guardLevel: zminfoUser.guard_level || 0,
|
|
645
645
|
guardLevelText: zminfoUser.guard_level_text || '',
|
|
646
646
|
maxGuardLevel: Math.max(bind.maxGuardLevel || 0, zminfoUser.guard_level || 0),
|
|
@@ -660,7 +660,7 @@ class GroupRequestReviewHandler extends base_handler_1.BaseHandler {
|
|
|
660
660
|
try {
|
|
661
661
|
const groupId = this.reviewConfig.targetGroupId;
|
|
662
662
|
const mcInfo = bind_status_1.BindStatus.getDisplayMcUsername(bind, '未绑定');
|
|
663
|
-
const nickname = `${
|
|
663
|
+
const nickname = `${zminfoUser.username}(ID:${mcInfo})`;
|
|
664
664
|
await bot.internal.setGroupCard(groupId, qq, nickname);
|
|
665
665
|
this.logger.info('入群审批', `已更新群昵称 - QQ: ${qq}, 昵称: ${nickname}`);
|
|
666
666
|
}
|
|
@@ -668,7 +668,7 @@ class GroupRequestReviewHandler extends base_handler_1.BaseHandler {
|
|
|
668
668
|
this.logger.warn('入群审批', `更新群昵称失败: ${error.message}`);
|
|
669
669
|
// 昵称更新失败不影响绑定
|
|
670
670
|
}
|
|
671
|
-
this.logger.info('入群审批', `自动绑定完成 - QQ: ${qq}, UID: ${uid}, 用户名: ${
|
|
671
|
+
this.logger.info('入群审批', `自动绑定完成 - QQ: ${qq}, UID: ${uid}, 用户名: ${zminfoUser.username}`, true);
|
|
672
672
|
}
|
|
673
673
|
catch (error) {
|
|
674
674
|
this.logger.error('入群审批', `自动绑定失败: ${error.message}`, error);
|
|
@@ -777,8 +777,8 @@ class McidCommandHandler extends base_handler_1.BaseHandler {
|
|
|
777
777
|
try {
|
|
778
778
|
await this.repos.mcidbind.create({
|
|
779
779
|
qqId: normalizedTargetId,
|
|
780
|
-
mcUsername:
|
|
781
|
-
mcUuid:
|
|
780
|
+
mcUsername: null,
|
|
781
|
+
mcUuid: null,
|
|
782
782
|
lastModified: new Date(),
|
|
783
783
|
isAdmin: true,
|
|
784
784
|
hasMcBind: false,
|
|
@@ -81,8 +81,8 @@ class TagHandler extends base_handler_1.BaseHandler {
|
|
|
81
81
|
if (!targetBind) {
|
|
82
82
|
await this.repos.mcidbind.create({
|
|
83
83
|
qqId: normalizedTargetId,
|
|
84
|
-
mcUsername:
|
|
85
|
-
mcUuid:
|
|
84
|
+
mcUsername: null,
|
|
85
|
+
mcUuid: null,
|
|
86
86
|
lastModified: new Date(),
|
|
87
87
|
isAdmin: false,
|
|
88
88
|
whitelist: [],
|
|
@@ -119,8 +119,8 @@ class TagHandler extends base_handler_1.BaseHandler {
|
|
|
119
119
|
if (!targetBind) {
|
|
120
120
|
await this.repos.mcidbind.create({
|
|
121
121
|
qqId: normalizedTargetId,
|
|
122
|
-
mcUsername:
|
|
123
|
-
mcUuid:
|
|
122
|
+
mcUsername: null,
|
|
123
|
+
mcUuid: null,
|
|
124
124
|
lastModified: new Date(),
|
|
125
125
|
isAdmin: false,
|
|
126
126
|
whitelist: [],
|
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})已完成全部绑定,跳过提醒`);
|
|
@@ -1316,8 +1328,8 @@ function apply(ctx, config) {
|
|
|
1316
1328
|
// 创建新记录
|
|
1317
1329
|
await mcidbindRepo.create({
|
|
1318
1330
|
qqId: normalizedUserId,
|
|
1319
|
-
mcUsername:
|
|
1320
|
-
mcUuid:
|
|
1331
|
+
mcUsername: null,
|
|
1332
|
+
mcUuid: null,
|
|
1321
1333
|
lastModified: new Date(),
|
|
1322
1334
|
isAdmin: false,
|
|
1323
1335
|
whitelist: [],
|
|
@@ -383,8 +383,8 @@ class DatabaseService {
|
|
|
383
383
|
if (bind_status_1.BindStatus.hasValidMcBind(bind)) {
|
|
384
384
|
// 如果有MC绑定,只清空B站字段,保留记录
|
|
385
385
|
await this.mcidbindRepo.update(normalizedQQId, {
|
|
386
|
-
buidUid:
|
|
387
|
-
buidUsername:
|
|
386
|
+
buidUid: null,
|
|
387
|
+
buidUsername: null,
|
|
388
388
|
guardLevel: 0,
|
|
389
389
|
guardLevelText: '',
|
|
390
390
|
maxGuardLevel: 0,
|
package/lib/types/database.d.ts
CHANGED
|
@@ -54,10 +54,10 @@ export interface MCIDBIND {
|
|
|
54
54
|
whitelist: string[];
|
|
55
55
|
/** 用户标签列表 (用于分类管理) */
|
|
56
56
|
tags: string[];
|
|
57
|
-
/** B站UID */
|
|
58
|
-
buidUid: string;
|
|
59
|
-
/** B站用户名 */
|
|
60
|
-
buidUsername: string;
|
|
57
|
+
/** B站UID (NULL表示未绑定) */
|
|
58
|
+
buidUid: string | null;
|
|
59
|
+
/** B站用户名 (NULL表示未绑定) */
|
|
60
|
+
buidUsername: string | null;
|
|
61
61
|
/** 当前舰长等级 (0=无, 1=总督, 2=提督, 3=舰长) */
|
|
62
62
|
guardLevel: number;
|
|
63
63
|
/** 当前舰长等级文本 (例: '舰长', '提督', '总督') */
|
|
@@ -66,10 +66,10 @@ export interface UpdateMcBindData {
|
|
|
66
66
|
* ```
|
|
67
67
|
*/
|
|
68
68
|
export interface UpdateBuidBindData {
|
|
69
|
-
/** B站UID (
|
|
70
|
-
buidUid?: string;
|
|
71
|
-
/** B站用户名 */
|
|
72
|
-
buidUsername?: string;
|
|
69
|
+
/** B站UID (数据库中存储为字符串, NULL表示未绑定) */
|
|
70
|
+
buidUid?: string | null;
|
|
71
|
+
/** B站用户名 (NULL表示未绑定) */
|
|
72
|
+
buidUsername?: string | null;
|
|
73
73
|
/** 当前舰长等级 */
|
|
74
74
|
guardLevel?: number;
|
|
75
75
|
/** 当前舰长等级文本 */
|