Load napcat.json, use o3HookMode & bypass

Add loadNapcatConfig helper that reads napcat.json (json5), validates with Ajv and fills defaults for early pre-login use. Change NativePacketHandler.init signature to accept an o3HookMode flag and forward it to native initHook. Update framework and shell to use loadNapcatConfig (remove duplicated file-reading logic) and pass configured o3HookMode and bypass options into Napi2NativeLoader/native packet initialization. Clean up imports (Ajv, path, fs, json5) and remove the old loadBypassConfig helper.
This commit is contained in:
手瓜一十雪
2026-02-20 21:49:19 +08:00
parent 052e7fa2b3
commit 5fec649425
4 changed files with 37 additions and 51 deletions

View File

@@ -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<typeof NapcatConfigSchema>;
/**
* 从指定配置目录读取 napcat.json按 NapcatConfigSchema 校验并填充默认值
* 用于登录前(无 NapCatCore 实例时)的早期配置读取
*/
export function loadNapcatConfig (configPath: string): NapcatConfig {
const ajv = new Ajv({ useDefaults: true, coerceTypes: true });
const validate = ajv.compile<NapcatConfig>(NapcatConfigSchema);
let data: Record<string, unknown> = {};
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<NapcatConfig> {
constructor (core: NapCatCore, configPath: string, schema: AnySchema) {
super('napcat', core, configPath, schema);

View File

@@ -194,7 +194,7 @@ export class NativePacketHandler {
}
}
async init (version: string): Promise<boolean> {
async init (version: string, o3HookMode: boolean = false): Promise<boolean> {
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) {

View File

@@ -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 服务

View File

@@ -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) {