koishi-plugin-bind-bot 2.0.2 → 2.0.3

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) {
@@ -336,8 +337,8 @@ class McidCommandHandler extends base_handler_1.BaseHandler {
336
337
  this.logger.warn('绑定', `权限不足: QQ(${operatorId})不是管理员`);
337
338
  return this.deps.sendMessage(session, [koishi_1.h.text('只有管理员才能为其他用户绑定MC账号')]);
338
339
  }
339
- // 检查用户名是否已被占用
340
- if (await this.deps.checkUsernameExists(username, target)) {
340
+ // 检查用户名是否已被占用(支持改名检测)
341
+ if (await this.deps.checkUsernameExists(username, target, uuid)) {
341
342
  this.logger.warn('绑定', `MC用户名"${username}"已被其他QQ号绑定`);
342
343
  return this.deps.sendMessage(session, [koishi_1.h.text(`用户名 ${username} 已被其他用户绑定`)]);
343
344
  }
@@ -410,8 +411,8 @@ class McidCommandHandler extends base_handler_1.BaseHandler {
410
411
  this.logger.debug('绑定', `QQ(${operatorId})之前绑定的是临时用户名"${selfBind.mcUsername}",允许直接使用bind命令`);
411
412
  }
412
413
  }
413
- // 检查用户名是否已被占用
414
- if (await this.deps.checkUsernameExists(username)) {
414
+ // 检查用户名是否已被占用(支持改名检测)
415
+ if (await this.deps.checkUsernameExists(username, session.userId, uuid)) {
415
416
  this.logger.warn('绑定', `MC用户名"${username}"已被其他QQ号绑定`);
416
417
  return this.deps.sendMessage(session, [koishi_1.h.text(`用户名 ${username} 已被其他用户绑定`)]);
417
418
  }
@@ -512,8 +513,8 @@ class McidCommandHandler extends base_handler_1.BaseHandler {
512
513
  this.logger.warn('修改', `QQ(${normalizedTargetId})已绑定相同的MC账号"${username}"`);
513
514
  return this.deps.sendMessage(session, [koishi_1.h.text(`用户 ${normalizedTargetId} 当前已绑定此用户名: ${username}`)]);
514
515
  }
515
- // 检查用户名是否已被占用
516
- if (await this.deps.checkUsernameExists(username, target)) {
516
+ // 检查用户名是否已被占用(支持改名检测)
517
+ if (await this.deps.checkUsernameExists(username, target, uuid)) {
517
518
  this.logger.warn('修改', `MC用户名"${username}"已被其他QQ号绑定`);
518
519
  return this.deps.sendMessage(session, [koishi_1.h.text(`用户名 ${username} 已被其他用户绑定`)]);
519
520
  }
@@ -563,8 +564,8 @@ class McidCommandHandler extends base_handler_1.BaseHandler {
563
564
  this.logger.warn('修改', `QQ(${operatorId})在冷却期内,无法修改MC账号`);
564
565
  return this.deps.sendMessage(session, [koishi_1.h.text(`您的MC账号绑定在冷却期内,还需${remainingDays}天才能修改。如需立即修改,请联系管理员。`)]);
565
566
  }
566
- // 检查用户名是否已被占用
567
- if (await this.deps.checkUsernameExists(username, session.userId)) {
567
+ // 检查用户名是否已被占用(支持改名检测)
568
+ if (await this.deps.checkUsernameExists(username, session.userId, uuid)) {
568
569
  this.logger.warn('修改', `MC用户名"${username}"已被其他QQ号绑定`);
569
570
  return this.deps.sendMessage(session, [koishi_1.h.text(`用户名 ${username} 已被其他用户绑定`)]);
570
571
  }
@@ -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
@@ -1176,8 +1176,8 @@ function apply(ctx, config) {
1176
1176
  return false;
1177
1177
  }
1178
1178
  };
1179
- // 检查MC用户名是否已被其他QQ号绑定
1180
- const checkUsernameExists = async (username, currentUserId) => {
1179
+ // 检查MC用户名是否已被其他QQ号绑定(支持不区分大小写和UUID检查)
1180
+ const checkUsernameExists = async (username, currentUserId, uuid) => {
1181
1181
  try {
1182
1182
  // 验证输入参数
1183
1183
  if (!username) {
@@ -1188,8 +1188,8 @@ function apply(ctx, config) {
1188
1188
  if (username.startsWith('_temp_')) {
1189
1189
  return false;
1190
1190
  }
1191
- // 查询新表中是否已有此用户名的绑定
1192
- const bind = await getMcBindByUsername(username);
1191
+ // 使用不区分大小写的查询
1192
+ const bind = await mcidbindRepo.findByUsernameIgnoreCase(username);
1193
1193
  // 如果没有绑定,返回false
1194
1194
  if (!bind)
1195
1195
  return false;
@@ -1197,6 +1197,16 @@ function apply(ctx, config) {
1197
1197
  if (bind.mcUsername && bind.mcUsername.startsWith('_temp_')) {
1198
1198
  return false;
1199
1199
  }
1200
+ // 如果提供了 UUID,检查是否为同一个 MC 账号(用户改名场景)
1201
+ if (uuid && bind.mcUuid) {
1202
+ const cleanUuid = uuid.replace(/-/g, '');
1203
+ const bindCleanUuid = bind.mcUuid.replace(/-/g, '');
1204
+ if (cleanUuid === bindCleanUuid) {
1205
+ // 同一个 UUID,说明是用户改名,允许绑定
1206
+ logger.info(`[绑定检查] 检测到MC账号改名: UUID=${uuid}, 旧用户名=${bind.mcUsername}, 新用户名=${username}`);
1207
+ return false;
1208
+ }
1209
+ }
1200
1210
  // 如果提供了当前用户ID,需要排除当前用户
1201
1211
  if (currentUserId) {
1202
1212
  const normalizedCurrentId = normalizeQQId(currentUserId);
@@ -1647,8 +1657,10 @@ function apply(ctx, config) {
1647
1657
  logger.warn(`[用户名更新] 无法获取UUID "${bind.mcUuid}" 的最新用户名`);
1648
1658
  return bind;
1649
1659
  }
1650
- // 如果用户名与数据库中的不同,更新数据库
1651
- if (latestUsername !== bind.mcUsername) {
1660
+ // 如果用户名与数据库中的不同,更新数据库(使用规范化比较,不区分大小写)
1661
+ const normalizedLatest = (0, helpers_1.normalizeUsername)(latestUsername);
1662
+ const normalizedCurrent = (0, helpers_1.normalizeUsername)(bind.mcUsername);
1663
+ if (normalizedLatest !== normalizedCurrent) {
1652
1664
  logger.info(`[用户名更新] 用户 QQ(${bind.qqId}) 的Minecraft用户名已变更: ${bind.mcUsername} -> ${latestUsername}`);
1653
1665
  // 更新数据库中的用户名
1654
1666
  await ctx.database.set('mcidbind', { qqId: bind.qqId }, {
@@ -1664,6 +1676,88 @@ function apply(ctx, config) {
1664
1676
  return bind;
1665
1677
  }
1666
1678
  };
1679
+ /**
1680
+ * 智能缓存版本的改名检测函数
1681
+ * 特性:
1682
+ * - 24小时冷却期(失败>=3次时延长到72小时)
1683
+ * - 失败计数追踪
1684
+ * - 成功时重置失败计数
1685
+ * @param bind 绑定记录
1686
+ * @returns 更新后的绑定记录
1687
+ */
1688
+ const checkAndUpdateUsernameWithCache = async (bind) => {
1689
+ try {
1690
+ if (!bind || !bind.mcUuid) {
1691
+ logger.warn(`[改名检测缓存] 无法检查用户名更新: 空绑定或空UUID`);
1692
+ return bind;
1693
+ }
1694
+ const now = new Date();
1695
+ const failCount = bind.usernameCheckFailCount || 0;
1696
+ // 根据失败次数决定冷却期:普通24小时,失败>=3次则72小时
1697
+ const cooldownHours = failCount >= 3 ? 72 : 24;
1698
+ // 检查是否在冷却期内
1699
+ if (bind.usernameLastChecked) {
1700
+ const lastCheck = new Date(bind.usernameLastChecked);
1701
+ const hoursSinceCheck = (now.getTime() - lastCheck.getTime()) / (1000 * 60 * 60);
1702
+ if (hoursSinceCheck < cooldownHours) {
1703
+ logger.debug(`[改名检测缓存] QQ(${bind.qqId}) 在冷却期内(${hoursSinceCheck.toFixed(1)}h/${cooldownHours}h),跳过检查`);
1704
+ return bind;
1705
+ }
1706
+ }
1707
+ logger.debug(`[改名检测缓存] QQ(${bind.qqId}) 开始检查用户名变更(失败计数: ${failCount})`);
1708
+ // 执行实际的改名检测
1709
+ const oldUsername = bind.mcUsername;
1710
+ const updatedBind = await checkAndUpdateUsername(bind);
1711
+ // 判断检测是否成功
1712
+ const detectionSuccess = updatedBind.mcUsername !== null && updatedBind.mcUsername !== undefined;
1713
+ if (detectionSuccess) {
1714
+ // 检测成功
1715
+ const usernameChanged = (0, helpers_1.normalizeUsername)(updatedBind.mcUsername) !== (0, helpers_1.normalizeUsername)(oldUsername);
1716
+ // 更新检查时间和重置失败计数
1717
+ await mcidbindRepo.update(bind.qqId, {
1718
+ usernameLastChecked: now,
1719
+ usernameCheckFailCount: 0
1720
+ });
1721
+ if (usernameChanged) {
1722
+ logger.info(`[改名检测缓存] QQ(${bind.qqId}) 用户名已变更: ${oldUsername} -> ${updatedBind.mcUsername}`);
1723
+ }
1724
+ else {
1725
+ logger.debug(`[改名检测缓存] QQ(${bind.qqId}) 用户名无变更: ${updatedBind.mcUsername}`);
1726
+ }
1727
+ // 更新返回对象的缓存字段
1728
+ updatedBind.usernameLastChecked = now;
1729
+ updatedBind.usernameCheckFailCount = 0;
1730
+ }
1731
+ else {
1732
+ // 检测失败��API失败或返回null)
1733
+ const newFailCount = failCount + 1;
1734
+ await mcidbindRepo.update(bind.qqId, {
1735
+ usernameLastChecked: now,
1736
+ usernameCheckFailCount: newFailCount
1737
+ });
1738
+ logger.warn(`[改名检测缓存] QQ(${bind.qqId}) 检测失败,失败计数: ${newFailCount}`);
1739
+ // 更新返回对象的缓存字段
1740
+ updatedBind.usernameLastChecked = now;
1741
+ updatedBind.usernameCheckFailCount = newFailCount;
1742
+ }
1743
+ return updatedBind;
1744
+ }
1745
+ catch (error) {
1746
+ logger.error(`[改名检测缓存] 检查和更新用户名失败: ${error.message}`);
1747
+ // 失败时也更新检查时间和递增失败计数
1748
+ try {
1749
+ const failCount = bind.usernameCheckFailCount || 0;
1750
+ await mcidbindRepo.update(bind.qqId, {
1751
+ usernameLastChecked: new Date(),
1752
+ usernameCheckFailCount: failCount + 1
1753
+ });
1754
+ }
1755
+ catch (updateError) {
1756
+ logger.error(`[改名检测缓存] 更新失败计数时出错: ${updateError.message}`);
1757
+ }
1758
+ return bind;
1759
+ }
1760
+ };
1667
1761
  // 安全地替换命令模板
1668
1762
  const safeCommandReplace = (template, mcid) => {
1669
1763
  // 过滤可能导致命令注入的字符
@@ -1758,6 +1852,7 @@ function apply(ctx, config) {
1758
1852
  deleteMcBind,
1759
1853
  checkUsernameExists,
1760
1854
  checkAndUpdateUsername,
1855
+ checkAndUpdateUsernameWithCache,
1761
1856
  // API operations
1762
1857
  validateUsername,
1763
1858
  validateBUID,
@@ -2172,7 +2267,7 @@ function apply(ctx, config) {
2172
2267
  }
2173
2268
  }
2174
2269
  // 检查用户名是否已被其他人绑定
2175
- if (await checkUsernameExists(username, session.userId)) {
2270
+ if (await checkUsernameExists(username, session.userId, uuid)) {
2176
2271
  logger.warn(`[交互绑定] MC用户名"${username}"已被其他用户绑定`);
2177
2272
  await sendMessage(session, [koishi_1.h.text(`❌ 用户名 ${username} 已被其他用户绑定\n\n请输入其他MC用户名或发送"跳过"完成绑定`)]);
2178
2273
  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.3",
5
5
  "main": "lib/index.js",
6
6
  "typings": "lib/index.d.ts",
7
7
  "files": [