koishi-plugin-bind-bot 2.0.4 → 2.1.0
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 +12 -18
- package/lib/handlers/binding.handler.js +3 -3
- package/lib/handlers/buid.handler.js +4 -4
- package/lib/handlers/index.d.ts +1 -0
- package/lib/handlers/index.js +1 -0
- package/lib/handlers/lottery.handler.d.ts +42 -0
- package/lib/handlers/lottery.handler.js +225 -0
- package/lib/handlers/mcid.handler.js +56 -56
- package/lib/handlers/tag.handler.js +8 -8
- package/lib/handlers/whitelist.handler.js +14 -14
- package/lib/index.js +73 -1061
- package/lib/services/api.service.d.ts +70 -0
- package/lib/services/api.service.js +344 -0
- package/lib/services/database.service.d.ts +64 -0
- package/lib/services/database.service.js +451 -0
- package/lib/services/index.d.ts +6 -0
- package/lib/services/index.js +22 -0
- package/lib/services/nickname.service.d.ts +42 -0
- package/lib/services/nickname.service.js +225 -0
- package/lib/services/service-container.d.ts +17 -0
- package/lib/services/service-container.js +24 -0
- package/lib/types/config.d.ts +2 -0
- package/lib/types/index.d.ts +1 -0
- package/lib/types/index.js +2 -0
- package/lib/types/update-data.d.ts +68 -0
- package/lib/types/update-data.js +5 -0
- package/lib/utils/helpers.d.ts +13 -0
- package/lib/utils/helpers.js +21 -0
- package/lib/utils/message-utils.d.ts +7 -2
- package/lib/utils/message-utils.js +117 -12
- package/package.json +1 -1
|
@@ -0,0 +1,451 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DatabaseService = void 0;
|
|
4
|
+
const helpers_1 = require("../utils/helpers");
|
|
5
|
+
/**
|
|
6
|
+
* 数据库服务层
|
|
7
|
+
* 统一管理数据库操作,包括 MC 绑定和 BUID 绑定的 CRUD
|
|
8
|
+
*/
|
|
9
|
+
class DatabaseService {
|
|
10
|
+
ctx;
|
|
11
|
+
logger;
|
|
12
|
+
mcidbindRepo;
|
|
13
|
+
normalizeQQId;
|
|
14
|
+
getUsernameByUuid;
|
|
15
|
+
constructor(ctx, logger, mcidbindRepo, normalizeQQId, getUsernameByUuid) {
|
|
16
|
+
this.ctx = ctx;
|
|
17
|
+
this.logger = logger;
|
|
18
|
+
this.mcidbindRepo = mcidbindRepo;
|
|
19
|
+
this.normalizeQQId = normalizeQQId;
|
|
20
|
+
this.getUsernameByUuid = getUsernameByUuid;
|
|
21
|
+
}
|
|
22
|
+
// =========== MC 绑定相关 ===========
|
|
23
|
+
/**
|
|
24
|
+
* 根据 QQ 号查询 MC 绑定信息
|
|
25
|
+
*/
|
|
26
|
+
async getMcBindByQQId(qqId) {
|
|
27
|
+
try {
|
|
28
|
+
// 处理空值
|
|
29
|
+
if (!qqId) {
|
|
30
|
+
this.logger.warn('MCIDBIND', `尝试查询空QQ号`);
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
const normalizedQQId = this.normalizeQQId(qqId);
|
|
34
|
+
// 查询MCIDBIND表中对应QQ号的绑定记录
|
|
35
|
+
return await this.mcidbindRepo.findByQQId(normalizedQQId);
|
|
36
|
+
}
|
|
37
|
+
catch (error) {
|
|
38
|
+
this.logger.error('MCIDBIND', `根据QQ号查询绑定信息失败: ${error.message}`);
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* 根据 MC 用户名查询绑定信息
|
|
44
|
+
*/
|
|
45
|
+
async getMcBindByUsername(mcUsername) {
|
|
46
|
+
// 处理空值
|
|
47
|
+
if (!mcUsername) {
|
|
48
|
+
this.logger.warn('MCIDBIND', `尝试查询空MC用户名`);
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
// 使用 Repository 查询
|
|
52
|
+
return await this.mcidbindRepo.findByMCUsername(mcUsername);
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* 创建或更新 MC 绑定
|
|
56
|
+
*/
|
|
57
|
+
async createOrUpdateMcBind(userId, mcUsername, mcUuid, isAdmin) {
|
|
58
|
+
try {
|
|
59
|
+
// 验证输入参数
|
|
60
|
+
if (!userId) {
|
|
61
|
+
this.logger.error('MCIDBIND', `创建/更新绑定失败: 无效的用户ID`);
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
if (!mcUsername) {
|
|
65
|
+
this.logger.error('MCIDBIND', `创建/更新绑定失败: 无效的MC用户名`);
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
const normalizedQQId = this.normalizeQQId(userId);
|
|
69
|
+
if (!normalizedQQId) {
|
|
70
|
+
this.logger.error('MCIDBIND', `创建/更新绑定失败: 无法提取有效的QQ号`);
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
// 查询是否已存在绑定记录
|
|
74
|
+
let bind = await this.getMcBindByQQId(normalizedQQId);
|
|
75
|
+
if (bind) {
|
|
76
|
+
// 更新现有记录,但保留管理员状态
|
|
77
|
+
const updateData = {
|
|
78
|
+
mcUsername,
|
|
79
|
+
mcUuid,
|
|
80
|
+
lastModified: new Date()
|
|
81
|
+
};
|
|
82
|
+
// 仅当指定了isAdmin参数时更新管理员状态
|
|
83
|
+
if (typeof isAdmin !== 'undefined') {
|
|
84
|
+
updateData.isAdmin = isAdmin;
|
|
85
|
+
}
|
|
86
|
+
await this.mcidbindRepo.update(normalizedQQId, updateData);
|
|
87
|
+
this.logger.info('MCIDBIND', `更新绑定: QQ=${normalizedQQId}, MC用户名=${mcUsername}, UUID=${mcUuid}`, true);
|
|
88
|
+
return true;
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
// 创建新记录
|
|
92
|
+
try {
|
|
93
|
+
await this.mcidbindRepo.create({
|
|
94
|
+
qqId: normalizedQQId,
|
|
95
|
+
mcUsername,
|
|
96
|
+
mcUuid,
|
|
97
|
+
lastModified: new Date(),
|
|
98
|
+
isAdmin: isAdmin || false
|
|
99
|
+
});
|
|
100
|
+
this.logger.info('MCIDBIND', `创建绑定: QQ=${normalizedQQId}, MC用户名=${mcUsername}, UUID=${mcUuid}`, true);
|
|
101
|
+
return true;
|
|
102
|
+
}
|
|
103
|
+
catch (createError) {
|
|
104
|
+
this.logger.error('MCIDBIND', `创建绑定失败: MC用户名=${mcUsername}, 错误=${createError.message}`);
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
catch (error) {
|
|
110
|
+
this.logger.error('MCIDBIND', `创建/更新绑定失败: MC用户名=${mcUsername}, 错误=${error.message}`);
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* 删除 MC 绑定(同时解绑 MC 和 B 站账号)
|
|
116
|
+
*/
|
|
117
|
+
async deleteMcBind(userId) {
|
|
118
|
+
try {
|
|
119
|
+
// 验证输入参数
|
|
120
|
+
if (!userId) {
|
|
121
|
+
this.logger.error('MCIDBIND', `删除绑定失败: 无效的用户ID`);
|
|
122
|
+
return false;
|
|
123
|
+
}
|
|
124
|
+
const normalizedQQId = this.normalizeQQId(userId);
|
|
125
|
+
if (!normalizedQQId) {
|
|
126
|
+
this.logger.error('MCIDBIND', `删除绑定失败: 无法提取有效的QQ号`);
|
|
127
|
+
return false;
|
|
128
|
+
}
|
|
129
|
+
// 查询是否存在绑定记录
|
|
130
|
+
const bind = await this.getMcBindByQQId(normalizedQQId);
|
|
131
|
+
if (bind) {
|
|
132
|
+
// 删除整个绑定记录,包括MC和B站账号
|
|
133
|
+
const removedCount = await this.mcidbindRepo.delete(normalizedQQId);
|
|
134
|
+
// 检查是否真正删除成功
|
|
135
|
+
if (removedCount > 0) {
|
|
136
|
+
let logMessage = `删除绑定: QQ=${normalizedQQId}`;
|
|
137
|
+
if (bind.mcUsername)
|
|
138
|
+
logMessage += `, MC用户名=${bind.mcUsername}`;
|
|
139
|
+
if (bind.buidUid)
|
|
140
|
+
logMessage += `, B站UID=${bind.buidUid}(${bind.buidUsername})`;
|
|
141
|
+
this.logger.info('MCIDBIND', logMessage, true);
|
|
142
|
+
return true;
|
|
143
|
+
}
|
|
144
|
+
else {
|
|
145
|
+
this.logger.warn('MCIDBIND', `删除绑定异常: QQ=${normalizedQQId}, 可能未实际删除`);
|
|
146
|
+
return false;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
this.logger.warn('MCIDBIND', `删除绑定失败: QQ=${normalizedQQId}不存在绑定记录`);
|
|
150
|
+
return false;
|
|
151
|
+
}
|
|
152
|
+
catch (error) {
|
|
153
|
+
this.logger.error('MCIDBIND', `删除绑定失败: 错误=${error.message}`);
|
|
154
|
+
return false;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* 检查 MC 用户名是否已被其他 QQ 号绑定(支持不区分大小写和 UUID 检查)
|
|
159
|
+
*/
|
|
160
|
+
async checkUsernameExists(username, currentUserId, uuid) {
|
|
161
|
+
try {
|
|
162
|
+
// 验证输入参数
|
|
163
|
+
if (!username) {
|
|
164
|
+
this.logger.warn('绑定检查', `尝试检查空MC用户名`);
|
|
165
|
+
return false;
|
|
166
|
+
}
|
|
167
|
+
// 跳过临时用户名的检查
|
|
168
|
+
if (username.startsWith('_temp_')) {
|
|
169
|
+
return false;
|
|
170
|
+
}
|
|
171
|
+
// 使用不区分大小写的查询
|
|
172
|
+
const bind = await this.mcidbindRepo.findByUsernameIgnoreCase(username);
|
|
173
|
+
// 如果没有绑定,返回false
|
|
174
|
+
if (!bind)
|
|
175
|
+
return false;
|
|
176
|
+
// 如果绑定的用户名是临时用户名,视为未绑定
|
|
177
|
+
if (bind.mcUsername && bind.mcUsername.startsWith('_temp_')) {
|
|
178
|
+
return false;
|
|
179
|
+
}
|
|
180
|
+
// 如果提供了 UUID,检查是否为同一个 MC 账号(用户改名场景)
|
|
181
|
+
if (uuid && bind.mcUuid) {
|
|
182
|
+
const cleanUuid = uuid.replace(/-/g, '');
|
|
183
|
+
const bindCleanUuid = bind.mcUuid.replace(/-/g, '');
|
|
184
|
+
if (cleanUuid === bindCleanUuid) {
|
|
185
|
+
// 同一个 UUID,说明是用户改名,允许绑定
|
|
186
|
+
this.logger.info('绑定检查', `检测到MC账号改名: UUID=${uuid}, 旧用户名=${bind.mcUsername}, 新用户名=${username}`, true);
|
|
187
|
+
return false;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
// 如果提供了当前用户ID,需要排除当前用户
|
|
191
|
+
if (currentUserId) {
|
|
192
|
+
const normalizedCurrentId = this.normalizeQQId(currentUserId);
|
|
193
|
+
// 如果绑定的用户就是当前用户,返回false,表示没有被其他用户绑定
|
|
194
|
+
return normalizedCurrentId ? bind.qqId !== normalizedCurrentId : true;
|
|
195
|
+
}
|
|
196
|
+
return true;
|
|
197
|
+
}
|
|
198
|
+
catch (error) {
|
|
199
|
+
this.logger.error('绑定检查', `检查用户名"${username}"是否已被绑定失败: ${error.message}`);
|
|
200
|
+
return false;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
// =========== BUID 绑定相关 ===========
|
|
204
|
+
/**
|
|
205
|
+
* 根据 B 站 UID 查询绑定信息
|
|
206
|
+
*/
|
|
207
|
+
async getBuidBindByBuid(buid) {
|
|
208
|
+
try {
|
|
209
|
+
if (!buid) {
|
|
210
|
+
this.logger.warn('B站账号绑定', `尝试查询空B站UID`);
|
|
211
|
+
return null;
|
|
212
|
+
}
|
|
213
|
+
const bind = await this.mcidbindRepo.findByBuidUid(buid);
|
|
214
|
+
return bind;
|
|
215
|
+
}
|
|
216
|
+
catch (error) {
|
|
217
|
+
this.logger.error('B站账号绑定', `根据B站UID(${buid})查询绑定信息失败: ${error.message}`);
|
|
218
|
+
return null;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* 检查 B 站 UID 是否已被绑定
|
|
223
|
+
*/
|
|
224
|
+
async checkBuidExists(buid, currentUserId) {
|
|
225
|
+
try {
|
|
226
|
+
const bind = await this.getBuidBindByBuid(buid);
|
|
227
|
+
if (!bind)
|
|
228
|
+
return false;
|
|
229
|
+
// 如果指定了当前用户ID,则排除当前用户的绑定
|
|
230
|
+
if (currentUserId) {
|
|
231
|
+
const normalizedCurrentId = this.normalizeQQId(currentUserId);
|
|
232
|
+
return bind.qqId !== normalizedCurrentId;
|
|
233
|
+
}
|
|
234
|
+
return true;
|
|
235
|
+
}
|
|
236
|
+
catch (error) {
|
|
237
|
+
this.logger.error('B站账号绑定', `检查B站UID(${buid})是否存在时出错: ${error.message}`);
|
|
238
|
+
return false;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* 创建或更新 B 站账号绑定
|
|
243
|
+
*/
|
|
244
|
+
async createOrUpdateBuidBind(userId, buidUser) {
|
|
245
|
+
try {
|
|
246
|
+
const normalizedQQId = this.normalizeQQId(userId);
|
|
247
|
+
if (!normalizedQQId) {
|
|
248
|
+
this.logger.error('B站账号绑定', `创建/更新绑定失败: 无法提取有效的QQ号`);
|
|
249
|
+
return false;
|
|
250
|
+
}
|
|
251
|
+
// 检查该UID是否已被其他用户绑定(安全检查)
|
|
252
|
+
const existingBuidBind = await this.getBuidBindByBuid(buidUser.uid);
|
|
253
|
+
if (existingBuidBind && existingBuidBind.qqId !== normalizedQQId) {
|
|
254
|
+
this.logger.error('B站账号绑定', `安全检查失败: B站UID ${buidUser.uid} 已被QQ(${existingBuidBind.qqId})绑定,无法为QQ(${normalizedQQId})绑定`);
|
|
255
|
+
return false;
|
|
256
|
+
}
|
|
257
|
+
// 查询是否已存在绑定记录
|
|
258
|
+
let bind = await this.getMcBindByQQId(normalizedQQId);
|
|
259
|
+
const updateData = {
|
|
260
|
+
buidUid: buidUser.uid.toString(), // 转换为字符串存储
|
|
261
|
+
buidUsername: buidUser.username,
|
|
262
|
+
guardLevel: buidUser.guard_level || 0,
|
|
263
|
+
guardLevelText: buidUser.guard_level_text || '',
|
|
264
|
+
maxGuardLevel: buidUser.max_guard_level || 0,
|
|
265
|
+
maxGuardLevelText: buidUser.max_guard_level_text || '',
|
|
266
|
+
medalName: buidUser.medal?.name || '',
|
|
267
|
+
medalLevel: buidUser.medal?.level || 0,
|
|
268
|
+
wealthMedalLevel: buidUser.wealthMedalLevel || 0,
|
|
269
|
+
lastActiveTime: buidUser.last_active_time ? new Date(buidUser.last_active_time) : new Date(),
|
|
270
|
+
lastModified: new Date()
|
|
271
|
+
};
|
|
272
|
+
if (bind) {
|
|
273
|
+
await this.mcidbindRepo.update(normalizedQQId, updateData);
|
|
274
|
+
this.logger.info('B站账号绑定', `更新绑定: QQ=${normalizedQQId}, B站UID=${buidUser.uid}, 用户名=${buidUser.username}`, true);
|
|
275
|
+
}
|
|
276
|
+
else {
|
|
277
|
+
// 为跳过MC绑定的用户生成唯一的临时用户名,避免UNIQUE constraint冲突
|
|
278
|
+
const tempMcUsername = `_temp_skip_${normalizedQQId}_${Date.now()}`;
|
|
279
|
+
const newBind = {
|
|
280
|
+
qqId: normalizedQQId,
|
|
281
|
+
mcUsername: tempMcUsername,
|
|
282
|
+
mcUuid: '',
|
|
283
|
+
isAdmin: false,
|
|
284
|
+
whitelist: [],
|
|
285
|
+
tags: [],
|
|
286
|
+
...updateData
|
|
287
|
+
};
|
|
288
|
+
await this.mcidbindRepo.create(newBind);
|
|
289
|
+
this.logger.info('B站账号绑定', `创建绑定(跳过MC): QQ=${normalizedQQId}, B站UID=${buidUser.uid}, 用户名=${buidUser.username}, 临时MC用户名=${tempMcUsername}`, true);
|
|
290
|
+
}
|
|
291
|
+
return true;
|
|
292
|
+
}
|
|
293
|
+
catch (error) {
|
|
294
|
+
this.logger.error('B站账号绑定', `创建/更新B站账号绑定失败: ${error.message}`);
|
|
295
|
+
return false;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
/**
|
|
299
|
+
* 仅更新 B 站信息,不更新绑定时间(用于查询时刷新数据)
|
|
300
|
+
*/
|
|
301
|
+
async updateBuidInfoOnly(userId, buidUser) {
|
|
302
|
+
try {
|
|
303
|
+
const normalizedQQId = this.normalizeQQId(userId);
|
|
304
|
+
if (!normalizedQQId) {
|
|
305
|
+
this.logger.error('B站账号信息更新', `更新失败: 无法提取有效的QQ号`);
|
|
306
|
+
return false;
|
|
307
|
+
}
|
|
308
|
+
// 查询是否已存在绑定记录
|
|
309
|
+
const bind = await this.getMcBindByQQId(normalizedQQId);
|
|
310
|
+
if (!bind) {
|
|
311
|
+
this.logger.warn('B站账号信息更新', `QQ(${normalizedQQId})没有绑定记录,无法更新B站信息`);
|
|
312
|
+
return false;
|
|
313
|
+
}
|
|
314
|
+
// 仅更新B站相关字段,不更新lastModified
|
|
315
|
+
const updateData = {
|
|
316
|
+
buidUsername: buidUser.username,
|
|
317
|
+
guardLevel: buidUser.guard_level || 0,
|
|
318
|
+
guardLevelText: buidUser.guard_level_text || '',
|
|
319
|
+
maxGuardLevel: buidUser.max_guard_level || 0,
|
|
320
|
+
maxGuardLevelText: buidUser.max_guard_level_text || '',
|
|
321
|
+
medalName: buidUser.medal?.name || '',
|
|
322
|
+
medalLevel: buidUser.medal?.level || 0,
|
|
323
|
+
wealthMedalLevel: buidUser.wealthMedalLevel || 0,
|
|
324
|
+
lastActiveTime: buidUser.last_active_time ? new Date(buidUser.last_active_time) : new Date()
|
|
325
|
+
};
|
|
326
|
+
await this.mcidbindRepo.update(normalizedQQId, updateData);
|
|
327
|
+
this.logger.info('B站账号信息更新', `刷新信息: QQ=${normalizedQQId}, B站UID=${bind.buidUid}, 用户名=${buidUser.username}`, true);
|
|
328
|
+
return true;
|
|
329
|
+
}
|
|
330
|
+
catch (error) {
|
|
331
|
+
this.logger.error('B站账号信息更新', `更新B站账号信息失败: ${error.message}`);
|
|
332
|
+
return false;
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
// =========== 用户名更新检查 ===========
|
|
336
|
+
/**
|
|
337
|
+
* 检查并更新用户名(如果与当前数据库中的不同)
|
|
338
|
+
*/
|
|
339
|
+
async checkAndUpdateUsername(bind) {
|
|
340
|
+
try {
|
|
341
|
+
if (!bind || !bind.mcUuid) {
|
|
342
|
+
this.logger.warn('用户名更新', `无法检查用户名更新: 空绑定或空UUID`);
|
|
343
|
+
return bind;
|
|
344
|
+
}
|
|
345
|
+
// 通过UUID查询最新用户名
|
|
346
|
+
const latestUsername = await this.getUsernameByUuid(bind.mcUuid);
|
|
347
|
+
if (!latestUsername) {
|
|
348
|
+
this.logger.warn('用户名更新', `无法获取UUID "${bind.mcUuid}" 的最新用户名`);
|
|
349
|
+
return bind;
|
|
350
|
+
}
|
|
351
|
+
// 如果用户名与数据库中的不同,更新数据库(使用规范化比较,不区分大小写)
|
|
352
|
+
const normalizedLatest = (0, helpers_1.normalizeUsername)(latestUsername);
|
|
353
|
+
const normalizedCurrent = (0, helpers_1.normalizeUsername)(bind.mcUsername);
|
|
354
|
+
if (normalizedLatest !== normalizedCurrent) {
|
|
355
|
+
this.logger.info('用户名更新', `用户 QQ(${bind.qqId}) 的Minecraft用户名已变更: ${bind.mcUsername} -> ${latestUsername}`, true);
|
|
356
|
+
// 更新数据库中的用户名
|
|
357
|
+
await this.mcidbindRepo.update(bind.qqId, {
|
|
358
|
+
mcUsername: latestUsername
|
|
359
|
+
});
|
|
360
|
+
// 更新返回的绑定对象
|
|
361
|
+
bind.mcUsername = latestUsername;
|
|
362
|
+
}
|
|
363
|
+
return bind;
|
|
364
|
+
}
|
|
365
|
+
catch (error) {
|
|
366
|
+
this.logger.error('用户名更新', `检查和更新用户名失败: ${error.message}`);
|
|
367
|
+
return bind;
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
/**
|
|
371
|
+
* 智能缓存版本的改名检测函数
|
|
372
|
+
* 特性:
|
|
373
|
+
* - 24小时冷却期(失败>=3次时延长到72小时)
|
|
374
|
+
* - 失败计数追踪
|
|
375
|
+
* - 成功时重置失败计数
|
|
376
|
+
*/
|
|
377
|
+
async checkAndUpdateUsernameWithCache(bind) {
|
|
378
|
+
try {
|
|
379
|
+
if (!bind || !bind.mcUuid) {
|
|
380
|
+
this.logger.warn('改名检测缓存', `无法检查用户名更新: 空绑定或空UUID`);
|
|
381
|
+
return bind;
|
|
382
|
+
}
|
|
383
|
+
const now = new Date();
|
|
384
|
+
const failCount = bind.usernameCheckFailCount || 0;
|
|
385
|
+
// 根据失败次数决定冷却期:普通24小时,失败>=3次则72小时
|
|
386
|
+
const cooldownHours = failCount >= 3 ? 72 : 24;
|
|
387
|
+
// 检查是否在冷却期内
|
|
388
|
+
if (bind.usernameLastChecked) {
|
|
389
|
+
const lastCheck = new Date(bind.usernameLastChecked);
|
|
390
|
+
const hoursSinceCheck = (now.getTime() - lastCheck.getTime()) / (1000 * 60 * 60);
|
|
391
|
+
if (hoursSinceCheck < cooldownHours) {
|
|
392
|
+
this.logger.debug('改名检测缓存', `QQ(${bind.qqId}) 在冷却期内(${hoursSinceCheck.toFixed(1)}h/${cooldownHours}h),跳过检查`);
|
|
393
|
+
return bind;
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
this.logger.debug('改名检测缓存', `QQ(${bind.qqId}) 开始检查用户名变更(失败计数: ${failCount})`);
|
|
397
|
+
// 执行实际的改名检测
|
|
398
|
+
const oldUsername = bind.mcUsername;
|
|
399
|
+
const updatedBind = await this.checkAndUpdateUsername(bind);
|
|
400
|
+
// 判断检测是否成功
|
|
401
|
+
const detectionSuccess = updatedBind.mcUsername !== null && updatedBind.mcUsername !== undefined;
|
|
402
|
+
if (detectionSuccess) {
|
|
403
|
+
// 检测成功
|
|
404
|
+
const usernameChanged = (0, helpers_1.normalizeUsername)(updatedBind.mcUsername) !== (0, helpers_1.normalizeUsername)(oldUsername);
|
|
405
|
+
// 更新检查时间和重置失败计数
|
|
406
|
+
await this.mcidbindRepo.update(bind.qqId, {
|
|
407
|
+
usernameLastChecked: now,
|
|
408
|
+
usernameCheckFailCount: 0
|
|
409
|
+
});
|
|
410
|
+
if (usernameChanged) {
|
|
411
|
+
this.logger.info('改名检测缓存', `QQ(${bind.qqId}) 用户名已变更: ${oldUsername} -> ${updatedBind.mcUsername}`, true);
|
|
412
|
+
}
|
|
413
|
+
else {
|
|
414
|
+
this.logger.debug('改名检测缓存', `QQ(${bind.qqId}) 用户名无变更: ${updatedBind.mcUsername}`);
|
|
415
|
+
}
|
|
416
|
+
// 更新返回对象的缓存字段
|
|
417
|
+
updatedBind.usernameLastChecked = now;
|
|
418
|
+
updatedBind.usernameCheckFailCount = 0;
|
|
419
|
+
}
|
|
420
|
+
else {
|
|
421
|
+
// 检测失败(API失败或返回null)
|
|
422
|
+
const newFailCount = failCount + 1;
|
|
423
|
+
await this.mcidbindRepo.update(bind.qqId, {
|
|
424
|
+
usernameLastChecked: now,
|
|
425
|
+
usernameCheckFailCount: newFailCount
|
|
426
|
+
});
|
|
427
|
+
this.logger.warn('改名检测缓存', `QQ(${bind.qqId}) 检测失败,失败计数: ${newFailCount}`);
|
|
428
|
+
// 更新返回对象的缓存字段
|
|
429
|
+
updatedBind.usernameLastChecked = now;
|
|
430
|
+
updatedBind.usernameCheckFailCount = newFailCount;
|
|
431
|
+
}
|
|
432
|
+
return updatedBind;
|
|
433
|
+
}
|
|
434
|
+
catch (error) {
|
|
435
|
+
this.logger.error('改名检测缓存', `检查和更新用户名失败: ${error.message}`);
|
|
436
|
+
// 失败时也更新检查时间和递增失败计数
|
|
437
|
+
try {
|
|
438
|
+
const failCount = bind.usernameCheckFailCount || 0;
|
|
439
|
+
await this.mcidbindRepo.update(bind.qqId, {
|
|
440
|
+
usernameLastChecked: new Date(),
|
|
441
|
+
usernameCheckFailCount: failCount + 1
|
|
442
|
+
});
|
|
443
|
+
}
|
|
444
|
+
catch (updateError) {
|
|
445
|
+
this.logger.error('改名检测缓存', `更新失败计数时出错: ${updateError.message}`);
|
|
446
|
+
}
|
|
447
|
+
return bind;
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
exports.DatabaseService = DatabaseService;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
/**
|
|
18
|
+
* 服务层统一导出
|
|
19
|
+
*/
|
|
20
|
+
__exportStar(require("./api.service"), exports);
|
|
21
|
+
__exportStar(require("./database.service"), exports);
|
|
22
|
+
__exportStar(require("./nickname.service"), exports);
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { Session } from 'koishi';
|
|
2
|
+
import { LoggerService } from '../utils/logger';
|
|
3
|
+
import type { ZminfoUser } from '../types';
|
|
4
|
+
/**
|
|
5
|
+
* 群昵称管理服务
|
|
6
|
+
* 负责自动设置和验证群昵称
|
|
7
|
+
*/
|
|
8
|
+
export declare class NicknameService {
|
|
9
|
+
private logger;
|
|
10
|
+
private config;
|
|
11
|
+
private normalizeQQId;
|
|
12
|
+
private validateBUID;
|
|
13
|
+
private getBilibiliOfficialUserInfo;
|
|
14
|
+
private updateBuidInfoOnly;
|
|
15
|
+
constructor(logger: LoggerService, config: {
|
|
16
|
+
autoNicknameGroupId: string;
|
|
17
|
+
}, normalizeQQId: (userId: string) => string, validateBUID: (buid: string) => Promise<ZminfoUser | null>, getBilibiliOfficialUserInfo: (uid: string) => Promise<{
|
|
18
|
+
name: string;
|
|
19
|
+
mid: number;
|
|
20
|
+
} | null>, updateBuidInfoOnly: (userId: string, buidUser: ZminfoUser) => Promise<boolean>);
|
|
21
|
+
/**
|
|
22
|
+
* 检查群昵称格式是否正确
|
|
23
|
+
*/
|
|
24
|
+
checkNicknameFormat(nickname: string, buidUsername: string, mcUsername: string | null): boolean;
|
|
25
|
+
/**
|
|
26
|
+
* 使用四层判断逻辑获取最准确的B站用户名
|
|
27
|
+
* 优先级:官方API > ZMINFO > 数据库
|
|
28
|
+
*/
|
|
29
|
+
private getLatestBuidUsername;
|
|
30
|
+
/**
|
|
31
|
+
* 同步数据库中的B站用户信息
|
|
32
|
+
*/
|
|
33
|
+
private syncDatabaseIfNeeded;
|
|
34
|
+
/**
|
|
35
|
+
* 设置群昵称并验证
|
|
36
|
+
*/
|
|
37
|
+
private setAndVerifyNickname;
|
|
38
|
+
/**
|
|
39
|
+
* 自动群昵称设置功能(重构版)
|
|
40
|
+
*/
|
|
41
|
+
autoSetGroupNickname(session: Session, mcUsername: string | null, buidUsername: string, buidUid?: string, targetUserId?: string, specifiedGroupId?: string): Promise<void>;
|
|
42
|
+
}
|