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.
@@ -398,8 +398,8 @@ class BuidHandler extends base_handler_1.BaseHandler {
398
398
  else {
399
399
  const newBind = {
400
400
  qqId: normalizedQQId,
401
- mcUsername: '',
402
- mcUuid: '',
401
+ mcUsername: null,
402
+ mcUuid: null,
403
403
  isAdmin: false,
404
404
  whitelist: [],
405
405
  tags: [],
@@ -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
- // 并行查询官方API和ZMINFO API
179
- const [officialInfo, zminfoData] = await Promise.all([
180
- this.deps.apiService.getBilibiliOfficialUserInfo(parsedUid).catch(() => null),
181
- this.deps.apiService.validateBUID(parsedUid).catch(() => null)
182
- ]);
183
- // 用户名:优先使用官方API(最准确),降级到ZMINFO
184
- if (officialInfo?.name) {
185
- buidUsername = officialInfo.name;
186
- this.logger.debug('入群审批', `✅ 使用官方API用户名: ${buidUsername}`);
187
- }
188
- else if (zminfoData?.username) {
189
- buidUsername = zminfoData.username;
190
- this.logger.debug('入群审批', `⚠️ 官方API失败,使用ZMINFO用户名: ${buidUsername}`);
191
- }
192
- // 粉丝牌信息:只能从ZMINFO获取(官方API不提供)
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('入群审批', 'ZMINFO API查询失败,无法获取粉丝牌信息');
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 查询失败(官方API和ZMINFO均失败)';
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
- // 4. 调用 BuidHandler 的绑定逻辑(需要从 handlers 获取)
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(参考BuidHandler)
542
+ // 格式3: UID:123456789 或 UID:123456789 用户名(参考BuidHandler)
528
543
  if (input.toLowerCase().startsWith('uid:')) {
529
- const uid = input.substring(4).trim();
530
- if (/^\d+$/.test(uid)) {
531
- this.logger.debug('入群审批', `parseUID: 从"UID:"前缀提取 -> ${uid}`);
532
- return uid;
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: 从文本中提取第一个长数字串(8-12位,B站UID的典型长度)
557
- const numberMatch = input.match(/\b(\d{8,12})\b/);
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: 从文本提取长数字串 -> ${numberMatch[1]}`);
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. 使用双API数据源获取最新用户信息(优先B站官方API)
600
+ // 1. 使用强制绑定模式获取最新用户信息(避免频率限制)
583
601
  this.logger.debug('入群审批', `开始获取 B站 UID ${uid} 的信息`);
584
- // 尝试获取B站官方API的用户信息(最权威)
585
- let officialUsername = null;
586
- try {
587
- this.logger.debug('入群审批', '正在查询B站官方API...');
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.debug('入群审批', `[ZMINFO] 获取到用户名: "${zminfoUser.username}"`);
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: finalUsername,
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: finalUsername,
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 = `${finalUsername}(ID:${mcInfo})`;
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}, 用户名: ${finalUsername}`, true);
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,
@@ -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
  /** 当前舰长等级文本 */
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "koishi-plugin-bind-bot",
3
3
  "description": "[WittF自用] BIND-BOT - 账号绑定管理机器人,支持Minecraft账号和B站账号绑定与管理。",
4
- "version": "2.2.3",
4
+ "version": "2.2.5",
5
5
  "main": "lib/index.js",
6
6
  "typings": "lib/index.d.ts",
7
7
  "files": [