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.
- package/lib/export-utils.d.ts +49 -0
- package/lib/export-utils.js +305 -0
- package/lib/force-bind-utils.d.ts +40 -0
- package/lib/force-bind-utils.js +242 -0
- package/lib/handlers/base.handler.d.ts +61 -0
- package/lib/handlers/base.handler.js +22 -0
- package/lib/handlers/binding.handler.d.ts +45 -0
- package/lib/handlers/binding.handler.js +285 -0
- package/lib/handlers/buid.handler.d.ts +71 -0
- package/lib/handlers/buid.handler.js +694 -0
- package/lib/handlers/index.d.ts +6 -0
- package/lib/handlers/index.js +22 -0
- package/lib/handlers/mcid.handler.d.ts +101 -0
- package/lib/handlers/mcid.handler.js +1045 -0
- package/lib/handlers/tag.handler.d.ts +14 -0
- package/lib/handlers/tag.handler.js +382 -0
- package/lib/handlers/whitelist.handler.d.ts +84 -0
- package/lib/handlers/whitelist.handler.js +1011 -0
- package/lib/index.d.ts +7 -0
- package/lib/index.js +2693 -0
- package/lib/managers/rcon-manager.d.ts +24 -0
- package/lib/managers/rcon-manager.js +308 -0
- package/lib/repositories/mcidbind.repository.d.ts +105 -0
- package/lib/repositories/mcidbind.repository.js +288 -0
- package/lib/repositories/schedule-mute.repository.d.ts +68 -0
- package/lib/repositories/schedule-mute.repository.js +175 -0
- package/lib/types/api.d.ts +135 -0
- package/lib/types/api.js +6 -0
- package/lib/types/common.d.ts +40 -0
- package/lib/types/common.js +6 -0
- package/lib/types/config.d.ts +55 -0
- package/lib/types/config.js +6 -0
- package/lib/types/database.d.ts +47 -0
- package/lib/types/database.js +6 -0
- package/lib/types/index.d.ts +8 -0
- package/lib/types/index.js +28 -0
- package/lib/utils/helpers.d.ts +76 -0
- package/lib/utils/helpers.js +275 -0
- package/lib/utils/logger.d.ts +75 -0
- package/lib/utils/logger.js +134 -0
- package/lib/utils/message-utils.d.ts +46 -0
- package/lib/utils/message-utils.js +234 -0
- package/lib/utils/rate-limiter.d.ts +26 -0
- package/lib/utils/rate-limiter.js +47 -0
- package/lib/utils/session-manager.d.ts +70 -0
- package/lib/utils/session-manager.js +120 -0
- package/package.json +39 -0
- package/readme.md +281 -0
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { Context, Session } from 'koishi';
|
|
2
|
+
import { LoggerService } from '../utils/logger';
|
|
3
|
+
import { MCIDBINDRepository } from '../repositories/mcidbind.repository';
|
|
4
|
+
import { ScheduleMuteRepository } from '../repositories/schedule-mute.repository';
|
|
5
|
+
import { RconManager } from '../managers/rcon-manager';
|
|
6
|
+
import { MessageUtils } from '../utils/message-utils';
|
|
7
|
+
import { ForceBinder } from '../force-bind-utils';
|
|
8
|
+
import { GroupExporter } from '../export-utils';
|
|
9
|
+
import type { MCIDBIND } from '../types';
|
|
10
|
+
/**
|
|
11
|
+
* Repositories 接口 - 聚合所有数据仓储
|
|
12
|
+
*/
|
|
13
|
+
export interface Repositories {
|
|
14
|
+
mcidbind: MCIDBINDRepository;
|
|
15
|
+
scheduleMute: ScheduleMuteRepository;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Handler 依赖项接口
|
|
19
|
+
* 包含所有 Handler 需要的共享函数和服务
|
|
20
|
+
*/
|
|
21
|
+
export interface HandlerDependencies {
|
|
22
|
+
normalizeQQId: (userId: string) => string;
|
|
23
|
+
formatCommand: (cmd: string) => string;
|
|
24
|
+
formatUuid: (uuid: string) => string;
|
|
25
|
+
checkCooldown: (lastModified: Date | null, multiplier?: number) => boolean;
|
|
26
|
+
getCrafatarUrl: (uuid: string) => string;
|
|
27
|
+
getStarlightSkinUrl: (uuid: string) => string;
|
|
28
|
+
sendMessage: (session: Session, content: any[], options?: any) => Promise<void>;
|
|
29
|
+
autoSetGroupNickname: (session: Session, mcUsername: string, buidUsername: string, targetUserId?: string, specifiedGroupId?: string) => Promise<void>;
|
|
30
|
+
getBindInfo: (qqId: string) => Promise<MCIDBIND | null>;
|
|
31
|
+
rconManager: RconManager;
|
|
32
|
+
messageUtils: MessageUtils;
|
|
33
|
+
forceBinder: ForceBinder;
|
|
34
|
+
groupExporter: GroupExporter;
|
|
35
|
+
getBindingSession: (userId: string, channelId: string) => any;
|
|
36
|
+
createBindingSession: (userId: string, channelId: string, initialState?: string) => void;
|
|
37
|
+
updateBindingSession: (userId: string, channelId: string, updates: any) => void;
|
|
38
|
+
removeBindingSession: (userId: string, channelId: string) => void;
|
|
39
|
+
avatarCache?: Map<string, {
|
|
40
|
+
url: string;
|
|
41
|
+
timestamp: number;
|
|
42
|
+
}>;
|
|
43
|
+
bindingSessions: Map<string, any>;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* 命令处理器基类
|
|
47
|
+
* 提供通用的上下文、配置、日志、数据仓储和共享依赖访问
|
|
48
|
+
*/
|
|
49
|
+
export declare abstract class BaseHandler {
|
|
50
|
+
protected ctx: Context;
|
|
51
|
+
protected config: any;
|
|
52
|
+
protected logger: LoggerService;
|
|
53
|
+
protected repos: Repositories;
|
|
54
|
+
protected deps: HandlerDependencies;
|
|
55
|
+
constructor(ctx: Context, config: any, logger: LoggerService, repos: Repositories, deps: HandlerDependencies);
|
|
56
|
+
/**
|
|
57
|
+
* 注册命令
|
|
58
|
+
* 每个子类实现自己的命令注册逻辑
|
|
59
|
+
*/
|
|
60
|
+
abstract register(): void;
|
|
61
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.BaseHandler = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* 命令处理器基类
|
|
6
|
+
* 提供通用的上下文、配置、日志、数据仓储和共享依赖访问
|
|
7
|
+
*/
|
|
8
|
+
class BaseHandler {
|
|
9
|
+
ctx;
|
|
10
|
+
config;
|
|
11
|
+
logger;
|
|
12
|
+
repos;
|
|
13
|
+
deps;
|
|
14
|
+
constructor(ctx, config, logger, repos, deps) {
|
|
15
|
+
this.ctx = ctx;
|
|
16
|
+
this.config = config;
|
|
17
|
+
this.logger = logger;
|
|
18
|
+
this.repos = repos;
|
|
19
|
+
this.deps = deps;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
exports.BaseHandler = BaseHandler;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { Context } from 'koishi';
|
|
2
|
+
import { BaseHandler, Repositories, HandlerDependencies } from './base.handler';
|
|
3
|
+
import { LoggerService } from '../utils/logger';
|
|
4
|
+
/**
|
|
5
|
+
* 交互式绑定命令处理器
|
|
6
|
+
* 处理 "绑定" 命令,引导用户完成 MC 和 B站双重绑定
|
|
7
|
+
*/
|
|
8
|
+
export declare class BindingHandler extends BaseHandler {
|
|
9
|
+
private readonly BINDING_SESSION_TIMEOUT;
|
|
10
|
+
constructor(ctx: Context, config: any, logger: LoggerService, repos: Repositories, deps: HandlerDependencies);
|
|
11
|
+
/**
|
|
12
|
+
* 注册交互式绑定命令
|
|
13
|
+
*/
|
|
14
|
+
register(): void;
|
|
15
|
+
/**
|
|
16
|
+
* 检查用户是否为管理员
|
|
17
|
+
* @param userId 用户ID
|
|
18
|
+
* @returns 是否为管理员
|
|
19
|
+
*/
|
|
20
|
+
private isAdmin;
|
|
21
|
+
/**
|
|
22
|
+
* 获取用户友好的错误信息
|
|
23
|
+
* @param error 错误对象
|
|
24
|
+
* @returns 用户友好的错误消息
|
|
25
|
+
*/
|
|
26
|
+
private getFriendlyErrorMessage;
|
|
27
|
+
/**
|
|
28
|
+
* 提取用户友好的错误信息
|
|
29
|
+
* @param errorMsg 原始错误消息
|
|
30
|
+
* @returns 用户友好的错误消息
|
|
31
|
+
*/
|
|
32
|
+
private getUserFacingErrorMessage;
|
|
33
|
+
/**
|
|
34
|
+
* 判断是否为警告级别错误(用户可能输入有误)
|
|
35
|
+
* @param errorMsg 错误消息
|
|
36
|
+
* @returns 是否为警告级别错误
|
|
37
|
+
*/
|
|
38
|
+
private isWarningError;
|
|
39
|
+
/**
|
|
40
|
+
* 判断是否为严重错误(系统问题)
|
|
41
|
+
* @param errorMsg 错误消息
|
|
42
|
+
* @returns 是否为严重错误
|
|
43
|
+
*/
|
|
44
|
+
private isCriticalError;
|
|
45
|
+
}
|
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.BindingHandler = void 0;
|
|
4
|
+
const koishi_1 = require("koishi");
|
|
5
|
+
const base_handler_1 = require("./base.handler");
|
|
6
|
+
/**
|
|
7
|
+
* 交互式绑定命令处理器
|
|
8
|
+
* 处理 "绑定" 命令,引导用户完成 MC 和 B站双重绑定
|
|
9
|
+
*/
|
|
10
|
+
class BindingHandler extends base_handler_1.BaseHandler {
|
|
11
|
+
BINDING_SESSION_TIMEOUT;
|
|
12
|
+
constructor(ctx, config, logger, repos, deps) {
|
|
13
|
+
super(ctx, config, logger, repos, deps);
|
|
14
|
+
// 从配置中获取会话超时时间,默认3分钟
|
|
15
|
+
this.BINDING_SESSION_TIMEOUT = 3 * 60 * 1000;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* 注册交互式绑定命令
|
|
19
|
+
*/
|
|
20
|
+
register() {
|
|
21
|
+
this.ctx.command('绑定 [target:string]', '交互式绑定流程')
|
|
22
|
+
.alias('bind')
|
|
23
|
+
.alias('interact')
|
|
24
|
+
.action(async ({ session }, target) => {
|
|
25
|
+
try {
|
|
26
|
+
const normalizedUserId = this.deps.normalizeQQId(session.userId);
|
|
27
|
+
const channelId = session.channelId;
|
|
28
|
+
// 如果指定了目标用户(管理员功能)
|
|
29
|
+
if (target) {
|
|
30
|
+
// 检查权限
|
|
31
|
+
if (!await this.isAdmin(session.userId)) {
|
|
32
|
+
this.logger.warn('交互绑定', `权限不足: QQ(${normalizedUserId})不是管理员,无法为他人启动绑定`);
|
|
33
|
+
return this.deps.sendMessage(session, [koishi_1.h.text('只有管理员才能为其他用户启动绑定流程')]);
|
|
34
|
+
}
|
|
35
|
+
const normalizedTargetId = this.deps.normalizeQQId(target);
|
|
36
|
+
// 检查目标用户ID是否有效
|
|
37
|
+
if (!normalizedTargetId) {
|
|
38
|
+
this.logger.warn('交互绑定', `QQ(${normalizedUserId})提供的目标用户ID"${target}"无效`);
|
|
39
|
+
if (target.startsWith('@')) {
|
|
40
|
+
return this.deps.sendMessage(session, [koishi_1.h.text('❌ 请使用真正的@功能,而不是手动输入@符号\n正确做法:点击或长按用户头像选择@功能')]);
|
|
41
|
+
}
|
|
42
|
+
return this.deps.sendMessage(session, [koishi_1.h.text('❌ 目标用户ID无效\n请提供有效的QQ号或使用@功能选择用户')]);
|
|
43
|
+
}
|
|
44
|
+
this.logger.info('交互绑定', `管理员QQ(${normalizedUserId})为QQ(${normalizedTargetId})启动交互式绑定流程`, true);
|
|
45
|
+
// 检查目标用户是否已有进行中的会话
|
|
46
|
+
const existingTargetSession = this.deps.getBindingSession(target, channelId);
|
|
47
|
+
if (existingTargetSession) {
|
|
48
|
+
this.logger.warn('交互绑定', `QQ(${normalizedTargetId})已有进行中的绑定会话`);
|
|
49
|
+
return this.deps.sendMessage(session, [koishi_1.h.text(`用户 ${normalizedTargetId} 已有进行中的绑定会话`)]);
|
|
50
|
+
}
|
|
51
|
+
// 检查目标用户当前绑定状态
|
|
52
|
+
const targetBind = await this.deps.getBindInfo(normalizedTargetId);
|
|
53
|
+
// 如果两个账号都已绑定,不需要进入绑定流程
|
|
54
|
+
if (targetBind && targetBind.mcUsername && targetBind.buidUid) {
|
|
55
|
+
this.logger.info('交互绑定', `QQ(${normalizedTargetId})已完成全部绑定`, true);
|
|
56
|
+
// 显示当前绑定信息
|
|
57
|
+
const displayUsername = targetBind.mcUsername && !targetBind.mcUsername.startsWith('_temp_') ? targetBind.mcUsername : '未绑定';
|
|
58
|
+
let bindInfo = `用户 ${normalizedTargetId} 已完成全部账号绑定:\n✅ MC账号: ${displayUsername}\n✅ B站账号: ${targetBind.buidUsername} (UID: ${targetBind.buidUid})`;
|
|
59
|
+
if (targetBind.guardLevel > 0) {
|
|
60
|
+
bindInfo += `\n舰长等级: ${targetBind.guardLevelText}`;
|
|
61
|
+
}
|
|
62
|
+
if (targetBind.medalName) {
|
|
63
|
+
bindInfo += `\n粉丝牌: ${targetBind.medalName} Lv.${targetBind.medalLevel}`;
|
|
64
|
+
}
|
|
65
|
+
return this.deps.sendMessage(session, [koishi_1.h.text(bindInfo)]);
|
|
66
|
+
}
|
|
67
|
+
// 为目标用户创建绑定会话
|
|
68
|
+
this.deps.createBindingSession(target, channelId, 'waiting_buid');
|
|
69
|
+
// 如果已绑定MC但未绑定B站,直接进入B站绑定流程
|
|
70
|
+
if (targetBind && targetBind.mcUsername && !targetBind.buidUid) {
|
|
71
|
+
this.logger.info('交互绑定', `QQ(${normalizedTargetId})已绑定MC,进入B站绑定流程`, true);
|
|
72
|
+
// 更新会话状态
|
|
73
|
+
this.deps.updateBindingSession(target, channelId, {
|
|
74
|
+
state: 'waiting_buid',
|
|
75
|
+
mcUsername: targetBind.mcUsername && !targetBind.mcUsername.startsWith('_temp_') ? targetBind.mcUsername : null,
|
|
76
|
+
mcUuid: targetBind.mcUuid
|
|
77
|
+
});
|
|
78
|
+
// 向目标用户发送提示(@他们)
|
|
79
|
+
const displayUsername = targetBind.mcUsername && !targetBind.mcUsername.startsWith('_temp_') ? targetBind.mcUsername : '未绑定';
|
|
80
|
+
await this.deps.sendMessage(session, [
|
|
81
|
+
koishi_1.h.at(normalizedTargetId),
|
|
82
|
+
koishi_1.h.text(` 管理员为您启动了B站绑定流程\n🎮 已绑定MC: ${displayUsername}\n🔗 请发送您的B站UID`)
|
|
83
|
+
]);
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
// 向目标用户发送提示(@他们)
|
|
87
|
+
await this.deps.sendMessage(session, [
|
|
88
|
+
koishi_1.h.at(normalizedTargetId),
|
|
89
|
+
koishi_1.h.text(` 管理员为您启动了账号绑定流程\n📋 请选择绑定方式:\n1. 发送您的B站UID进行B站绑定\n2. 发送"跳过"仅绑定MC账号`)
|
|
90
|
+
]);
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
// 为自己启动绑定流程
|
|
94
|
+
this.logger.info('交互绑定', `QQ(${normalizedUserId})开始交互式绑定流程`, true);
|
|
95
|
+
// 检查是否已有进行中的会话
|
|
96
|
+
const existingSession = this.deps.getBindingSession(session.userId, channelId);
|
|
97
|
+
if (existingSession) {
|
|
98
|
+
this.logger.warn('交互绑定', `QQ(${normalizedUserId})已有进行中的绑定会话`);
|
|
99
|
+
return this.deps.sendMessage(session, [koishi_1.h.text('您已有进行中的绑定会话,请先完成当前绑定或等待会话超时')]);
|
|
100
|
+
}
|
|
101
|
+
// 检查用户当前绑定状态
|
|
102
|
+
const existingBind = await this.deps.getBindInfo(normalizedUserId);
|
|
103
|
+
// 如果两个账号都已绑定(且MC不是temp用户名),不需要进入绑定流程
|
|
104
|
+
if (existingBind && existingBind.mcUsername && !existingBind.mcUsername.startsWith('_temp_') && existingBind.buidUid) {
|
|
105
|
+
this.logger.info('交互绑定', `QQ(${normalizedUserId})已完成全部绑定`, true);
|
|
106
|
+
// 显示当前绑定信息
|
|
107
|
+
const displayUsername = existingBind.mcUsername;
|
|
108
|
+
let bindInfo = `您已完成全部账号绑定:\n✅ MC账号: ${displayUsername}\n✅ B站账号: ${existingBind.buidUsername} (UID: ${existingBind.buidUid})`;
|
|
109
|
+
if (existingBind.guardLevel > 0) {
|
|
110
|
+
bindInfo += `\n舰长等级: ${existingBind.guardLevelText}`;
|
|
111
|
+
}
|
|
112
|
+
if (existingBind.medalName) {
|
|
113
|
+
bindInfo += `\n粉丝牌: ${existingBind.medalName} Lv.${existingBind.medalLevel}`;
|
|
114
|
+
}
|
|
115
|
+
bindInfo += `\n\n如需修改绑定信息,请使用:\n- ${this.deps.formatCommand('mcid change <新用户名>')} 修改MC账号\n- ${this.deps.formatCommand('buid bind <新UID>')} 修改B站账号`;
|
|
116
|
+
return this.deps.sendMessage(session, [koishi_1.h.text(bindInfo)]);
|
|
117
|
+
}
|
|
118
|
+
// 如果已绑定MC(且不是temp用户名)但未绑定B站,直接进入B站绑定流程
|
|
119
|
+
if (existingBind && existingBind.mcUsername && !existingBind.mcUsername.startsWith('_temp_') && !existingBind.buidUid) {
|
|
120
|
+
this.logger.info('交互绑定', `QQ(${normalizedUserId})已绑定MC,进入B站绑定流程`, true);
|
|
121
|
+
// 创建绑定会话,状态直接设为等待B站UID
|
|
122
|
+
const timeout = setTimeout(() => {
|
|
123
|
+
this.deps.bindingSessions.delete(`${normalizedUserId}_${channelId}`);
|
|
124
|
+
this.ctx.bots.forEach(bot => {
|
|
125
|
+
bot.sendMessage(channelId, [koishi_1.h.at(normalizedUserId), koishi_1.h.text(' 绑定会话已超时,请重新开始绑定流程\n\n⚠️ 温馨提醒:若在管理员多次提醒后仍不配合绑定账号信息,将按群规进行相应处理。')]).catch(() => { });
|
|
126
|
+
});
|
|
127
|
+
this.logger.info('交互绑定', `QQ(${normalizedUserId})的绑定会话因超时被清理`, true);
|
|
128
|
+
}, this.BINDING_SESSION_TIMEOUT);
|
|
129
|
+
const sessionData = {
|
|
130
|
+
userId: session.userId,
|
|
131
|
+
channelId: channelId,
|
|
132
|
+
state: 'waiting_buid',
|
|
133
|
+
startTime: Date.now(),
|
|
134
|
+
timeout: timeout,
|
|
135
|
+
mcUsername: existingBind.mcUsername,
|
|
136
|
+
mcUuid: existingBind.mcUuid
|
|
137
|
+
};
|
|
138
|
+
this.deps.bindingSessions.set(`${normalizedUserId}_${channelId}`, sessionData);
|
|
139
|
+
return this.deps.sendMessage(session, [koishi_1.h.text(`🎮 已绑定MC: ${existingBind.mcUsername}\n🔗 请发送您的B站UID`)]);
|
|
140
|
+
}
|
|
141
|
+
// 如果只绑定了B站(MC是temp用户名),提醒绑定MC账号
|
|
142
|
+
if (existingBind && existingBind.buidUid && existingBind.buidUsername &&
|
|
143
|
+
existingBind.mcUsername && existingBind.mcUsername.startsWith('_temp_')) {
|
|
144
|
+
this.logger.info('交互绑定', `QQ(${normalizedUserId})只绑定了B站,进入MC绑定流程`, true);
|
|
145
|
+
// 创建绑定会话,状态设为等待MC用户名
|
|
146
|
+
this.deps.createBindingSession(session.userId, channelId, 'waiting_mc_username');
|
|
147
|
+
const bindingSession = this.deps.getBindingSession(session.userId, channelId);
|
|
148
|
+
bindingSession.state = 'waiting_mc_username';
|
|
149
|
+
return this.deps.sendMessage(session, [koishi_1.h.text(`✅ 已绑定B站: ${existingBind.buidUsername}\n🎮 请发送您的MC用户名,或发送"跳过"保持当前状态`)]);
|
|
150
|
+
}
|
|
151
|
+
// 如果未绑定账号,让用户选择绑定方式,优先B站绑定
|
|
152
|
+
this.deps.createBindingSession(session.userId, channelId, 'waiting_buid');
|
|
153
|
+
// 发送绑定选项提示
|
|
154
|
+
return this.deps.sendMessage(session, [koishi_1.h.text(`📋 请选择绑定方式:\n1. 发送您的B站UID进行B站绑定\n2. 发送"跳过"仅绑定MC账号`)]);
|
|
155
|
+
}
|
|
156
|
+
catch (error) {
|
|
157
|
+
const normalizedUserId = this.deps.normalizeQQId(session.userId);
|
|
158
|
+
this.logger.error('交互绑定', `QQ(${normalizedUserId})开始交互式绑定失败: ${error.message}`, error);
|
|
159
|
+
return this.deps.sendMessage(session, [koishi_1.h.text(this.getFriendlyErrorMessage(error))]);
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* 检查用户是否为管理员
|
|
165
|
+
* @param userId 用户ID
|
|
166
|
+
* @returns 是否为管理员
|
|
167
|
+
*/
|
|
168
|
+
async isAdmin(userId) {
|
|
169
|
+
try {
|
|
170
|
+
// 主人始终是管理员
|
|
171
|
+
const normalizedMasterId = this.deps.normalizeQQId(this.config.masterId);
|
|
172
|
+
const normalizedQQId = this.deps.normalizeQQId(userId);
|
|
173
|
+
if (normalizedQQId === normalizedMasterId)
|
|
174
|
+
return true;
|
|
175
|
+
// 查询MCIDBIND表中是否是管理员
|
|
176
|
+
const bind = await this.deps.getBindInfo(normalizedQQId);
|
|
177
|
+
return bind && bind.isAdmin === true;
|
|
178
|
+
}
|
|
179
|
+
catch (error) {
|
|
180
|
+
const normalizedQQId = this.deps.normalizeQQId(userId);
|
|
181
|
+
this.logger.error('权限检查', `QQ(${normalizedQQId})的管理员状态查询失败: ${error.message}`, error);
|
|
182
|
+
return false;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* 获取用户友好的错误信息
|
|
187
|
+
* @param error 错误对象
|
|
188
|
+
* @returns 用户友好的错误消息
|
|
189
|
+
*/
|
|
190
|
+
getFriendlyErrorMessage(error) {
|
|
191
|
+
const errorMsg = error instanceof Error ? error.message : error;
|
|
192
|
+
// 拆分错误信息
|
|
193
|
+
const userError = this.getUserFacingErrorMessage(errorMsg);
|
|
194
|
+
// 将警告级别错误标记出来
|
|
195
|
+
if (this.isWarningError(userError)) {
|
|
196
|
+
return `⚠️ ${userError}`;
|
|
197
|
+
}
|
|
198
|
+
// 将严重错误标记出来
|
|
199
|
+
if (this.isCriticalError(userError)) {
|
|
200
|
+
return `❌ ${userError}`;
|
|
201
|
+
}
|
|
202
|
+
return userError;
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* 提取用户友好的错误信息
|
|
206
|
+
* @param errorMsg 原始错误消息
|
|
207
|
+
* @returns 用户友好的错误消息
|
|
208
|
+
*/
|
|
209
|
+
getUserFacingErrorMessage(errorMsg) {
|
|
210
|
+
// Mojang API相关错误
|
|
211
|
+
if (errorMsg.includes('ECONNABORTED') || errorMsg.includes('timeout')) {
|
|
212
|
+
return '无法连接到Mojang服务器,请稍后再试';
|
|
213
|
+
}
|
|
214
|
+
if (errorMsg.includes('404')) {
|
|
215
|
+
return '该Minecraft用户名不存在';
|
|
216
|
+
}
|
|
217
|
+
if (errorMsg.includes('network') || errorMsg.includes('connect')) {
|
|
218
|
+
return '网络连接异常,请稍后再试';
|
|
219
|
+
}
|
|
220
|
+
// 数据库相关错误
|
|
221
|
+
if (errorMsg.includes('unique') || errorMsg.includes('duplicate')) {
|
|
222
|
+
return '该Minecraft用户名已被其他用户绑定';
|
|
223
|
+
}
|
|
224
|
+
// RCON相关错误
|
|
225
|
+
if (errorMsg.includes('RCON') || errorMsg.includes('服务器')) {
|
|
226
|
+
if (errorMsg.includes('authentication') || errorMsg.includes('auth') || errorMsg.includes('认证')) {
|
|
227
|
+
return 'RCON认证失败,服务器拒绝访问,请联系管理员检查密码';
|
|
228
|
+
}
|
|
229
|
+
if (errorMsg.includes('ECONNREFUSED') || errorMsg.includes('ETIMEDOUT') || errorMsg.includes('无法连接')) {
|
|
230
|
+
return '无法连接到游戏服务器,请确认服务器是否在线或联系管理员';
|
|
231
|
+
}
|
|
232
|
+
if (errorMsg.includes('command') || errorMsg.includes('执行命令')) {
|
|
233
|
+
return '服务器命令执行失败,请稍后再试';
|
|
234
|
+
}
|
|
235
|
+
return '与游戏服务器通信失败,请稍后再试';
|
|
236
|
+
}
|
|
237
|
+
// 用户名相关错误
|
|
238
|
+
if (errorMsg.includes('用户名') || errorMsg.includes('username')) {
|
|
239
|
+
if (errorMsg.includes('不存在')) {
|
|
240
|
+
return '该Minecraft用户名不存在,请检查拼写';
|
|
241
|
+
}
|
|
242
|
+
if (errorMsg.includes('已被')) {
|
|
243
|
+
return '该Minecraft用户名已被其他用户绑定,请使用其他用户名';
|
|
244
|
+
}
|
|
245
|
+
if (errorMsg.includes('格式')) {
|
|
246
|
+
return 'Minecraft用户名格式不正确,应为3-16位字母、数字和下划线';
|
|
247
|
+
}
|
|
248
|
+
return '用户名验证失败,请检查用户名并重试';
|
|
249
|
+
}
|
|
250
|
+
// 默认错误信息
|
|
251
|
+
return '操作失败,请稍后再试';
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* 判断是否为警告级别错误(用户可能输入有误)
|
|
255
|
+
* @param errorMsg 错误消息
|
|
256
|
+
* @returns 是否为警告级别错误
|
|
257
|
+
*/
|
|
258
|
+
isWarningError(errorMsg) {
|
|
259
|
+
const warningPatterns = [
|
|
260
|
+
'用户名不存在',
|
|
261
|
+
'格式不正确',
|
|
262
|
+
'已被其他用户绑定',
|
|
263
|
+
'已在白名单中',
|
|
264
|
+
'不在白名单中',
|
|
265
|
+
'未绑定MC账号',
|
|
266
|
+
'冷却期内'
|
|
267
|
+
];
|
|
268
|
+
return warningPatterns.some(pattern => errorMsg.includes(pattern));
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* 判断是否为严重错误(系统问题)
|
|
272
|
+
* @param errorMsg 错误消息
|
|
273
|
+
* @returns 是否为严重错误
|
|
274
|
+
*/
|
|
275
|
+
isCriticalError(errorMsg) {
|
|
276
|
+
const criticalPatterns = [
|
|
277
|
+
'无法连接',
|
|
278
|
+
'RCON认证失败',
|
|
279
|
+
'服务器通信失败',
|
|
280
|
+
'数据库操作出错'
|
|
281
|
+
];
|
|
282
|
+
return criticalPatterns.some(pattern => errorMsg.includes(pattern));
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
exports.BindingHandler = BindingHandler;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { BaseHandler } from './base.handler';
|
|
2
|
+
/**
|
|
3
|
+
* BUID 命令处理器
|
|
4
|
+
* 处理 B站账号相关命令
|
|
5
|
+
*/
|
|
6
|
+
export declare class BuidHandler extends BaseHandler {
|
|
7
|
+
/**
|
|
8
|
+
* 注册 BUID 相关命令
|
|
9
|
+
*/
|
|
10
|
+
register(): void;
|
|
11
|
+
/**
|
|
12
|
+
* 处理 BUID 查询命令
|
|
13
|
+
*/
|
|
14
|
+
private handleQuery;
|
|
15
|
+
/**
|
|
16
|
+
* 处理 BUID 绑定命令
|
|
17
|
+
*/
|
|
18
|
+
private handleBind;
|
|
19
|
+
/**
|
|
20
|
+
* 处理查找用户命令(通过BUID反查QQ)
|
|
21
|
+
*/
|
|
22
|
+
private handleFindUser;
|
|
23
|
+
/**
|
|
24
|
+
* 处理 mcid.bindbuid 命令
|
|
25
|
+
*/
|
|
26
|
+
private handleBindBuid;
|
|
27
|
+
/**
|
|
28
|
+
* 处理 mcid.unbindbuid 命令
|
|
29
|
+
*/
|
|
30
|
+
private handleUnbindBuid;
|
|
31
|
+
/**
|
|
32
|
+
* 解析UID输入(支持多种格式)
|
|
33
|
+
*/
|
|
34
|
+
private parseUidInput;
|
|
35
|
+
/**
|
|
36
|
+
* 验证B站UID
|
|
37
|
+
*/
|
|
38
|
+
private validateBUID;
|
|
39
|
+
/**
|
|
40
|
+
* 检查B站UID是否已被其他用户绑定
|
|
41
|
+
*/
|
|
42
|
+
private checkBuidExists;
|
|
43
|
+
/**
|
|
44
|
+
* 创建或更新B站账号绑定
|
|
45
|
+
*/
|
|
46
|
+
private createOrUpdateBuidBind;
|
|
47
|
+
/**
|
|
48
|
+
* 仅更新B站信息,不更新绑定时间
|
|
49
|
+
*/
|
|
50
|
+
private updateBuidInfoOnly;
|
|
51
|
+
/**
|
|
52
|
+
* 检查是否为管理员
|
|
53
|
+
*/
|
|
54
|
+
private checkIsAdmin;
|
|
55
|
+
/**
|
|
56
|
+
* 强制绑定模式处理
|
|
57
|
+
*/
|
|
58
|
+
private handleForceBindMode;
|
|
59
|
+
/**
|
|
60
|
+
* 管理员为他人绑定处理
|
|
61
|
+
*/
|
|
62
|
+
private handleAdminBindForOthers;
|
|
63
|
+
/**
|
|
64
|
+
* 为自己绑定处理
|
|
65
|
+
*/
|
|
66
|
+
private handleSelfBind;
|
|
67
|
+
/**
|
|
68
|
+
* 获取友好的错误消息
|
|
69
|
+
*/
|
|
70
|
+
private getFriendlyErrorMessage;
|
|
71
|
+
}
|