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.
- package/lib/handlers/base.handler.d.ts +2 -1
- package/lib/handlers/mcid.handler.js +12 -11
- package/lib/handlers/whitelist.handler.js +6 -2
- package/lib/index.js +102 -7
- package/lib/repositories/mcidbind.repository.d.ts +13 -1
- package/lib/repositories/mcidbind.repository.js +50 -1
- package/lib/types/database.d.ts +2 -0
- package/lib/utils/helpers.d.ts +18 -0
- package/lib/utils/helpers.js +41 -0
- package/package.json +1 -1
|
@@ -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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
package/lib/types/database.d.ts
CHANGED
package/lib/utils/helpers.d.ts
CHANGED
|
@@ -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;
|
package/lib/utils/helpers.js
CHANGED
|
@@ -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
|
+
}
|