diff --git a/src/common/log.ts b/src/common/log.ts index 50e04173..346baaed 100644 --- a/src/common/log.ts +++ b/src/common/log.ts @@ -148,6 +148,7 @@ export class LogWrapper { } else if (this.consoleLogEnabled) { this.logger.log(level, message); } else if (this.fileLogEnabled) { + // eslint-disable-next-line no-control-regex this.logger.log(level, message.replace(/\x1B[@-_][0-?]*[ -/]*[@-~]/g, '')); } } @@ -226,7 +227,7 @@ export function rawMessageToText(msg: RawMessage, recursiveLevel = 0): string { ? rawMessageToText(recordMsgOrNull, recursiveLevel + 1) : `未找到消息记录 (MsgId = ${element.replyElement.sourceMsgIdInRecords})` - }]`; + }]`; } if (element.picElement) { @@ -277,4 +278,4 @@ export function rawMessageToText(msg: RawMessage, recursiveLevel = 0): string { } return tokens.join(' '); -} \ No newline at end of file +} diff --git a/src/core/apis/packet.ts b/src/core/apis/packet.ts index fd3a94ba..c8c65021 100644 --- a/src/core/apis/packet.ts +++ b/src/core/apis/packet.ts @@ -19,34 +19,46 @@ export class NTQQPacketApi { core: NapCatCore; logger: LogWrapper; qqVersion: string | undefined; - pkt: PacketClientSession; + pkt!: PacketClientSession; + errStack: string[] = []; constructor(context: InstanceContext, core: NapCatCore) { this.context = context; this.core = core; this.logger = core.context.logger; - this.pkt = new PacketClientSession(core); this.InitSendPacket(this.context.basicInfoWrapper.getFullQQVesion()) .then() - .catch(this.core.context.logger.logError.bind(this.core.context.logger)); + .catch((err) => { + this.logger.logError.bind(this.core.context.logger); + this.errStack.push(err); + }); } get available(): boolean { - return this.pkt?.available; + return this.pkt?.available ?? false; + } + + get clientLogStack() { + return this.pkt?.clientLogStack + '\n' + this.errStack.join('\n'); } async InitSendPacket(qqVer: string) { this.qqVersion = qqVer; const table = typedOffset[qqVer + '-' + os.arch()]; if (!table) { - this.logger.logError(`[Core] [Packet] PacketBackend 不支持当前QQ版本架构:${qqVer}-${os.arch()}, - 请参照 https://github.com/NapNeko/NapCatQQ/releases/tag/v${napCatVersion} 配置正确的QQ版本!`); + const err = `[Core] [Packet] PacketBackend 不支持当前QQ版本架构:${qqVer}-${os.arch()}, + 请参照 https://github.com/NapNeko/NapCatQQ/releases/tag/v${napCatVersion} 配置正确的QQ版本!`; + this.logger.logError(err); + this.errStack.push(err); return false; } if (this.core.configLoader.configData.packetBackend === 'disable') { - this.logger.logWarn('[Core] [Packet] 已禁用PacketBackend,NapCat.Packet将不会加载!'); + const err = '[Core] [Packet] 已禁用PacketBackend,NapCat.Packet将不会加载!'; + this.logger.logError(err); + this.errStack.push(err); return false; } + this.pkt = new PacketClientSession(this.core); await this.pkt.init(process.pid, table.recv, table.send); return true; } diff --git a/src/core/packet/client/baseClient.ts b/src/core/packet/client/baseClient.ts index d7ee669d..5516550a 100644 --- a/src/core/packet/client/baseClient.ts +++ b/src/core/packet/client/baseClient.ts @@ -2,6 +2,7 @@ import { LRUCache } from "@/common/lru-cache"; import crypto, { createHash } from "crypto"; import { PacketContext } from "@/core/packet/context/packetContext"; import { OidbPacket, PacketHexStr } from "@/core/packet/transformer/base"; +import { LogStack } from "@/core/packet/context/clientContext"; export interface RecvPacket { type: string, // 仅recv @@ -24,13 +25,16 @@ function randText(len: number): string { return text; } + export abstract class IPacketClient { protected readonly context: PacketContext; protected readonly cb = new LRUCache Promise>(500); // trace_id-type callback + logStack: LogStack; available: boolean = false; - protected constructor(context: PacketContext) { + protected constructor(context: PacketContext, logStack: LogStack) { this.context = context; + this.logStack = logStack; } abstract check(): boolean; diff --git a/src/core/packet/client/nativeClient.ts b/src/core/packet/client/nativeClient.ts index d5c3537f..abd1758b 100644 --- a/src/core/packet/client/nativeClient.ts +++ b/src/core/packet/client/nativeClient.ts @@ -6,6 +6,7 @@ import { IPacketClient } from "@/core/packet/client/baseClient"; import { constants } from "node:os"; import { LRUCache } from "@/common/lru-cache"; import { PacketContext } from "@/core/packet/context/packetContext"; +import { LogStack } from "@/core/packet/context/clientContext"; // 0 send 1 recv export interface NativePacketExportType { @@ -18,19 +19,19 @@ export class NativePacketClient extends IPacketClient { private MoeHooExport: { exports: NativePacketExportType } = { exports: {} }; private sendEvent = new LRUCache(500); // seq->trace_id - constructor(context: PacketContext) { - super(context); + constructor(context: PacketContext, logStack: LogStack) { + super(context, logStack); } check(): boolean { const platform = process.platform + '.' + process.arch; if (!this.supportedPlatforms.includes(platform)) { - this.context.logger.warn(`不支持的平台: ${platform}`); + this.logStack.pushLogWarn(`NativePacketClient: 不支持的平台: ${platform}`); return false; } const moehoo_path = path.join(dirname(fileURLToPath(import.meta.url)), './moehoo/MoeHoo.' + platform + '.node'); if (!fs.existsSync(moehoo_path)) { - this.context.logger.warn(`[Core] [Packet:Native] 缺失运行时文件: ${moehoo_path}`); + this.logStack.pushLogWarn(`NativePacketClient: 缺失运行时文件: ${moehoo_path}`); return false; } return true; diff --git a/src/core/packet/client/wsClient.ts b/src/core/packet/client/wsClient.ts index 69368c8d..a4ffb0b3 100644 --- a/src/core/packet/client/wsClient.ts +++ b/src/core/packet/client/wsClient.ts @@ -1,6 +1,7 @@ import { Data, WebSocket } from "ws"; import { IPacketClient, RecvPacket } from "@/core/packet/client/baseClient"; import { PacketContext } from "@/core/packet/context/packetContext"; +import { LogStack } from "@/core/packet/context/clientContext"; export class wsPacketClient extends IPacketClient { private websocket: WebSocket | null = null; @@ -12,8 +13,8 @@ export class wsPacketClient extends IPacketClient { private isInitialized: boolean = false; private initPayload: { pid: number, recv: string, send: string } | null = null; - constructor(context: PacketContext) { - super(context); + constructor(context: PacketContext, logStack: LogStack) { + super(context, logStack); this.clientUrl = this.context.napcore.config.packetServer ? this.clientUrlWrap(this.context.napcore.config.packetServer) : this.clientUrlWrap('127.0.0.1:8083'); @@ -21,7 +22,7 @@ export class wsPacketClient extends IPacketClient { check(): boolean { if (!this.context.napcore.config.packetServer) { - this.context.logger.warn(`wsPacketClient 未配置服务器地址`); + this.logStack.pushLogWarn(`wsPacketClient 未配置服务器地址`); return false; } return true; @@ -41,7 +42,7 @@ export class wsPacketClient extends IPacketClient { trace_id })); } else { - this.context.logger.warn(`WebSocket 未连接,无法发送命令: ${cmd}`); + this.logStack.pushLogWarn(`WebSocket 未连接,无法发送命令: ${cmd}`); } } @@ -52,11 +53,11 @@ export class wsPacketClient extends IPacketClient { return; } catch (error) { this.reconnectAttempts++; - this.context.logger.warn(`第 ${this.reconnectAttempts}/${this.maxReconnectAttempts} 次尝试重连失败!`); + this.logStack.pushLogWarn(`第 ${this.reconnectAttempts}/${this.maxReconnectAttempts} 次尝试重连失败!`); await this.delay(5000); } } - this.context.logger.error(`wsPacketClient 在 ${this.clientUrl} 达到最大重连次数 (${this.maxReconnectAttempts})!`); + this.logStack.pushLogError(`wsPacketClient 在 ${this.clientUrl} 达到最大重连次数 (${this.maxReconnectAttempts})!`); throw new Error(`无法连接到 WebSocket 服务器:${this.clientUrl}`); } diff --git a/src/core/packet/clientSession.ts b/src/core/packet/clientSession.ts index 15dd77ed..3faea314 100644 --- a/src/core/packet/clientSession.ts +++ b/src/core/packet/clientSession.ts @@ -12,6 +12,10 @@ export class PacketClientSession { return this.context.client.init(pid, recv, send); } + get clientLogStack() { + return this.context.client.clientLogStack; + } + get available() { return this.context.client.available; } diff --git a/src/core/packet/context/clientContext.ts b/src/core/packet/context/clientContext.ts index 51dfefde..fc9c1c8b 100644 --- a/src/core/packet/context/clientContext.ts +++ b/src/core/packet/context/clientContext.ts @@ -3,22 +3,61 @@ import { IPacketClient } from "@/core/packet/client/baseClient"; import { NativePacketClient } from "@/core/packet/client/nativeClient"; import { wsPacketClient } from "@/core/packet/client/wsClient"; import { OidbPacket } from "@/core/packet/transformer/base"; +import { PacketLogger } from "@/core/packet/context/loggerContext"; type clientPriority = { - [key: number]: (context: PacketContext) => IPacketClient; + [key: number]: (context: PacketContext, logStack: LogStack) => IPacketClient; } const clientPriority: clientPriority = { - 10: (context: PacketContext) => new NativePacketClient(context), - 1: (context: PacketContext) => new wsPacketClient(context), + 10: (context: PacketContext, logStack: LogStack) => new NativePacketClient(context, logStack), + 1: (context: PacketContext, logStack: LogStack) => new wsPacketClient(context, logStack), }; +export class LogStack { + private stack: string[] = []; + private logger: PacketLogger; + + constructor(logger: PacketLogger) { + this.logger = logger; + } + + push(msg: string) { + this.stack.push(msg); + } + + pushLogInfo(msg: string) { + this.logger.info(msg); + this.stack.push(`${new Date().toISOString()} [INFO] ${msg}`); + } + + pushLogWarn(msg: string) { + this.logger.warn(msg); + this.stack.push(`${new Date().toISOString()} [WARN] ${msg}`); + } + + pushLogError(msg: string) { + this.logger.error(msg); + this.stack.push(`${new Date().toISOString()} [ERROR] ${msg}`); + } + + clear() { + this.stack = []; + } + + content() { + return this.stack.join('\n'); + } +} + export class PacketClientContext { - private readonly _client: IPacketClient; private readonly context: PacketContext; + private readonly logStack: LogStack; + private readonly _client: IPacketClient; constructor(context: PacketContext) { this.context = context; + this.logStack = new LogStack(context.logger); this._client = this.newClient(); } @@ -26,13 +65,17 @@ export class PacketClientContext { return this._client.available; } + get clientLogStack(): string { + return this._client.logStack.content(); + } + async init(pid: number, recv: string, send: string): Promise { await this._client.init(pid, recv, send); } - async sendOidbPacket(pkt: OidbPacket, rsp = false): Promise { + async sendOidbPacket(pkt: OidbPacket, rsp?: T): Promise { const raw = await this._client.sendOidbPacket(pkt, rsp); - return Buffer.from(raw.hex_data, "hex"); + return (rsp ? Buffer.from(raw.hex_data, "hex") : undefined) as T extends true ? Buffer : void; } private newClient(): IPacketClient { @@ -41,11 +84,11 @@ export class PacketClientContext { switch (prefer) { case "native": this.context.logger.info("使用指定的 NativePacketClient 作为后端"); - client = new NativePacketClient(this.context); + client = new NativePacketClient(this.context, this.logStack); break; case "frida": this.context.logger.info("[Core] [Packet] 使用指定的 FridaPacketClient 作为后端"); - client = new wsPacketClient(this.context); + client = new wsPacketClient(this.context, this.logStack); break; case "auto": case undefined: @@ -64,7 +107,7 @@ export class PacketClientContext { private judgeClient(): IPacketClient { const sortedClients = Object.entries(clientPriority) .map(([priority, clientFactory]) => { - const client = clientFactory(this.context); + const client = clientFactory(this.context, this.logStack); const score = +priority * +client.check(); return { client, score }; }) diff --git a/src/core/packet/utils/helper/miniAppHelper.ts b/src/core/packet/utils/helper/miniAppHelper.ts index 33a458eb..a65f4e3c 100644 --- a/src/core/packet/utils/helper/miniAppHelper.ts +++ b/src/core/packet/utils/helper/miniAppHelper.ts @@ -4,7 +4,7 @@ import { MiniAppRawData, MiniAppReqCustomParams, MiniAppReqTemplateParams -} from "@/core/packet/client/entities/miniApp"; +} from "@/core/packet/entities/miniApp"; type MiniAppTemplateNameList = "bili" | "weibo"; diff --git a/src/onebot/action/packet/GetPacketStatus.ts b/src/onebot/action/packet/GetPacketStatus.ts index 20f005c6..3e013245 100644 --- a/src/onebot/action/packet/GetPacketStatus.ts +++ b/src/onebot/action/packet/GetPacketStatus.ts @@ -10,7 +10,8 @@ export abstract class GetPacketStatusDepends extends BaseAction // TODO: add error stack? return { valid: false, - message: "packetBackend不可用,请参照文档 https://napneko.github.io/config/advanced 和启动日志检查packetBackend状态或进行配置!", + message: "packetBackend不可用,请参照文档 https://napneko.github.io/config/advanced 和启动日志检查packetBackend状态或进行配置!" + + "错误堆栈信息:" + this.core.apis.PacketApi.clientLogStack, }; } return await super.check(payload); diff --git a/src/onebot/index.ts b/src/onebot/index.ts index 7f304a59..495619f6 100644 --- a/src/onebot/index.ts +++ b/src/onebot/index.ts @@ -83,7 +83,7 @@ export class NapCatOneBot11Adapter { async registerNative(core: NapCatCore, context: InstanceContext) { try { this.nativeCore = new Native(context.pathWrapper.binaryPath); - if (!this.nativeCore.inited) throw new Error('Native Not Init'); + // if (!this.nativeCore.inited) throw new Error('Native Not Init'); this.nativeCore.registerRecallCallback(async (hex: string) => { try { // TODO: refactor! @@ -346,10 +346,10 @@ export class NapCatOneBot11Adapter { } }; msgListener.onKickedOffLine = async (kick) => { - let event = new BotOfflineEvent(this.core, kick.tipsTitle, kick.tipsDesc); + const event = new BotOfflineEvent(this.core, kick.tipsTitle, kick.tipsDesc); this.networkManager.emitEvent(event) .catch(e => this.context.logger.logError.bind(this.context.logger)('处理Bot掉线失败', e)); - } + }; this.context.session.getMsgService().addKernelMsgListener( proxiedListenerOf(msgListener, this.context.logger), ); diff --git a/src/shell/napcat.ts b/src/shell/napcat.ts index f0d80eac..ff5f59d3 100644 --- a/src/shell/napcat.ts +++ b/src/shell/napcat.ts @@ -232,7 +232,7 @@ export async function NCoreInitShell() { logger.log(`可用于快速登录的 QQ:\n${historyLoginList .map((u, index) => `${index + 1}. ${u.uin} ${u.nickName}`) .join('\n') - }`); + }`); } loginService.getQRCodePicture(); }