koishi-plugin-bind-bot 2.0.2 → 2.0.4

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.
@@ -29,8 +29,9 @@ export interface HandlerDependencies {
29
29
  getMcBindByUsername: (username: string) => Promise<MCIDBIND | null>;
30
30
  createOrUpdateMcBind: (userId: string, username: string, uuid: string, isAdmin?: boolean) => Promise<boolean>;
31
31
  deleteMcBind: (userId: string) => Promise<boolean>;
32
- checkUsernameExists: (username: string, currentUserId?: string) => Promise<boolean>;
32
+ checkUsernameExists: (username: string, currentUserId?: string, uuid?: string) => Promise<boolean>;
33
33
  checkAndUpdateUsername: (bind: MCIDBIND) => Promise<MCIDBIND>;
34
+ checkAndUpdateUsernameWithCache: (bind: MCIDBIND) => Promise<MCIDBIND>;
34
35
  validateUsername: (username: string) => Promise<any>;
35
36
  validateBUID: (uid: string) => Promise<any>;
36
37
  updateBuidInfoOnly: (qqId: string, buidUser: any) => Promise<boolean>;
@@ -96,8 +96,8 @@ class McidCommandHandler extends base_handler_1.BaseHandler {
96
96
  }
97
97
  return this.deps.sendMessage(session, [koishi_1.h.text('该用户尚未绑定MC账号')]);
98
98
  }
99
- // 显示MC绑定信息
100
- const updatedBind = await this.deps.checkAndUpdateUsername(targetBind);
99
+ // 显示MC绑定信息(使用智能缓存检测,避免频繁API调用)
100
+ const updatedBind = await this.deps.checkAndUpdateUsernameWithCache(targetBind);
101
101
  return this.buildQueryResponse(session, updatedBind, normalizedTargetId);
102
102
  }
103
103
  // 查询自己
@@ -136,7 +136,8 @@ class McidCommandHandler extends base_handler_1.BaseHandler {
136
136
  }
137
137
  return this.deps.sendMessage(session, [koishi_1.h.text('您尚未绑定MC账号,请使用 ' + this.deps.formatCommand('mcid bind <用户名>') + ' 进行绑定')]);
138
138
  }
139
- const updatedBind = await this.deps.checkAndUpdateUsername(selfBind);
139
+ // 使用智能缓存检测,避免频繁API调用
140
+ const updatedBind = await this.deps.checkAndUpdateUsernameWithCache(selfBind);
140
141
  return this.buildQueryResponse(session, updatedBind, null);
141
142
  }
142
143
  catch (error) {
@@ -210,14 +211,6 @@ class McidCommandHandler extends base_handler_1.BaseHandler {
210
211
  buidInfo = targetId ? '该用户尚未绑定B站账号' : `您尚未绑定B站账号,使用 ${this.deps.formatCommand('buid bind <B站UID>')} 进行绑定`;
211
212
  }
212
213
  this.logger.info('查询', `QQ(${bind.qqId})的MC账号信息:用户名=${bind.mcUsername}, UUID=${bind.mcUuid}`);
213
- // 自动设置群昵称
214
- if (bind.buidUid && bind.buidUsername) {
215
- const mcName = bind.mcUsername && !bind.mcUsername.startsWith('_temp_') ? bind.mcUsername : null;
216
- await this.deps.autoSetGroupNickname(session, mcName, bind.buidUsername, targetId || undefined);
217
- }
218
- else {
219
- this.logger.info('查询', `QQ(${bind.qqId})未绑定B站账号,跳过群昵称设置`);
220
- }
221
214
  const displayUsername = bind.mcUsername && !bind.mcUsername.startsWith('_temp_') ? bind.mcUsername : '未绑定';
222
215
  const prefix = targetId ? `用户 ${targetId} 的` : '您的';
223
216
  const messageElements = [
@@ -226,7 +219,18 @@ class McidCommandHandler extends base_handler_1.BaseHandler {
226
219
  koishi_1.h.text(`\n${buidInfo}`),
227
220
  ...(buidAvatar ? [buidAvatar] : [])
228
221
  ];
229
- return this.deps.sendMessage(session, messageElements);
222
+ // 先发送响应,然后异步设置群昵称
223
+ const sendPromise = this.deps.sendMessage(session, messageElements);
224
+ // 异步设置群昵称
225
+ if (bind.buidUid && bind.buidUsername) {
226
+ const mcName = bind.mcUsername && !bind.mcUsername.startsWith('_temp_') ? bind.mcUsername : null;
227
+ this.deps.autoSetGroupNickname(session, mcName, bind.buidUsername, targetId || undefined)
228
+ .catch(err => this.logger.warn('查询', `群昵称设置失败: ${err.message}`));
229
+ }
230
+ else {
231
+ this.logger.info('查询', `QQ(${bind.qqId})未绑定B站账号,跳过群昵称设置`);
232
+ }
233
+ return sendPromise;
230
234
  }
231
235
  /**
232
236
  * 通过MC用户名查询QQ号
@@ -336,8 +340,8 @@ class McidCommandHandler extends base_handler_1.BaseHandler {
336
340
  this.logger.warn('绑定', `权限不足: QQ(${operatorId})不是管理员`);
337
341
  return this.deps.sendMessage(session, [koishi_1.h.text('只有管理员才能为其他用户绑定MC账号')]);
338
342
  }
339
- // 检查用户名是否已被占用
340
- if (await this.deps.checkUsernameExists(username, target)) {
343
+ // 检查用户名是否已被占用(支持改名检测)
344
+ if (await this.deps.checkUsernameExists(username, target, uuid)) {
341
345
  this.logger.warn('绑定', `MC用户名"${username}"已被其他QQ号绑定`);
342
346
  return this.deps.sendMessage(session, [koishi_1.h.text(`用户名 ${username} 已被其他用户绑定`)]);
343
347
  }
@@ -410,8 +414,8 @@ class McidCommandHandler extends base_handler_1.BaseHandler {
410
414
  this.logger.debug('绑定', `QQ(${operatorId})之前绑定的是临时用户名"${selfBind.mcUsername}",允许直接使用bind命令`);
411
415
  }
412
416
  }
413
- // 检查用户名是否已被占用
414
- if (await this.deps.checkUsernameExists(username)) {
417
+ // 检查用户名是否已被占用(支持改名检测)
418
+ if (await this.deps.checkUsernameExists(username, session.userId, uuid)) {
415
419
  this.logger.warn('绑定', `MC用户名"${username}"已被其他QQ号绑定`);
416
420
  return this.deps.sendMessage(session, [koishi_1.h.text(`用户名 ${username} 已被其他用户绑定`)]);
417
421
  }
@@ -512,8 +516,8 @@ class McidCommandHandler extends base_handler_1.BaseHandler {
512
516
  this.logger.warn('修改', `QQ(${normalizedTargetId})已绑定相同的MC账号"${username}"`);
513
517
  return this.deps.sendMessage(session, [koishi_1.h.text(`用户 ${normalizedTargetId} 当前已绑定此用户名: ${username}`)]);
514
518
  }
515
- // 检查用户名是否已被占用
516
- if (await this.deps.checkUsernameExists(username, target)) {
519
+ // 检查用户名是否已被占用(支持改名检测)
520
+ if (await this.deps.checkUsernameExists(username, target, uuid)) {
517
521
  this.logger.warn('修改', `MC用户名"${username}"已被其他QQ号绑定`);
518
522
  return this.deps.sendMessage(session, [koishi_1.h.text(`用户名 ${username} 已被其他用户绑定`)]);
519
523
  }
@@ -563,8 +567,8 @@ class McidCommandHandler extends base_handler_1.BaseHandler {
563
567
  this.logger.warn('修改', `QQ(${operatorId})在冷却期内,无法修改MC账号`);
564
568
  return this.deps.sendMessage(session, [koishi_1.h.text(`您的MC账号绑定在冷却期内,还需${remainingDays}天才能修改。如需立即修改,请联系管理员。`)]);
565
569
  }
566
- // 检查用户名是否已被占用
567
- if (await this.deps.checkUsernameExists(username, session.userId)) {
570
+ // 检查用户名是否已被占用(支持改名检测)
571
+ if (await this.deps.checkUsernameExists(username, session.userId, uuid)) {
568
572
  this.logger.warn('修改', `MC用户名"${username}"已被其他QQ号绑定`);
569
573
  return this.deps.sendMessage(session, [koishi_1.h.text(`用户名 ${username} 已被其他用户绑定`)]);
570
574
  }
@@ -779,11 +779,13 @@ class WhitelistHandler extends base_handler_1.BaseHandler {
779
779
  return false;
780
780
  }
781
781
  // 重新获取最新的用户绑定信息
782
- const freshBind = await this.deps.getBindInfo(mcBind.qqId);
782
+ let freshBind = await this.deps.getBindInfo(mcBind.qqId);
783
783
  if (!freshBind || !freshBind.mcUsername) {
784
784
  this.logger.warn('白名单', `用户QQ(${mcBind.qqId})可能在操作过程中解绑了MC账号`);
785
785
  return false;
786
786
  }
787
+ // 智能检测用户名变更(带缓存,避免频繁API调用)
788
+ freshBind = await this.deps.checkAndUpdateUsernameWithCache(freshBind);
787
789
  // 检查最新状态是否已在白名单中
788
790
  if (freshBind.whitelist && freshBind.whitelist.includes(server.id)) {
789
791
  this.logger.info('白名单', `用户QQ(${mcBind.qqId})已在服务器${server.name}的白名单中`);
@@ -860,11 +862,13 @@ class WhitelistHandler extends base_handler_1.BaseHandler {
860
862
  return false;
861
863
  }
862
864
  // 重新获取最新的用户绑定信息
863
- const freshBind = await this.deps.getBindInfo(mcBind.qqId);
865
+ let freshBind = await this.deps.getBindInfo(mcBind.qqId);
864
866
  if (!freshBind || !freshBind.mcUsername) {
865
867
  this.logger.warn('白名单', `用户QQ(${mcBind.qqId})可能在操作过程中解绑了MC账号`);
866
868
  return false;
867
869
  }
870
+ // 智能检测用户名变更(带缓存,避免频繁API调用)
871
+ freshBind = await this.deps.checkAndUpdateUsernameWithCache(freshBind);
868
872
  // 检查最新状态是否在白名单中
869
873
  if (!freshBind.whitelist || !freshBind.whitelist.includes(server.id)) {
870
874
  this.logger.info('白名单', `用户QQ(${mcBind.qqId})不在服务器${server.name}的白名单中`);
package/lib/index.js CHANGED
@@ -718,6 +718,14 @@ function apply(ctx, config) {
718
718
  type: 'integer',
719
719
  initial: 0,
720
720
  },
721
+ usernameLastChecked: {
722
+ type: 'timestamp',
723
+ initial: null,
724
+ },
725
+ usernameCheckFailCount: {
726
+ type: 'integer',
727
+ initial: 0,
728
+ },
721
729
  }, {
722
730
  // 设置主键为qqId
723
731
  primary: 'qqId',
@@ -1176,8 +1184,8 @@ function apply(ctx, config) {
1176
1184
  return false;
1177
1185
  }
1178
1186
  };
1179
- // 检查MC用户名是否已被其他QQ号绑定
1180
- const checkUsernameExists = async (username, currentUserId) => {
1187
+ // 检查MC用户名是否已被其他QQ号绑定(支持不区分大小写和UUID检查)
1188
+ const checkUsernameExists = async (username, currentUserId, uuid) => {
1181
1189
  try {
1182
1190
  // 验证输入参数
1183
1191
  if (!username) {
@@ -1188,8 +1196,8 @@ function apply(ctx, config) {
1188
1196
  if (username.startsWith('_temp_')) {
1189
1197
  return false;
1190
1198
  }
1191
- // 查询新表中是否已有此用户名的绑定
1192
- const bind = await getMcBindByUsername(username);
1199
+ // 使用不区分大小写的查询
1200
+ const bind = await mcidbindRepo.findByUsernameIgnoreCase(username);
1193
1201
  // 如果没有绑定,返回false
1194
1202
  if (!bind)
1195
1203
  return false;
@@ -1197,6 +1205,16 @@ function apply(ctx, config) {
1197
1205
  if (bind.mcUsername && bind.mcUsername.startsWith('_temp_')) {
1198
1206
  return false;
1199
1207
  }
1208
+ // 如果提供了 UUID,检查是否为同一个 MC 账号(用户改名场景)
1209
+ if (uuid && bind.mcUuid) {
1210
+ const cleanUuid = uuid.replace(/-/g, '');
1211
+ const bindCleanUuid = bind.mcUuid.replace(/-/g, '');
1212
+ if (cleanUuid === bindCleanUuid) {
1213
+ // 同一个 UUID,说明是用户改名,允许绑定
1214
+ logger.info(`[绑定检查] 检测到MC账号改名: UUID=${uuid}, 旧用户名=${bind.mcUsername}, 新用户名=${username}`);
1215
+ return false;
1216
+ }
1217
+ }
1200
1218
  // 如果提供了当前用户ID,需要排除当前用户
1201
1219
  if (currentUserId) {
1202
1220
  const normalizedCurrentId = normalizeQQId(currentUserId);
@@ -1647,8 +1665,10 @@ function apply(ctx, config) {
1647
1665
  logger.warn(`[用户名更新] 无法获取UUID "${bind.mcUuid}" 的最新用户名`);
1648
1666
  return bind;
1649
1667
  }
1650
- // 如果用户名与数据库中的不同,更新数据库
1651
- if (latestUsername !== bind.mcUsername) {
1668
+ // 如果用户名与数据库中的不同,更新数据库(使用规范化比较,不区分大小写)
1669
+ const normalizedLatest = (0, helpers_1.normalizeUsername)(latestUsername);
1670
+ const normalizedCurrent = (0, helpers_1.normalizeUsername)(bind.mcUsername);
1671
+ if (normalizedLatest !== normalizedCurrent) {
1652
1672
  logger.info(`[用户名更新] 用户 QQ(${bind.qqId}) 的Minecraft用户名已变更: ${bind.mcUsername} -> ${latestUsername}`);
1653
1673
  // 更新数据库中的用户名
1654
1674
  await ctx.database.set('mcidbind', { qqId: bind.qqId }, {
@@ -1664,6 +1684,88 @@ function apply(ctx, config) {
1664
1684
  return bind;
1665
1685
  }
1666
1686
  };
1687
+ /**
1688
+ * 智能缓存版本的改名检测函数
1689
+ * 特性:
1690
+ * - 24小时冷却期(失败>=3次时延长到72小时)
1691
+ * - 失败计数追踪
1692
+ * - 成功时重置失败计数
1693
+ * @param bind 绑定记录
1694
+ * @returns 更新后的绑定记录
1695
+ */
1696
+ const checkAndUpdateUsernameWithCache = async (bind) => {
1697
+ try {
1698
+ if (!bind || !bind.mcUuid) {
1699
+ logger.warn(`[改名检测缓存] 无法检查用户名更新: 空绑定或空UUID`);
1700
+ return bind;
1701
+ }
1702
+ const now = new Date();
1703
+ const failCount = bind.usernameCheckFailCount || 0;
1704
+ // 根据失败次数决定冷却期:普通24小时,失败>=3次则72小时
1705
+ const cooldownHours = failCount >= 3 ? 72 : 24;
1706
+ // 检查是否在冷却期内
1707
+ if (bind.usernameLastChecked) {
1708
+ const lastCheck = new Date(bind.usernameLastChecked);
1709
+ const hoursSinceCheck = (now.getTime() - lastCheck.getTime()) / (1000 * 60 * 60);
1710
+ if (hoursSinceCheck < cooldownHours) {
1711
+ logger.debug(`[改名检测缓存] QQ(${bind.qqId}) 在冷却期内(${hoursSinceCheck.toFixed(1)}h/${cooldownHours}h),跳过检查`);
1712
+ return bind;
1713
+ }
1714
+ }
1715
+ logger.debug(`[改名检测缓存] QQ(${bind.qqId}) 开始检查用户名变更(失败计数: ${failCount})`);
1716
+ // 执行实际的改名检测
1717
+ const oldUsername = bind.mcUsername;
1718
+ const updatedBind = await checkAndUpdateUsername(bind);
1719
+ // 判断检测是否成功
1720
+ const detectionSuccess = updatedBind.mcUsername !== null && updatedBind.mcUsername !== undefined;
1721
+ if (detectionSuccess) {
1722
+ // 检测成功
1723
+ const usernameChanged = (0, helpers_1.normalizeUsername)(updatedBind.mcUsername) !== (0, helpers_1.normalizeUsername)(oldUsername);
1724
+ // 更新检查时间和重置失败计数
1725
+ await mcidbindRepo.update(bind.qqId, {
1726
+ usernameLastChecked: now,
1727
+ usernameCheckFailCount: 0
1728
+ });
1729
+ if (usernameChanged) {
1730
+ logger.info(`[改名检测缓存] QQ(${bind.qqId}) 用户名已变更: ${oldUsername} -> ${updatedBind.mcUsername}`);
1731
+ }
1732
+ else {
1733
+ logger.debug(`[改名检测缓存] QQ(${bind.qqId}) 用户名无变更: ${updatedBind.mcUsername}`);
1734
+ }
1735
+ // 更新返回对象的缓存字段
1736
+ updatedBind.usernameLastChecked = now;
1737
+ updatedBind.usernameCheckFailCount = 0;
1738
+ }
1739
+ else {
1740
+ // 检测失败��API失败或返回null)
1741
+ const newFailCount = failCount + 1;
1742
+ await mcidbindRepo.update(bind.qqId, {
1743
+ usernameLastChecked: now,
1744
+ usernameCheckFailCount: newFailCount
1745
+ });
1746
+ logger.warn(`[改名检测缓存] QQ(${bind.qqId}) 检测失败,失败计数: ${newFailCount}`);
1747
+ // 更新返回对象的缓存字段
1748
+ updatedBind.usernameLastChecked = now;
1749
+ updatedBind.usernameCheckFailCount = newFailCount;
1750
+ }
1751
+ return updatedBind;
1752
+ }
1753
+ catch (error) {
1754
+ logger.error(`[改名检测缓存] 检查和更新用户名失败: ${error.message}`);
1755
+ // 失败时也更新检查时间和递增失败计数
1756
+ try {
1757
+ const failCount = bind.usernameCheckFailCount || 0;
1758
+ await mcidbindRepo.update(bind.qqId, {
1759
+ usernameLastChecked: new Date(),
1760
+ usernameCheckFailCount: failCount + 1
1761
+ });
1762
+ }
1763
+ catch (updateError) {
1764
+ logger.error(`[改名检测缓存] 更新失败计数时出错: ${updateError.message}`);
1765
+ }
1766
+ return bind;
1767
+ }
1768
+ };
1667
1769
  // 安全地替换命令模板
1668
1770
  const safeCommandReplace = (template, mcid) => {
1669
1771
  // 过滤可能导致命令注入的字符
@@ -1758,6 +1860,7 @@ function apply(ctx, config) {
1758
1860
  deleteMcBind,
1759
1861
  checkUsernameExists,
1760
1862
  checkAndUpdateUsername,
1863
+ checkAndUpdateUsernameWithCache,
1761
1864
  // API operations
1762
1865
  validateUsername,
1763
1866
  validateBUID,
@@ -2172,7 +2275,7 @@ function apply(ctx, config) {
2172
2275
  }
2173
2276
  }
2174
2277
  // 检查用户名是否已被其他人绑定
2175
- if (await checkUsernameExists(username, session.userId)) {
2278
+ if (await checkUsernameExists(username, session.userId, uuid)) {
2176
2279
  logger.warn(`[交互绑定] MC用户名"${username}"已被其他用户绑定`);
2177
2280
  await sendMessage(session, [koishi_1.h.text(`❌ 用户名 ${username} 已被其他用户绑定\n\n请输入其他MC用户名或发送"跳过"完成绑定`)]);
2178
2281
  return;
@@ -16,11 +16,23 @@ export declare class MCIDBINDRepository {
16
16
  */
17
17
  findByQQId(qqId: string): Promise<MCIDBIND | null>;
18
18
  /**
19
- * 根据 MC 用户名查询绑定信息
19
+ * 根据 MC 用户名查询绑定信息(精确匹配)
20
20
  * @param mcUsername MC用户名
21
21
  * @returns 绑定信息或 null
22
22
  */
23
23
  findByMCUsername(mcUsername: string): Promise<MCIDBIND | null>;
24
+ /**
25
+ * 根据 MC 用户名查询绑定信息(不区分大小写)
26
+ * @param mcUsername MC用户名
27
+ * @returns 绑定信息或 null
28
+ */
29
+ findByUsernameIgnoreCase(mcUsername: string): Promise<MCIDBIND | null>;
30
+ /**
31
+ * 根据 MC UUID 查询绑定信息
32
+ * @param mcUuid MC UUID(可带或不带连字符)
33
+ * @returns 绑定信息或 null
34
+ */
35
+ findByUuid(mcUuid: string): Promise<MCIDBIND | null>;
24
36
  /**
25
37
  * 根据 B站 UID 查询绑定信息
26
38
  * @param buidUid B站UID
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.MCIDBINDRepository = void 0;
4
+ const helpers_1 = require("../utils/helpers");
4
5
  /**
5
6
  * MCIDBIND 数据仓储类
6
7
  * 封装所有 MCIDBIND 表的数据库操作
@@ -29,7 +30,7 @@ class MCIDBINDRepository {
29
30
  }
30
31
  }
31
32
  /**
32
- * 根据 MC 用户名查询绑定信息
33
+ * 根据 MC 用户名查询绑定信息(精确匹配)
33
34
  * @param mcUsername MC用户名
34
35
  * @returns 绑定信息或 null
35
36
  */
@@ -44,6 +45,54 @@ class MCIDBINDRepository {
44
45
  return null;
45
46
  }
46
47
  }
48
+ /**
49
+ * 根据 MC 用户名查询绑定信息(不区分大小写)
50
+ * @param mcUsername MC用户名
51
+ * @returns 绑定信息或 null
52
+ */
53
+ async findByUsernameIgnoreCase(mcUsername) {
54
+ try {
55
+ const normalizedInput = (0, helpers_1.normalizeUsername)(mcUsername, this.logger.getRawLogger());
56
+ this.logger.debug('数据库', `查询MC用户名(${mcUsername} -> ${normalizedInput})的绑定信息(不区分大小写)`);
57
+ // 获取所有绑定记录,然后在应用层过滤(因为 Koishi 数据库不支持不区分大小写查询)
58
+ const allBinds = await this.ctx.database.get('mcidbind', {});
59
+ const match = allBinds.find(bind => bind.mcUsername && (0, helpers_1.normalizeUsername)(bind.mcUsername) === normalizedInput);
60
+ return match || null;
61
+ }
62
+ catch (error) {
63
+ this.logger.error('数据库', `查询MC用户名(${mcUsername})绑定信息失败(不区分大小写): ${error.message}`);
64
+ return null;
65
+ }
66
+ }
67
+ /**
68
+ * 根据 MC UUID 查询绑定信息
69
+ * @param mcUuid MC UUID(可带或不带连字符)
70
+ * @returns 绑定信息或 null
71
+ */
72
+ async findByUuid(mcUuid) {
73
+ try {
74
+ // 规范化 UUID(移除连字符)
75
+ const cleanUuid = mcUuid.replace(/-/g, '');
76
+ this.logger.debug('数据库', `查询MC UUID(${cleanUuid})的绑定信息`);
77
+ // 先尝试精确匹配
78
+ let binds = await this.ctx.database.get('mcidbind', { mcUuid: cleanUuid });
79
+ if (binds.length > 0)
80
+ return binds[0];
81
+ // 尝试带连字符的格式
82
+ const formattedUuid = `${cleanUuid.substring(0, 8)}-${cleanUuid.substring(8, 12)}-${cleanUuid.substring(12, 16)}-${cleanUuid.substring(16, 20)}-${cleanUuid.substring(20)}`;
83
+ binds = await this.ctx.database.get('mcidbind', { mcUuid: formattedUuid });
84
+ if (binds.length > 0)
85
+ return binds[0];
86
+ // 如果都没有,在应用层过滤(去除连字符后比较)
87
+ const allBinds = await this.ctx.database.get('mcidbind', {});
88
+ const match = allBinds.find(bind => bind.mcUuid && bind.mcUuid.replace(/-/g, '') === cleanUuid);
89
+ return match || null;
90
+ }
91
+ catch (error) {
92
+ this.logger.error('数据库', `查询MC UUID(${mcUuid})绑定信息失败: ${error.message}`);
93
+ return null;
94
+ }
95
+ }
47
96
  /**
48
97
  * 根据 B站 UID 查询绑定信息
49
98
  * @param buidUid B站UID
@@ -9,6 +9,8 @@ export interface MCIDBIND {
9
9
  qqId: string;
10
10
  mcUsername: string;
11
11
  mcUuid: string;
12
+ usernameLastChecked?: Date;
13
+ usernameCheckFailCount?: number;
12
14
  lastModified: Date;
13
15
  isAdmin: boolean;
14
16
  whitelist: string[];
@@ -89,3 +89,21 @@ export declare function levenshteinDistance(str1: string, str2: string): number;
89
89
  * @returns 相似度值(0到1之间,1表示完全相同)
90
90
  */
91
91
  export declare function calculateSimilarity(str1: string, str2: string): number;
92
+ /**
93
+ * 规范化 Minecraft 用户名(统一小写,用于存储和比较)
94
+ * Minecraft 用户名不区分大小写,但 Mojang 返回的是规范大小写
95
+ * 为避免 "Notch" 和 "notch" 被视为不同用户,统一转小写存储
96
+ *
97
+ * @param username MC 用户名
98
+ * @param logger Koishi Logger实例(用于日志)
99
+ * @returns 规范化后的用户名(小写)
100
+ */
101
+ export declare function normalizeUsername(username: string, logger?: Logger): string;
102
+ /**
103
+ * 比较两个 Minecraft 用户名是否相同(不区分大小写)
104
+ *
105
+ * @param username1 第一个用户名
106
+ * @param username2 第二个用户名
107
+ * @returns 是否相同
108
+ */
109
+ export declare function isSameUsername(username1: string, username2: string): boolean;
@@ -11,6 +11,8 @@ exports.escapeRegExp = escapeRegExp;
11
11
  exports.cleanUserInput = cleanUserInput;
12
12
  exports.levenshteinDistance = levenshteinDistance;
13
13
  exports.calculateSimilarity = calculateSimilarity;
14
+ exports.normalizeUsername = normalizeUsername;
15
+ exports.isSameUsername = isSameUsername;
14
16
  /**
15
17
  * 通用工具函数集合
16
18
  */
@@ -319,3 +321,42 @@ function calculateSimilarity(str1, str2) {
319
321
  const maxLength = Math.max(str1.length, str2.length);
320
322
  return 1 - distance / maxLength;
321
323
  }
324
+ /**
325
+ * 规范化 Minecraft 用户名(统一小写,用于存储和比较)
326
+ * Minecraft 用户名不区分大小写,但 Mojang 返回的是规范大小写
327
+ * 为避免 "Notch" 和 "notch" 被视为不同用户,统一转小写存储
328
+ *
329
+ * @param username MC 用户名
330
+ * @param logger Koishi Logger实例(用于日志)
331
+ * @returns 规范化后的用户名(小写)
332
+ */
333
+ function normalizeUsername(username, logger) {
334
+ if (!username) {
335
+ logger?.warn(`[用户名规范化] 收到空用户名`);
336
+ return '';
337
+ }
338
+ // 移除首尾空格
339
+ const trimmed = username.trim();
340
+ // 检查是否为临时用户名,临时用户名不做转换
341
+ if (trimmed.startsWith('_temp_')) {
342
+ return trimmed;
343
+ }
344
+ // 转小写
345
+ const normalized = trimmed.toLowerCase();
346
+ if (normalized !== trimmed) {
347
+ logger?.debug(`[用户名规范化] "${trimmed}" -> "${normalized}"`);
348
+ }
349
+ return normalized;
350
+ }
351
+ /**
352
+ * 比较两个 Minecraft 用户名是否相同(不区分大小写)
353
+ *
354
+ * @param username1 第一个用户名
355
+ * @param username2 第二个用户名
356
+ * @returns 是否相同
357
+ */
358
+ function isSameUsername(username1, username2) {
359
+ if (!username1 || !username2)
360
+ return false;
361
+ return normalizeUsername(username1) === normalizeUsername(username2);
362
+ }
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.0.2",
4
+ "version": "2.0.4",
5
5
  "main": "lib/index.js",
6
6
  "typings": "lib/index.d.ts",
7
7
  "files": [