koishi-plugin-bind-bot 2.0.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.
Files changed (48) hide show
  1. package/lib/export-utils.d.ts +49 -0
  2. package/lib/export-utils.js +305 -0
  3. package/lib/force-bind-utils.d.ts +40 -0
  4. package/lib/force-bind-utils.js +242 -0
  5. package/lib/handlers/base.handler.d.ts +61 -0
  6. package/lib/handlers/base.handler.js +22 -0
  7. package/lib/handlers/binding.handler.d.ts +45 -0
  8. package/lib/handlers/binding.handler.js +285 -0
  9. package/lib/handlers/buid.handler.d.ts +71 -0
  10. package/lib/handlers/buid.handler.js +694 -0
  11. package/lib/handlers/index.d.ts +6 -0
  12. package/lib/handlers/index.js +22 -0
  13. package/lib/handlers/mcid.handler.d.ts +101 -0
  14. package/lib/handlers/mcid.handler.js +1045 -0
  15. package/lib/handlers/tag.handler.d.ts +14 -0
  16. package/lib/handlers/tag.handler.js +382 -0
  17. package/lib/handlers/whitelist.handler.d.ts +84 -0
  18. package/lib/handlers/whitelist.handler.js +1011 -0
  19. package/lib/index.d.ts +7 -0
  20. package/lib/index.js +2693 -0
  21. package/lib/managers/rcon-manager.d.ts +24 -0
  22. package/lib/managers/rcon-manager.js +308 -0
  23. package/lib/repositories/mcidbind.repository.d.ts +105 -0
  24. package/lib/repositories/mcidbind.repository.js +288 -0
  25. package/lib/repositories/schedule-mute.repository.d.ts +68 -0
  26. package/lib/repositories/schedule-mute.repository.js +175 -0
  27. package/lib/types/api.d.ts +135 -0
  28. package/lib/types/api.js +6 -0
  29. package/lib/types/common.d.ts +40 -0
  30. package/lib/types/common.js +6 -0
  31. package/lib/types/config.d.ts +55 -0
  32. package/lib/types/config.js +6 -0
  33. package/lib/types/database.d.ts +47 -0
  34. package/lib/types/database.js +6 -0
  35. package/lib/types/index.d.ts +8 -0
  36. package/lib/types/index.js +28 -0
  37. package/lib/utils/helpers.d.ts +76 -0
  38. package/lib/utils/helpers.js +275 -0
  39. package/lib/utils/logger.d.ts +75 -0
  40. package/lib/utils/logger.js +134 -0
  41. package/lib/utils/message-utils.d.ts +46 -0
  42. package/lib/utils/message-utils.js +234 -0
  43. package/lib/utils/rate-limiter.d.ts +26 -0
  44. package/lib/utils/rate-limiter.js +47 -0
  45. package/lib/utils/session-manager.d.ts +70 -0
  46. package/lib/utils/session-manager.js +120 -0
  47. package/package.json +39 -0
  48. package/readme.md +281 -0
@@ -0,0 +1,694 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.BuidHandler = void 0;
7
+ const koishi_1 = require("koishi");
8
+ const base_handler_1 = require("./base.handler");
9
+ const axios_1 = __importDefault(require("axios"));
10
+ /**
11
+ * BUID 命令处理器
12
+ * 处理 B站账号相关命令
13
+ */
14
+ class BuidHandler extends base_handler_1.BaseHandler {
15
+ /**
16
+ * 注册 BUID 相关命令
17
+ */
18
+ register() {
19
+ const buidCmd = this.ctx.command('buid', 'B站UID绑定管理');
20
+ // 查询BUID绑定
21
+ buidCmd.subcommand('.query [target:string]', '查询用户绑定的BUID')
22
+ .action(async ({ session }, target) => {
23
+ return this.handleQuery(session, target);
24
+ });
25
+ // 绑定BUID(支持强制模式)
26
+ buidCmd.subcommand('.bind <uid:string> [target:string]', '绑定B站UID')
27
+ .option('force', '-f', { fallback: false })
28
+ .action(async ({ session, options }, uid, target) => {
29
+ return this.handleBind(session, uid, target, !!options.force);
30
+ });
31
+ // 通过BUID查找用户
32
+ buidCmd.subcommand('.finduser <uid:string>', '[管理员]通过BUID查询绑定的QQ账号')
33
+ .action(async ({ session }, uid) => {
34
+ return this.handleFindUser(session, uid);
35
+ });
36
+ // mcid 命令组中的 BUID 相关子命令
37
+ const mcidCmd = this.ctx.command('mcid');
38
+ // 绑定B站账号(mcid.bindbuid)
39
+ mcidCmd.subcommand('.bindbuid <buid:string>', '绑定B站账号')
40
+ .action(async ({ session }, buid) => {
41
+ return this.handleBindBuid(session, buid);
42
+ });
43
+ // 解绑B站账号(mcid.unbindbuid)
44
+ mcidCmd.subcommand('.unbindbuid', '解绑B站账号')
45
+ .action(async ({ session }) => {
46
+ return this.handleUnbindBuid(session);
47
+ });
48
+ }
49
+ /**
50
+ * 处理 BUID 查询命令
51
+ */
52
+ async handleQuery(session, target) {
53
+ try {
54
+ const normalizedUserId = this.deps.normalizeQQId(session.userId);
55
+ let bind;
56
+ if (target) {
57
+ const normalizedTargetId = this.deps.normalizeQQId(target);
58
+ bind = await this.repos.mcidbind.findByQQId(normalizedTargetId);
59
+ }
60
+ else {
61
+ bind = await this.repos.mcidbind.findByQQId(normalizedUserId);
62
+ }
63
+ if (!bind || !bind.buidUid) {
64
+ return this.deps.sendMessage(session, [
65
+ koishi_1.h.text(target
66
+ ? `该用户尚未绑定B站账号`
67
+ : `您尚未绑定B站账号,请使用 ${this.deps.formatCommand('buid bind <UID>')} 进行绑定`)
68
+ ]);
69
+ }
70
+ // 每次查询都刷新B站数据
71
+ const buidUser = await this.validateBUID(bind.buidUid);
72
+ if (buidUser) {
73
+ await this.updateBuidInfoOnly(bind.qqId, buidUser);
74
+ bind = await this.repos.mcidbind.findByQQId(bind.qqId);
75
+ }
76
+ const userInfo = `${target ? `用户 ${bind.qqId} 的` : '您的'}B站账号信息:\nB站UID: ${bind.buidUid}\n用户名: ${bind.buidUsername}`;
77
+ let detailInfo = '';
78
+ if (bind.guardLevel > 0) {
79
+ detailInfo += `\n舰长等级: ${bind.guardLevelText} (${bind.guardLevel})`;
80
+ if (bind.maxGuardLevel > 0 && bind.maxGuardLevel < bind.guardLevel) {
81
+ detailInfo += `\n历史最高: ${bind.maxGuardLevelText} (${bind.maxGuardLevel})`;
82
+ }
83
+ }
84
+ else if (bind.maxGuardLevel > 0) {
85
+ detailInfo += `\n历史舰长: ${bind.maxGuardLevelText} (${bind.maxGuardLevel})`;
86
+ }
87
+ detailInfo += `\n粉丝牌: ${bind.medalName || '无'} Lv.${bind.medalLevel || 0}`;
88
+ detailInfo += `\n荣耀等级: ${bind.wealthMedalLevel || 0}`;
89
+ detailInfo += `\n最后活跃: ${bind.lastActiveTime ? new Date(bind.lastActiveTime).toLocaleString('zh-CN', { timeZone: 'Asia/Shanghai' }) : '未知'}`;
90
+ const messageContent = [koishi_1.h.text(userInfo + detailInfo)];
91
+ if (this.config?.showAvatar && bind.buidUid) {
92
+ messageContent.push(koishi_1.h.image(`https://workers.vrp.moe/bilibili/avatar/${bind.buidUid}?size=160`));
93
+ }
94
+ return this.deps.sendMessage(session, messageContent);
95
+ }
96
+ catch (error) {
97
+ return this.deps.sendMessage(session, [koishi_1.h.text(`查询失败: ${error.message}`)]);
98
+ }
99
+ }
100
+ /**
101
+ * 处理 BUID 绑定命令
102
+ */
103
+ async handleBind(session, uid, target, isForceMode = false) {
104
+ try {
105
+ const normalizedUserId = this.deps.normalizeQQId(session.userId);
106
+ // 解析UID格式
107
+ const actualUid = this.parseUidInput(uid, normalizedUserId);
108
+ // 检查UID格式
109
+ if (!actualUid || !/^\d+$/.test(actualUid)) {
110
+ this.logger.warn('BUID绑定', `QQ(${normalizedUserId})提供的UID"${uid}"格式无效`);
111
+ return this.deps.sendMessage(session, [
112
+ koishi_1.h.text('请提供有效的B站UID(支持以下格式):\n• 纯数字:123456789\n• UID格式:UID:123456789\n• 空间链接:https://space.bilibili.com/123456789')
113
+ ]);
114
+ }
115
+ // 强制绑定模式
116
+ if (isForceMode) {
117
+ return this.handleForceBindMode(session, actualUid, target, normalizedUserId);
118
+ }
119
+ // 管理员为他人绑定
120
+ if (target) {
121
+ return this.handleAdminBindForOthers(session, actualUid, target, normalizedUserId);
122
+ }
123
+ // 为自己绑定
124
+ return this.handleSelfBind(session, actualUid, normalizedUserId);
125
+ }
126
+ catch (error) {
127
+ this.logger.error('绑定', session.userId, error);
128
+ return this.deps.sendMessage(session, [koishi_1.h.text(`绑定失败:${this.getFriendlyErrorMessage(error)}`)]);
129
+ }
130
+ }
131
+ /**
132
+ * 处理查找用户命令(通过BUID反查QQ)
133
+ */
134
+ async handleFindUser(session, uid) {
135
+ try {
136
+ const normalizedUserId = this.deps.normalizeQQId(session.userId);
137
+ // 检查管理员权限
138
+ const isAdmin = await this.checkIsAdmin(session.userId);
139
+ if (!isAdmin) {
140
+ this.logger.warn('B站账号反向查询', `权限不足: QQ(${normalizedUserId})不是管理员`);
141
+ return this.deps.sendMessage(session, [koishi_1.h.text('只有管理员才能使用此命令')]);
142
+ }
143
+ // 解析UID
144
+ const actualUid = this.parseUidInput(uid, normalizedUserId);
145
+ if (!actualUid || !/^\d+$/.test(actualUid)) {
146
+ this.logger.warn('B站账号反向查询', `QQ(${normalizedUserId})提供的UID"${uid}"格式无效`);
147
+ return this.deps.sendMessage(session, [
148
+ koishi_1.h.text('请提供有效的B站UID(支持以下格式):\n• 纯数字:123456789\n• UID格式:UID:123456789\n• 空间链接:https://space.bilibili.com/123456789')
149
+ ]);
150
+ }
151
+ this.logger.info('B站账号反向查询', `QQ(${normalizedUserId})尝试通过B站UID"${actualUid}"查询绑定的QQ账号`);
152
+ const bind = await this.repos.mcidbind.findByBuidUid(actualUid);
153
+ if (!bind || !bind.qqId) {
154
+ this.logger.info('B站账号反向查询', `B站UID"${actualUid}"未被任何QQ账号绑定`);
155
+ return this.deps.sendMessage(session, [koishi_1.h.text(`未找到绑定B站UID"${actualUid}"的QQ账号`)]);
156
+ }
157
+ // 构建详细信息
158
+ let adminInfo = `B站UID"${bind.buidUid}"绑定信息:\nQQ号: ${bind.qqId}\n用户名: ${bind.buidUsername}`;
159
+ if (bind.guardLevel > 0) {
160
+ adminInfo += `\n舰长等级: ${bind.guardLevelText} (${bind.guardLevel})`;
161
+ if (bind.maxGuardLevel > 0 && bind.maxGuardLevel < bind.guardLevel) {
162
+ adminInfo += `\n历史最高: ${bind.maxGuardLevelText} (${bind.maxGuardLevel})`;
163
+ }
164
+ }
165
+ else if (bind.maxGuardLevel > 0) {
166
+ adminInfo += `\n历史舰长: ${bind.maxGuardLevelText} (${bind.maxGuardLevel})`;
167
+ }
168
+ if (bind.medalName) {
169
+ adminInfo += `\n粉丝牌: ${bind.medalName} Lv.${bind.medalLevel}`;
170
+ }
171
+ if (bind.wealthMedalLevel > 0) {
172
+ adminInfo += `\n荣耀等级: ${bind.wealthMedalLevel}`;
173
+ }
174
+ if (bind.lastActiveTime) {
175
+ adminInfo += `\n最后活跃: ${new Date(bind.lastActiveTime).toLocaleString()}`;
176
+ }
177
+ adminInfo += `\n绑定时间: ${bind.lastModified ? new Date(bind.lastModified).toLocaleString() : '未知'}`;
178
+ adminInfo += `\n管理员权限: ${bind.isAdmin ? '是' : '否'}`;
179
+ this.logger.info('B站账号反向查询', `成功: B站UID"${actualUid}"被QQ(${bind.qqId})绑定`);
180
+ return this.deps.sendMessage(session, [koishi_1.h.text(adminInfo)]);
181
+ }
182
+ catch (error) {
183
+ const normalizedUserId = this.deps.normalizeQQId(session.userId);
184
+ this.logger.error('B站账号反向查询', normalizedUserId, `通过B站UID查询失败: ${error.message}`);
185
+ return this.deps.sendMessage(session, [koishi_1.h.text(this.getFriendlyErrorMessage(error))]);
186
+ }
187
+ }
188
+ /**
189
+ * 处理 mcid.bindbuid 命令
190
+ */
191
+ async handleBindBuid(session, buid) {
192
+ try {
193
+ const normalizedUserId = this.deps.normalizeQQId(session.userId);
194
+ this.logger.info('绑定', `QQ(${normalizedUserId})尝试绑定B站UID(${buid})`);
195
+ // 验证格式
196
+ if (!buid || !/^\d+$/.test(buid)) {
197
+ this.logger.warn('绑定', `QQ(${normalizedUserId})尝试绑定无效的B站UID格式: ${buid}`);
198
+ return this.deps.sendMessage(session, [koishi_1.h.text(`无效的B站UID格式,请输入正确的B站UID`)]);
199
+ }
200
+ // 检查是否已被他人绑定
201
+ const existingBind = await this.repos.mcidbind.findByBuidUid(buid);
202
+ if (existingBind) {
203
+ const existingQQId = existingBind.qqId;
204
+ this.logger.warn('绑定', `QQ(${normalizedUserId})尝试绑定已被QQ(${existingQQId})绑定的B站UID(${buid})`);
205
+ return this.deps.sendMessage(session, [koishi_1.h.text(`该B站UID已被其他用户绑定`)]);
206
+ }
207
+ // 验证B站UID
208
+ const buidUser = await this.validateBUID(buid);
209
+ if (!buidUser) {
210
+ this.logger.warn('绑定', `QQ(${normalizedUserId})尝试绑定不存在的B站UID(${buid})`);
211
+ return this.deps.sendMessage(session, [koishi_1.h.text(`无法验证B站UID,请确认输入正确`)]);
212
+ }
213
+ // 创建或更新绑定
214
+ const success = await this.createOrUpdateBuidBind(normalizedUserId, buidUser);
215
+ if (success) {
216
+ this.logger.info('绑定', `QQ(${normalizedUserId})成功绑定B站UID(${buid})`);
217
+ return this.deps.sendMessage(session, [
218
+ koishi_1.h.text(`成功绑定B站账号!\n`),
219
+ koishi_1.h.text(`B站UID: ${buidUser.uid}\n`),
220
+ koishi_1.h.text(`用户名: ${buidUser.username}\n`),
221
+ buidUser.guard_level > 0 ? koishi_1.h.text(`舰长等级: ${buidUser.guard_level_text} (${buidUser.guard_level})\n`) : null,
222
+ buidUser.medal ? koishi_1.h.text(`粉丝牌: ${buidUser.medal.name} Lv.${buidUser.medal.level}\n`) : null,
223
+ buidUser.wealthMedalLevel > 0 ? koishi_1.h.text(`荣耀等级: ${buidUser.wealthMedalLevel}\n`) : null,
224
+ ...(this.config?.showAvatar ? [koishi_1.h.image(`https://workers.vrp.moe/bilibili/avatar/${buidUser.uid}?size=160`)] : [])
225
+ ].filter(Boolean));
226
+ }
227
+ else {
228
+ this.logger.error('绑定', normalizedUserId, `QQ(${normalizedUserId})绑定B站UID(${buid})失败`);
229
+ return this.deps.sendMessage(session, [koishi_1.h.text(`绑定失败,请稍后重试`)]);
230
+ }
231
+ }
232
+ catch (error) {
233
+ this.logger.error('绑定', session.userId, error);
234
+ return this.deps.sendMessage(session, [koishi_1.h.text(`绑定失败:${this.getFriendlyErrorMessage(error)}`)]);
235
+ }
236
+ }
237
+ /**
238
+ * 处理 mcid.unbindbuid 命令
239
+ */
240
+ async handleUnbindBuid(session) {
241
+ try {
242
+ const normalizedUserId = this.deps.normalizeQQId(session.userId);
243
+ this.logger.info('解绑', `QQ(${normalizedUserId})尝试解绑B站账号`);
244
+ // 查询当前绑定
245
+ const bind = await this.repos.mcidbind.findByQQId(normalizedUserId);
246
+ if (!bind || !bind.buidUid) {
247
+ this.logger.warn('解绑', `QQ(${normalizedUserId})尝试解绑未绑定的B站账号`);
248
+ return this.deps.sendMessage(session, [koishi_1.h.text(`您尚未绑定B站账号`)]);
249
+ }
250
+ // 更新绑定信息
251
+ const updateData = {
252
+ buidUid: '',
253
+ buidUsername: '',
254
+ guardLevel: 0,
255
+ guardLevelText: '',
256
+ medalName: '',
257
+ medalLevel: 0,
258
+ wealthMedalLevel: 0,
259
+ lastActiveTime: null,
260
+ lastModified: new Date()
261
+ };
262
+ await this.repos.mcidbind.update(normalizedUserId, updateData);
263
+ this.logger.info('解绑', `QQ(${normalizedUserId})成功解绑B站账号`);
264
+ return this.deps.sendMessage(session, [koishi_1.h.text(`已成功解绑B站账号`)]);
265
+ }
266
+ catch (error) {
267
+ this.logger.error('解绑', session.userId, error);
268
+ return this.deps.sendMessage(session, [koishi_1.h.text(`解绑失败:${this.getFriendlyErrorMessage(error)}`)]);
269
+ }
270
+ }
271
+ // ========== 私有辅助方法 ==========
272
+ /**
273
+ * 解析UID输入(支持多种格式)
274
+ */
275
+ parseUidInput(uid, operatorQQId) {
276
+ let actualUid = uid;
277
+ if (uid && uid.toLowerCase().startsWith('uid:')) {
278
+ actualUid = uid.substring(4);
279
+ }
280
+ else if (uid && uid.includes('space.bilibili.com/')) {
281
+ try {
282
+ let urlPart = uid.replace(/^https?:\/\/space\.bilibili\.com\//, '');
283
+ if (urlPart.includes('?')) {
284
+ urlPart = urlPart.split('?')[0];
285
+ }
286
+ if (urlPart.includes('/')) {
287
+ urlPart = urlPart.split('/')[0];
288
+ }
289
+ actualUid = urlPart;
290
+ this.logger.debug('BUID解析', `QQ(${operatorQQId})从URL提取UID: ${uid} -> ${actualUid}`);
291
+ }
292
+ catch (error) {
293
+ this.logger.warn('BUID解析', `QQ(${operatorQQId})URL解析失败: ${error.message}`);
294
+ actualUid = '';
295
+ }
296
+ }
297
+ return actualUid;
298
+ }
299
+ /**
300
+ * 验证B站UID
301
+ */
302
+ async validateBUID(buid) {
303
+ try {
304
+ if (!buid || !/^\d+$/.test(buid)) {
305
+ this.logger.warn('B站账号验证', `无效的B站UID格式: ${buid}`);
306
+ return null;
307
+ }
308
+ this.logger.debug('B站账号验证', `验证B站UID: ${buid}`);
309
+ const response = await axios_1.default.get(`${this.config.zminfoApiUrl}/api/user/${buid}`, {
310
+ timeout: 10000,
311
+ headers: {
312
+ 'User-Agent': 'Koishi-MCID-Bot/1.0'
313
+ }
314
+ });
315
+ if (response.data.success && response.data.data && response.data.data.user) {
316
+ const user = response.data.data.user;
317
+ this.logger.debug('B站账号验证', `B站UID ${buid} 验证成功: ${user.username}`);
318
+ return user;
319
+ }
320
+ else {
321
+ this.logger.warn('B站账号验证', `B站UID ${buid} 不存在或API返回失败: ${response.data.message}`);
322
+ return null;
323
+ }
324
+ }
325
+ catch (error) {
326
+ if (error.response?.status === 404) {
327
+ this.logger.warn('B站账号验证', `B站UID ${buid} 不存在`);
328
+ return null;
329
+ }
330
+ this.logger.error('B站账号验证', 'system', `验证B站UID ${buid} 时出错: ${error.message}`);
331
+ throw new Error(`无法验证B站UID: ${error.message}`);
332
+ }
333
+ }
334
+ /**
335
+ * 检查B站UID是否已被其他用户绑定
336
+ */
337
+ async checkBuidExists(buid, currentUserId) {
338
+ try {
339
+ const bind = await this.repos.mcidbind.findByBuidUid(buid);
340
+ if (!bind)
341
+ return false;
342
+ if (currentUserId) {
343
+ const normalizedCurrentId = this.deps.normalizeQQId(currentUserId);
344
+ return bind.qqId !== normalizedCurrentId;
345
+ }
346
+ return true;
347
+ }
348
+ catch (error) {
349
+ this.logger.error('B站账号绑定', 'system', `检查B站UID(${buid})是否存在时出错: ${error.message}`);
350
+ return false;
351
+ }
352
+ }
353
+ /**
354
+ * 创建或更新B站账号绑定
355
+ */
356
+ async createOrUpdateBuidBind(userId, buidUser) {
357
+ try {
358
+ const normalizedQQId = this.deps.normalizeQQId(userId);
359
+ if (!normalizedQQId) {
360
+ this.logger.error('B站账号绑定', 'system', `创建/更新绑定失败: 无法提取有效的QQ号`);
361
+ return false;
362
+ }
363
+ // 安全检查
364
+ const existingBuidBind = await this.repos.mcidbind.findByBuidUid(buidUser.uid);
365
+ if (existingBuidBind && existingBuidBind.qqId !== normalizedQQId) {
366
+ this.logger.error('B站账号绑定', 'system', `安全检查失败: B站UID ${buidUser.uid} 已被QQ(${existingBuidBind.qqId})绑定`);
367
+ return false;
368
+ }
369
+ const bind = await this.repos.mcidbind.findByQQId(normalizedQQId);
370
+ const updateData = {
371
+ buidUid: buidUser.uid,
372
+ buidUsername: buidUser.username,
373
+ guardLevel: buidUser.guard_level || 0,
374
+ guardLevelText: buidUser.guard_level_text || '',
375
+ maxGuardLevel: buidUser.max_guard_level || 0,
376
+ maxGuardLevelText: buidUser.max_guard_level_text || '',
377
+ medalName: buidUser.medal?.name || '',
378
+ medalLevel: buidUser.medal?.level || 0,
379
+ wealthMedalLevel: buidUser.wealthMedalLevel || 0,
380
+ lastActiveTime: buidUser.last_active_time ? new Date(buidUser.last_active_time) : new Date(),
381
+ lastModified: new Date()
382
+ };
383
+ if (bind) {
384
+ await this.repos.mcidbind.update(normalizedQQId, updateData);
385
+ this.logger.info('B站账号绑定', `更新绑定: QQ=${normalizedQQId}, B站UID=${buidUser.uid}, 用户名=${buidUser.username}`);
386
+ }
387
+ else {
388
+ const tempMcUsername = `_temp_skip_${normalizedQQId}_${Date.now()}`;
389
+ const newBind = {
390
+ qqId: normalizedQQId,
391
+ mcUsername: tempMcUsername,
392
+ mcUuid: '',
393
+ isAdmin: false,
394
+ whitelist: [],
395
+ tags: [],
396
+ ...updateData
397
+ };
398
+ await this.repos.mcidbind.create(newBind);
399
+ this.logger.info('B站账号绑定', `创建绑定(跳过MC): QQ=${normalizedQQId}, B站UID=${buidUser.uid}, 用户名=${buidUser.username}, 临时MC用户名=${tempMcUsername}`);
400
+ }
401
+ return true;
402
+ }
403
+ catch (error) {
404
+ this.logger.error('B站账号绑定', userId, `创建/更新B站账号绑定失败: ${error.message}`);
405
+ return false;
406
+ }
407
+ }
408
+ /**
409
+ * 仅更新B站信息,不更新绑定时间
410
+ */
411
+ async updateBuidInfoOnly(userId, buidUser) {
412
+ try {
413
+ const normalizedQQId = this.deps.normalizeQQId(userId);
414
+ if (!normalizedQQId) {
415
+ this.logger.error('B站账号信息更新', 'system', `更新失败: 无法提取有效的QQ号`);
416
+ return false;
417
+ }
418
+ const bind = await this.repos.mcidbind.findByQQId(normalizedQQId);
419
+ if (!bind) {
420
+ this.logger.warn('B站账号信息更新', `QQ(${normalizedQQId})没有绑定记录,无法更新B站信息`);
421
+ return false;
422
+ }
423
+ const updateData = {
424
+ buidUsername: buidUser.username,
425
+ guardLevel: buidUser.guard_level || 0,
426
+ guardLevelText: buidUser.guard_level_text || '',
427
+ maxGuardLevel: buidUser.max_guard_level || 0,
428
+ maxGuardLevelText: buidUser.max_guard_level_text || '',
429
+ medalName: buidUser.medal?.name || '',
430
+ medalLevel: buidUser.medal?.level || 0,
431
+ wealthMedalLevel: buidUser.wealthMedalLevel || 0,
432
+ lastActiveTime: buidUser.last_active_time ? new Date(buidUser.last_active_time) : new Date()
433
+ };
434
+ await this.repos.mcidbind.update(normalizedQQId, updateData);
435
+ this.logger.info('B站账号信息更新', `刷新信息: QQ=${normalizedQQId}, B站UID=${bind.buidUid}, 用户名=${buidUser.username}`);
436
+ return true;
437
+ }
438
+ catch (error) {
439
+ this.logger.error('B站账号信息更新', userId, `更新B站账号信息失败: ${error.message}`);
440
+ return false;
441
+ }
442
+ }
443
+ /**
444
+ * 检查是否为管理员
445
+ */
446
+ async checkIsAdmin(userId) {
447
+ try {
448
+ const normalizedUserId = this.deps.normalizeQQId(userId);
449
+ // 检查是否是masterId
450
+ if (this.config.masterId && normalizedUserId === this.config.masterId) {
451
+ return true;
452
+ }
453
+ // 检查数据库中的管理员标记
454
+ const bind = await this.repos.mcidbind.findByQQId(normalizedUserId);
455
+ return bind?.isAdmin === true;
456
+ }
457
+ catch (error) {
458
+ this.logger.error('权限检查', userId, `检查管理员权限失败: ${error.message}`);
459
+ return false;
460
+ }
461
+ }
462
+ /**
463
+ * 强制绑定模式处理
464
+ */
465
+ async handleForceBindMode(session, actualUid, target, operatorQQId) {
466
+ this.logger.info('强制BUID绑定', `QQ(${operatorQQId})使用强制模式绑定UID: ${actualUid}`, true);
467
+ // 检查配置
468
+ if (!this.config.forceBindSessdata) {
469
+ this.logger.warn('强制BUID绑定', `QQ(${operatorQQId})尝试强制绑定但未配置Cookie`);
470
+ return this.deps.sendMessage(session, [koishi_1.h.text('❌ 强制绑定功能未配置,请联系管理员设置B站Cookie信息')]);
471
+ }
472
+ try {
473
+ await this.deps.sendMessage(session, [koishi_1.h.text('🔄 正在使用强制模式获取用户信息和粉丝牌数据,请稍候...')]);
474
+ // 执行强制绑定
475
+ const enhancedUser = await this.deps.forceBinder.forceBindUser(actualUid);
476
+ const standardUser = this.deps.forceBinder.convertToZminfoUser(enhancedUser);
477
+ // 处理管理员为他人强制绑定
478
+ if (target) {
479
+ const normalizedTargetId = this.deps.normalizeQQId(target);
480
+ if (!normalizedTargetId) {
481
+ this.logger.warn('强制BUID绑定', `QQ(${operatorQQId})提供的目标用户ID"${target}"无效`);
482
+ return this.deps.sendMessage(session, [koishi_1.h.text('❌ 目标用户ID无效')]);
483
+ }
484
+ // 检查权限
485
+ const isAdmin = await this.checkIsAdmin(session.userId);
486
+ if (!isAdmin) {
487
+ this.logger.warn('强制BUID绑定', `权限不足: QQ(${operatorQQId})不是管理员`);
488
+ return this.deps.sendMessage(session, [koishi_1.h.text('只有管理员才能为其他用户强制绑定')]);
489
+ }
490
+ // 检查UID是否已被占用
491
+ if (await this.checkBuidExists(actualUid, target)) {
492
+ this.logger.warn('强制BUID绑定', `BUID"${actualUid}"已被其他QQ号绑定`);
493
+ return this.deps.sendMessage(session, [koishi_1.h.text(`❌ UID ${actualUid} 已被其他用户绑定,即使使用强制模式也无法绑定已被占用的UID`)]);
494
+ }
495
+ const bindResult = await this.createOrUpdateBuidBind(normalizedTargetId, standardUser);
496
+ if (bindResult) {
497
+ // 尝试设置群昵称
498
+ try {
499
+ const latestTargetBind = await this.repos.mcidbind.findByQQId(normalizedTargetId);
500
+ if (latestTargetBind) {
501
+ const mcName = latestTargetBind.mcUsername && !latestTargetBind.mcUsername.startsWith('_temp_') ? latestTargetBind.mcUsername : null;
502
+ await this.deps.autoSetGroupNickname(session, mcName, enhancedUser.username, normalizedTargetId);
503
+ }
504
+ }
505
+ catch (renameError) {
506
+ this.logger.warn('强制绑定', `群昵称设置失败: ${renameError.message}`);
507
+ }
508
+ this.logger.info('强制为他人绑定BUID', `管理员QQ(${operatorQQId})为QQ(${normalizedTargetId})强制绑定BUID: ${actualUid}(${enhancedUser.username})`, true);
509
+ // 清理目标用户的绑定会话
510
+ this.deps.removeBindingSession(target, session.channelId);
511
+ this.logger.info('强制绑定', `管理员为QQ(${normalizedTargetId})强制绑定B站账号后,已清理该用户的交互式绑定会话`);
512
+ const medalDetails = this.deps.forceBinder.getTargetMedalDetails(enhancedUser);
513
+ return this.deps.sendMessage(session, [koishi_1.h.text(`✅ 已成功为用户 ${normalizedTargetId} 强制绑定B站账号\n用户名: ${enhancedUser.username}\nUID: ${actualUid}\n\n${medalDetails}`)]);
514
+ }
515
+ else {
516
+ this.logger.error('强制BUID绑定', operatorQQId, `为QQ(${normalizedTargetId})强制绑定失败`);
517
+ return this.deps.sendMessage(session, [koishi_1.h.text('❌ 强制绑定失败,数据库操作出错')]);
518
+ }
519
+ }
520
+ else {
521
+ // 为自己强制绑定
522
+ if (await this.checkBuidExists(actualUid, session.userId)) {
523
+ this.logger.warn('强制BUID绑定', `BUID"${actualUid}"已被其他QQ号绑定`);
524
+ return this.deps.sendMessage(session, [koishi_1.h.text(`❌ UID ${actualUid} 已被其他用户绑定,即使使用强制模式也无法绑定已被占用的UID`)]);
525
+ }
526
+ const bindResult = await this.createOrUpdateBuidBind(session.userId, standardUser);
527
+ if (bindResult) {
528
+ // 尝试设置群昵称
529
+ try {
530
+ const latestBind = await this.repos.mcidbind.findByQQId(operatorQQId);
531
+ if (latestBind) {
532
+ const mcName = latestBind.mcUsername && !latestBind.mcUsername.startsWith('_temp_') ? latestBind.mcUsername : null;
533
+ await this.deps.autoSetGroupNickname(session, mcName, enhancedUser.username);
534
+ }
535
+ }
536
+ catch (renameError) {
537
+ this.logger.warn('强制绑定', `群昵称设置失败: ${renameError.message}`);
538
+ }
539
+ this.logger.info('强制绑定BUID', `QQ(${operatorQQId})强制绑定BUID: ${actualUid}(${enhancedUser.username})`, true);
540
+ const medalDetails = this.deps.forceBinder.getTargetMedalDetails(enhancedUser);
541
+ return this.deps.sendMessage(session, [
542
+ koishi_1.h.text(`✅ 强制绑定成功!\nB站UID: ${enhancedUser.uid}\n用户名: ${enhancedUser.username}\n\n${medalDetails}`),
543
+ ...(this.config?.showAvatar ? [koishi_1.h.image(`https://workers.vrp.moe/bilibili/avatar/${enhancedUser.uid}?size=160`)] : [])
544
+ ]);
545
+ }
546
+ else {
547
+ this.logger.error('强制BUID绑定', operatorQQId, `强制绑定失败`);
548
+ return this.deps.sendMessage(session, [koishi_1.h.text('❌ 强制绑定失败,数据库操作出错')]);
549
+ }
550
+ }
551
+ }
552
+ catch (forceBindError) {
553
+ this.logger.error('强制BUID绑定', operatorQQId, `强制绑定过程出错: ${forceBindError.message}`);
554
+ return this.deps.sendMessage(session, [koishi_1.h.text(`❌ 强制绑定失败: ${forceBindError.message}`)]);
555
+ }
556
+ }
557
+ /**
558
+ * 管理员为他人绑定处理
559
+ */
560
+ async handleAdminBindForOthers(session, actualUid, target, operatorQQId) {
561
+ const normalizedTargetId = this.deps.normalizeQQId(target);
562
+ if (!normalizedTargetId) {
563
+ this.logger.warn('BUID绑定', `QQ(${operatorQQId})提供的目标用户ID"${target}"无效`);
564
+ if (target.startsWith('@')) {
565
+ return this.deps.sendMessage(session, [koishi_1.h.text('❌ 请使用真正的@功能,而不是手动输入@符号\n正确做法:点击或长按用户头像选择@功能')]);
566
+ }
567
+ return this.deps.sendMessage(session, [koishi_1.h.text('❌ 目标用户ID无效\n请提供有效的QQ号或使用@功能选择用户')]);
568
+ }
569
+ this.logger.debug('BUID绑定', `QQ(${operatorQQId})尝试为QQ(${normalizedTargetId})绑定BUID: ${actualUid}`);
570
+ // 检查权限
571
+ const isAdmin = await this.checkIsAdmin(session.userId);
572
+ if (!isAdmin) {
573
+ this.logger.warn('BUID绑定', `权限不足: QQ(${operatorQQId})不是管理员`);
574
+ return this.deps.sendMessage(session, [koishi_1.h.text('只有管理员才能为其他用户绑定BUID')]);
575
+ }
576
+ // 检查UID是否已被占用
577
+ if (await this.checkBuidExists(actualUid, target)) {
578
+ this.logger.warn('BUID绑定', `BUID"${actualUid}"已被其他QQ号绑定`);
579
+ return this.deps.sendMessage(session, [koishi_1.h.text(`UID ${actualUid} 已被其他用户绑定`)]);
580
+ }
581
+ // 验证UID
582
+ const buidUser = await this.validateBUID(actualUid);
583
+ if (!buidUser) {
584
+ this.logger.warn('BUID绑定', `QQ(${operatorQQId})提供的UID"${actualUid}"不存在`);
585
+ return this.deps.sendMessage(session, [koishi_1.h.text(`无法验证UID: ${actualUid},该用户可能不存在或未被发现,你可以去直播间发个弹幕回来再绑定`)]);
586
+ }
587
+ // 创建或更新绑定
588
+ const bindResult = await this.createOrUpdateBuidBind(normalizedTargetId, buidUser);
589
+ if (!bindResult) {
590
+ this.logger.error('BUID绑定', operatorQQId, `管理员QQ(${operatorQQId})为QQ(${normalizedTargetId})绑定BUID"${actualUid}"失败`);
591
+ return this.deps.sendMessage(session, [koishi_1.h.text(`为用户 ${normalizedTargetId} 绑定BUID失败: 数据库操作出错,请联系管理员`)]);
592
+ }
593
+ this.logger.info('为他人绑定BUID', `管理员QQ(${operatorQQId})为QQ(${normalizedTargetId})绑定BUID: ${actualUid}(${buidUser.username})`, true);
594
+ // 清理目标用户的绑定会话
595
+ this.deps.removeBindingSession(target, session.channelId);
596
+ this.logger.info('绑定', `管理员为QQ(${normalizedTargetId})绑定B站账号后,已清理该用户的交互式绑定会话`);
597
+ // 尝试设置群昵称
598
+ try {
599
+ const latestTargetBind = await this.repos.mcidbind.findByQQId(normalizedTargetId);
600
+ if (latestTargetBind) {
601
+ const mcName = latestTargetBind.mcUsername && !latestTargetBind.mcUsername.startsWith('_temp_') ? latestTargetBind.mcUsername : null;
602
+ await this.deps.autoSetGroupNickname(session, mcName, buidUser.username, normalizedTargetId);
603
+ this.logger.info('绑定', `管理员QQ(${operatorQQId})为QQ(${normalizedTargetId})B站绑定完成,已尝试设置群昵称`);
604
+ }
605
+ }
606
+ catch (renameError) {
607
+ this.logger.warn('绑定', `管理员QQ(${operatorQQId})为QQ(${normalizedTargetId})B站绑定后群昵称设置失败: ${renameError.message}`);
608
+ }
609
+ return this.deps.sendMessage(session, [
610
+ koishi_1.h.text(`已成功为用户 ${normalizedTargetId} 绑定B站账号\n用户名: ${buidUser.username}\nUID: ${actualUid}\n${buidUser.guard_level > 0 ? `舰长等级: ${buidUser.guard_level_text}\n` : ''}${buidUser.medal ? `粉丝牌: ${buidUser.medal.name} Lv.${buidUser.medal.level}` : ''}`)
611
+ ]);
612
+ }
613
+ /**
614
+ * 为自己绑定处理
615
+ */
616
+ async handleSelfBind(session, actualUid, operatorQQId) {
617
+ this.logger.debug('BUID绑定', `QQ(${operatorQQId})尝试绑定BUID: ${actualUid}`);
618
+ const selfBind = await this.repos.mcidbind.findByQQId(operatorQQId);
619
+ // 检查冷却时间
620
+ if (selfBind && selfBind.buidUid) {
621
+ const isAdmin = await this.checkIsAdmin(session.userId);
622
+ if (!isAdmin && !this.deps.checkCooldown(selfBind.lastModified)) {
623
+ const days = this.config.cooldownDays;
624
+ const now = new Date();
625
+ const diffTime = now.getTime() - selfBind.lastModified.getTime();
626
+ const passedDays = Math.floor(diffTime / (1000 * 60 * 60 * 24));
627
+ const remainingDays = days - passedDays;
628
+ this.logger.warn('BUID绑定', `QQ(${operatorQQId})已绑定BUID"${selfBind.buidUid}",且在冷却期内,还需${remainingDays}天`);
629
+ return this.deps.sendMessage(session, [koishi_1.h.text(`您已绑定B站UID: ${selfBind.buidUid},如需修改,请在冷却期结束后(还需${remainingDays}天)或联系管理员。`)]);
630
+ }
631
+ this.logger.debug('BUID绑定', `QQ(${operatorQQId})已绑定BUID"${selfBind.buidUid}",将进行更新`);
632
+ }
633
+ // 检查UID是否已被占用
634
+ if (await this.checkBuidExists(actualUid, session.userId)) {
635
+ this.logger.warn('BUID绑定', `BUID"${actualUid}"已被其他QQ号绑定`);
636
+ return this.deps.sendMessage(session, [koishi_1.h.text(`UID ${actualUid} 已被其他用户绑定`)]);
637
+ }
638
+ // 验证UID
639
+ const buidUser = await this.validateBUID(actualUid);
640
+ if (!buidUser) {
641
+ this.logger.warn('BUID绑定', `QQ(${operatorQQId})提供的UID"${actualUid}"不存在`);
642
+ return this.deps.sendMessage(session, [koishi_1.h.text(`无法验证UID: ${actualUid},该用户可能不存在或未被发现,你可以去直播间逛一圈,发个弹幕回来再绑定`)]);
643
+ }
644
+ // 创建或更新绑定
645
+ const bindResult = await this.createOrUpdateBuidBind(session.userId, buidUser);
646
+ if (!bindResult) {
647
+ this.logger.error('BUID绑定', operatorQQId, `QQ(${operatorQQId})绑定BUID"${actualUid}"失败`);
648
+ return this.deps.sendMessage(session, [koishi_1.h.text('绑定失败,数据库操作出错,请联系管理员')]);
649
+ }
650
+ this.logger.info('绑定BUID', `QQ(${operatorQQId})绑定BUID: ${actualUid}(${buidUser.username})`, true);
651
+ // 尝试设置群昵称
652
+ try {
653
+ const latestBind = await this.repos.mcidbind.findByQQId(operatorQQId);
654
+ if (latestBind) {
655
+ const mcName = latestBind.mcUsername && !latestBind.mcUsername.startsWith('_temp_') ? latestBind.mcUsername : null;
656
+ await this.deps.autoSetGroupNickname(session, mcName, buidUser.username);
657
+ this.logger.info('绑定', `QQ(${operatorQQId})B站绑定完成,已尝试设置群昵称`);
658
+ }
659
+ }
660
+ catch (renameError) {
661
+ this.logger.warn('绑定', `QQ(${operatorQQId})B站绑定后群昵称设置失败: ${renameError.message}`);
662
+ }
663
+ this.logger.info('绑定', `QQ(${operatorQQId})成功绑定B站UID(${actualUid})`);
664
+ return this.deps.sendMessage(session, [
665
+ koishi_1.h.text(`成功绑定B站账号!\n`),
666
+ koishi_1.h.text(`B站UID: ${buidUser.uid}\n`),
667
+ koishi_1.h.text(`用户名: ${buidUser.username}\n`),
668
+ buidUser.guard_level > 0 ? koishi_1.h.text(`舰长等级: ${buidUser.guard_level_text} (${buidUser.guard_level})\n`) : null,
669
+ buidUser.medal ? koishi_1.h.text(`粉丝牌: ${buidUser.medal.name} Lv.${buidUser.medal.level}\n`) : null,
670
+ buidUser.wealthMedalLevel > 0 ? koishi_1.h.text(`荣耀等级: ${buidUser.wealthMedalLevel}\n`) : null,
671
+ ...(this.config?.showAvatar ? [koishi_1.h.image(`https://workers.vrp.moe/bilibili/avatar/${buidUser.uid}?size=160`)] : [])
672
+ ].filter(Boolean));
673
+ }
674
+ /**
675
+ * 获取友好的错误消息
676
+ */
677
+ getFriendlyErrorMessage(error) {
678
+ const message = typeof error === 'string' ? error : error.message;
679
+ if (message.includes('timeout') || message.includes('ETIMEDOUT')) {
680
+ return '操作超时,请稍后重试';
681
+ }
682
+ if (message.includes('ECONNREFUSED')) {
683
+ return '无法连接到服务,请稍后重试';
684
+ }
685
+ if (message.includes('Network')) {
686
+ return '网络错误,请检查网络连接';
687
+ }
688
+ if (message.includes('404')) {
689
+ return '用户不存在';
690
+ }
691
+ return message;
692
+ }
693
+ }
694
+ exports.BuidHandler = BuidHandler;