diff --git a/packages/napcat-core/helper/config.ts b/packages/napcat-core/helper/config.ts index b3a01bdc..b1b323c9 100644 --- a/packages/napcat-core/helper/config.ts +++ b/packages/napcat-core/helper/config.ts @@ -1,7 +1,10 @@ import { ConfigBase } from '@/napcat-core/helper/config-base'; import { NapCatCore } from '@/napcat-core/index'; import { Type, Static } from '@sinclair/typebox'; -import { AnySchema } from 'ajv'; +import Ajv, { AnySchema } from 'ajv'; +import path from 'node:path'; +import fs from 'node:fs'; +import json5 from 'json5'; export const BypassOptionsSchema = Type.Object({ hook: Type.Boolean({ default: true }), @@ -25,6 +28,26 @@ export const NapcatConfigSchema = Type.Object({ export type NapcatConfig = Static; +/** + * 从指定配置目录读取 napcat.json,按 NapcatConfigSchema 校验并填充默认值 + * 用于登录前(无 NapCatCore 实例时)的早期配置读取 + */ +export function loadNapcatConfig (configPath: string): NapcatConfig { + const ajv = new Ajv({ useDefaults: true, coerceTypes: true }); + const validate = ajv.compile(NapcatConfigSchema); + let data: Record = {}; + try { + const configFile = path.join(configPath, 'napcat.json'); + if (fs.existsSync(configFile)) { + data = json5.parse(fs.readFileSync(configFile, 'utf-8')); + } + } catch { + // 读取失败时使用 schema 默认值 + } + validate(data); + return data as NapcatConfig; +} + export class NapCatConfigLoader extends ConfigBase { constructor (core: NapCatCore, configPath: string, schema: AnySchema) { super('napcat', core, configPath, schema); diff --git a/packages/napcat-core/packet/handler/client.ts b/packages/napcat-core/packet/handler/client.ts index 48854b7b..3f3cc276 100644 --- a/packages/napcat-core/packet/handler/client.ts +++ b/packages/napcat-core/packet/handler/client.ts @@ -194,7 +194,7 @@ export class NativePacketHandler { } } - async init (version: string): Promise { + async init (version: string, o3HookMode: boolean = false): Promise { const version_arch = version + '-' + process.arch; try { if (!this.loaded) { @@ -215,7 +215,7 @@ export class NativePacketHandler { this.MoeHooExport.exports.initHook?.(send, recv, (type: PacketType, uin: string, cmd: string, seq: number, hex_data: string) => { this.emitPacket(type, uin, cmd, seq, hex_data); - }, true); + }, o3HookMode); this.logger.log('[PacketHandler] 初始化成功'); return true; } catch (error) { diff --git a/packages/napcat-framework/napcat.ts b/packages/napcat-framework/napcat.ts index e243c342..ecd0ae6b 100644 --- a/packages/napcat-framework/napcat.ts +++ b/packages/napcat-framework/napcat.ts @@ -2,10 +2,8 @@ import { NapCatPathWrapper } from 'napcat-common/src/path'; import { InitWebUi, WebUiConfig, webUiRuntimePort } from 'napcat-webui-backend/index'; import { NapCatAdapterManager } from 'napcat-adapter'; import { NativePacketHandler } from 'napcat-core/packet/handler/client'; -import { Napi2NativeLoader, BypassOptions } from 'napcat-core/packet/handler/napi2nativeLoader'; -import path from 'path'; -import fs from 'fs'; -import json5 from 'json5'; +import { Napi2NativeLoader } from 'napcat-core/packet/handler/napi2nativeLoader'; +import { loadNapcatConfig } from '@/napcat-core/helper/config'; import { FFmpegService } from 'napcat-core/helper/ffmpeg/ffmpeg'; import { logSubscription, LogWrapper } from 'napcat-core/helper/log'; import { QQBasicInfoWrapper } from '@/napcat-core/helper/qq-basic-info'; @@ -45,21 +43,10 @@ export async function NCoreInitFramework ( const wrapper = loadQQWrapper(basicInfoWrapper.QQMainPath, basicInfoWrapper.getFullQQVersion()); const nativePacketHandler = new NativePacketHandler({ logger }); // 初始化 NativePacketHandler 用于后续使用 const napi2nativeLoader = new Napi2NativeLoader({ logger }); // 初始化 Napi2NativeLoader 用于后续使用 + const napcatConfig = loadNapcatConfig(pathWrapper.configPath); //console.log('[NapCat] [Napi2NativeLoader]', napi2nativeLoader.nativeExports.enableAllBypasses?.()); if (process.env['NAPCAT_DISABLE_BYPASS'] !== '1') { - let bypassOptions: BypassOptions = {}; - try { - const configFile = path.join(pathWrapper.configPath, 'napcat.json'); - if (fs.existsSync(configFile)) { - const content = fs.readFileSync(configFile, 'utf-8'); - const config = json5.parse(content); - if (config.bypass && typeof config.bypass === 'object') { - bypassOptions = { ...config.bypass }; - } - } - } catch (e) { - logger.logWarn('[NapCat] 读取 napcat.json bypass 配置失败,已全部禁用:', e); - } + const bypassOptions = napcatConfig.bypass ?? {}; const bypassEnabled = napi2nativeLoader.nativeExports.enableAllBypasses?.(bypassOptions); if (bypassEnabled) { logger.log('[NapCat] Napi2NativeLoader: 已启用Bypass'); @@ -71,7 +58,7 @@ export async function NCoreInitFramework ( // nativePacketHandler.onAll((packet) => { // console.log('[Packet]', packet.uin, packet.cmd, packet.hex_data); // }); - await nativePacketHandler.init(basicInfoWrapper.getFullQQVersion()); + await nativePacketHandler.init(basicInfoWrapper.getFullQQVersion(), napcatConfig.o3HookMode === 1 ? true : false); // 在 init 之后注册监听器 // 初始化 FFmpeg 服务 diff --git a/packages/napcat-shell/base.ts b/packages/napcat-shell/base.ts index 05ebd2b5..1937059c 100644 --- a/packages/napcat-shell/base.ts +++ b/packages/napcat-shell/base.ts @@ -20,7 +20,6 @@ import { hostname, systemVersion } from 'napcat-common/src/system'; import path from 'path'; import fs from 'fs'; import os from 'os'; -import json5 from 'json5'; import { LoginListItem, NodeIKernelLoginService } from 'napcat-core/services'; import qrcode from 'napcat-qrcode/lib/main'; import { NapCatAdapterManager } from 'napcat-adapter'; @@ -31,7 +30,8 @@ import { NodeIO3MiscListener } from 'napcat-core/listeners/NodeIO3MiscListener'; import { sleep } from 'napcat-common/src/helper'; import { FFmpegService } from '@/napcat-core/helper/ffmpeg/ffmpeg'; import { NativePacketHandler } from 'napcat-core/packet/handler/client'; -import { Napi2NativeLoader, BypassOptions } from 'napcat-core/packet/handler/napi2nativeLoader'; +import { Napi2NativeLoader } from 'napcat-core/packet/handler/napi2nativeLoader'; +import { loadNapcatConfig } from '@/napcat-core/helper/config'; import { logSubscription, LogWrapper } from '@/napcat-core/helper/log'; import { proxiedListenerOf } from '@/napcat-core/helper/proxy-handler'; import { QQBasicInfoWrapper } from '@/napcat-core/helper/qq-basic-info'; @@ -39,31 +39,6 @@ import { statusHelperSubscription } from '@/napcat-core/helper/status'; import { applyPendingUpdates } from '@/napcat-webui-backend/src/api/UpdateNapCat'; import { connectToNamedPipe } from './pipe'; -/** - * 读取 napcat.json 配置中的 bypass 选项,并根据分步禁用级别覆盖 - * - * 分步禁用级别 (NAPCAT_BYPASS_DISABLE_LEVEL): - * 0: 使用配置文件原始值(全部启用或用户自定义) - * 1: 强制禁用 hook - * 2: 强制禁用 hook + module - * 3: 强制禁用全部 bypass - */ -function loadBypassConfig (configPath: string, logger: LogWrapper): BypassOptions { - let options: BypassOptions = {}; - try { - const configFile = path.join(configPath, 'napcat.json'); - if (fs.existsSync(configFile)) { - const content = fs.readFileSync(configFile, 'utf-8'); - const config = json5.parse(content); - if (config.bypass && typeof config.bypass === 'object') { - options = { ...config.bypass }; - } - } - } catch (e) { - logger.logWarn('[NapCat] 读取 bypass 配置失败:', e); - } - return options; -} // NapCat Shell App ES 入口文件 async function handleUncaughtExceptions (logger: LogWrapper) { process.on('uncaughtException', (err) => { @@ -427,14 +402,15 @@ export async function NCoreInitShell () { await connectToNamedPipe(logger).catch(e => logger.logError('命名管道连接失败', e)); } const wrapper = loadQQWrapper(basicInfoWrapper.QQMainPath, basicInfoWrapper.getFullQQVersion()); - // wrapper.node 加载后再初始化 hook - await nativePacketHandler.init(basicInfoWrapper.getFullQQVersion()); + // wrapper.node 加载后再初始化 hook,按 schema 读取配置 + const napcatConfig = loadNapcatConfig(pathWrapper.configPath); + await nativePacketHandler.init(basicInfoWrapper.getFullQQVersion(), napcatConfig.o3HookMode === 1 ? true : false); if (process.env['NAPCAT_ENABLE_VERBOSE_LOG'] === '1') { napi2nativeLoader.nativeExports.setVerbose?.(true); } // wrapper.node 加载后立刻启用 Bypass(可通过环境变量禁用) if (process.env['NAPCAT_DISABLE_BYPASS'] !== '1') { - const bypassOptions = loadBypassConfig(pathWrapper.configPath, logger); + const bypassOptions = napcatConfig.bypass ?? {}; logger.logDebug('[NapCat] Bypass 配置:', bypassOptions); const bypassEnabled = napi2nativeLoader.nativeExports.enableAllBypasses?.(bypassOptions); if (bypassEnabled) {