Skip to content

Commit b9f61cc

Browse files
committed
Add configurable bypass options and UI
Introduce granular "bypass" configuration to control Napi2Native bypass features and expose it in the WebUI. Key changes: - Add bypass defaults to packages/napcat-core/external/napcat.json and BypassOptionsSchema in napcat-core helper config. - Extend Napi2NativeLoader types: enableAllBypasses now accepts BypassOptions. - Framework & Shell: load napcat.json (via json5), pass parsed bypass options to native loader, and log the applied config. Add json5 dependency. - Shell: implement loadBypassConfig with a crash-recovery override (NAPCAT_BYPASS_DISABLE_LEVEL) and add master<->worker IPC (login-success) plus progressive bypass-disable strategy to mitigate repeated crashes before login. - WebUI backend: add GET/Set endpoints for NapCat config (NapCatConfigRouter) with validation and JSON5-aware defaults. - WebUI frontend: add BypassConfig page, types, and controller methods to get/set bypass config. - Update package.json to include json5 and update pnpm lockfile; native binaries (.node / ffmpeg.dll) also updated. This enables operators to tune bypass behavior per-installation and to have an in-UI control for toggling anti-detection features; it also adds progressive fallback behavior to help recover from crashes caused by bypasses.
1 parent 9998207 commit b9f61cc

20 files changed

Lines changed: 541 additions & 15 deletions

File tree

packages/napcat-core/external/napcat.json

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,13 @@
55
"consoleLogLevel": "info",
66
"packetBackend": "auto",
77
"packetServer": "",
8-
"o3HookMode": 1
8+
"o3HookMode": 1,
9+
"bypass": {
10+
"hook": true,
11+
"module": true,
12+
"window": true,
13+
"js": true,
14+
"container": true,
15+
"maps": true
16+
}
917
}

packages/napcat-core/helper/config.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,15 @@ import { NapCatCore } from '@/napcat-core/index';
33
import { Type, Static } from '@sinclair/typebox';
44
import { AnySchema } from 'ajv';
55

6+
export const BypassOptionsSchema = Type.Object({
7+
hook: Type.Boolean({ default: true }),
8+
module: Type.Boolean({ default: true }),
9+
window: Type.Boolean({ default: true }),
10+
js: Type.Boolean({ default: true }),
11+
container: Type.Boolean({ default: true }),
12+
maps: Type.Boolean({ default: true }),
13+
});
14+
615
export const NapcatConfigSchema = Type.Object({
716
fileLog: Type.Boolean({ default: false }),
817
consoleLog: Type.Boolean({ default: true }),
@@ -11,6 +20,7 @@ export const NapcatConfigSchema = Type.Object({
1120
packetBackend: Type.String({ default: 'auto' }),
1221
packetServer: Type.String({ default: '' }),
1322
o3HookMode: Type.Number({ default: 0 }),
23+
bypass: Type.Optional(BypassOptionsSchema),
1424
});
1525

1626
export type NapcatConfig = Static<typeof NapcatConfigSchema>;

packages/napcat-core/packet/handler/napi2nativeLoader.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,19 @@ import fs from 'fs';
44
import { constants } from 'node:os';
55
import { LogWrapper } from '../../helper/log';
66

7+
export interface BypassOptions {
8+
hook?: boolean;
9+
module?: boolean;
10+
window?: boolean;
11+
js?: boolean;
12+
container?: boolean;
13+
maps?: boolean;
14+
}
15+
716
export interface Napi2NativeExportType {
817
initHook?: (send: string, recv: string) => boolean;
918
setVerbose?: (verbose: boolean) => void; // 默认关闭日志
10-
enableAllBypasses?: () => void;
19+
enableAllBypasses?: (options?: BypassOptions) => boolean;
1120
}
1221

1322
export class Napi2NativeLoader {

packages/napcat-framework/napcat.ts

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@ import { NapCatPathWrapper } from 'napcat-common/src/path';
22
import { InitWebUi, WebUiConfig, webUiRuntimePort } from 'napcat-webui-backend/index';
33
import { NapCatAdapterManager } from 'napcat-adapter';
44
import { NativePacketHandler } from 'napcat-core/packet/handler/client';
5-
import { Napi2NativeLoader } from 'napcat-core/packet/handler/napi2nativeLoader';
5+
import { Napi2NativeLoader, BypassOptions } from 'napcat-core/packet/handler/napi2nativeLoader';
6+
import path from 'path';
7+
import fs from 'fs';
8+
import json5 from 'json5';
69
import { FFmpegService } from 'napcat-core/helper/ffmpeg/ffmpeg';
710
import { logSubscription, LogWrapper } from 'napcat-core/helper/log';
811
import { QQBasicInfoWrapper } from '@/napcat-core/helper/qq-basic-info';
@@ -44,10 +47,29 @@ export async function NCoreInitFramework (
4447
const napi2nativeLoader = new Napi2NativeLoader({ logger }); // 初始化 Napi2NativeLoader 用于后续使用
4548
//console.log('[NapCat] [Napi2NativeLoader]', napi2nativeLoader.nativeExports.enableAllBypasses?.());
4649
if (process.env['NAPCAT_DISABLE_BYPASS'] !== '1') {
47-
const bypassEnabled = napi2nativeLoader.nativeExports.enableAllBypasses?.();
48-
if (bypassEnabled) {
49-
logger.log('[NapCat] Napi2NativeLoader: 已启用Bypass');
50+
// 读取 napcat.json 配置
51+
let bypassOptions: BypassOptions = {
52+
hook: false,
53+
module: false,
54+
window: false,
55+
js: false,
56+
container: false,
57+
maps: false,
58+
};
59+
try {
60+
const configFile = path.join(pathWrapper.configPath, 'napcat.json');
61+
if (fs.existsSync(configFile)) {
62+
const content = fs.readFileSync(configFile, 'utf-8');
63+
const config = json5.parse(content);
64+
if (config.bypass && typeof config.bypass === 'object') {
65+
bypassOptions = { ...bypassOptions, ...config.bypass };
66+
}
67+
}
68+
} catch (e) {
69+
logger.logWarn('[NapCat] 读取 napcat.json bypass 配置失败,已全部禁用:', e);
5070
}
71+
const bypassEnabled = napi2nativeLoader.nativeExports.enableAllBypasses?.(bypassOptions);
72+
logger.log('[NapCat] Napi2NativeLoader: Framework模式Bypass配置:', bypassOptions);
5173
} else {
5274
logger.log('[NapCat] Napi2NativeLoader: Bypass已通过环境变量禁用');
5375
}

packages/napcat-framework/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@
2222
"napcat-adapter": "workspace:*",
2323
"napcat-webui-backend": "workspace:*",
2424
"napcat-vite": "workspace:*",
25-
"napcat-qrcode": "workspace:*"
25+
"napcat-qrcode": "workspace:*",
26+
"json5": "^3.2.2"
2627
},
2728
"devDependencies": {
2829
"@types/node": "^22.0.1"
0 Bytes
Binary file not shown.
4.09 KB
Binary file not shown.
0 Bytes
Binary file not shown.
-15.5 KB
Binary file not shown.

packages/napcat-shell/base.ts

Lines changed: 71 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import { hostname, systemVersion } from 'napcat-common/src/system';
2020
import path from 'path';
2121
import fs from 'fs';
2222
import os from 'os';
23+
import json5 from 'json5';
2324
import { LoginListItem, NodeIKernelLoginService } from 'napcat-core/services';
2425
import qrcode from 'napcat-qrcode/lib/main';
2526
import { NapCatAdapterManager } from 'napcat-adapter';
@@ -30,13 +31,72 @@ import { NodeIO3MiscListener } from 'napcat-core/listeners/NodeIO3MiscListener';
3031
import { sleep } from 'napcat-common/src/helper';
3132
import { FFmpegService } from '@/napcat-core/helper/ffmpeg/ffmpeg';
3233
import { NativePacketHandler } from 'napcat-core/packet/handler/client';
33-
import { Napi2NativeLoader } from 'napcat-core/packet/handler/napi2nativeLoader';
34+
import { Napi2NativeLoader, BypassOptions } from 'napcat-core/packet/handler/napi2nativeLoader';
3435
import { logSubscription, LogWrapper } from '@/napcat-core/helper/log';
3536
import { proxiedListenerOf } from '@/napcat-core/helper/proxy-handler';
3637
import { QQBasicInfoWrapper } from '@/napcat-core/helper/qq-basic-info';
3738
import { statusHelperSubscription } from '@/napcat-core/helper/status';
3839
import { applyPendingUpdates } from '@/napcat-webui-backend/src/api/UpdateNapCat';
3940
import { connectToNamedPipe } from './pipe';
41+
42+
/**
43+
* 读取 napcat.json 配置中的 bypass 选项,并根据分步禁用级别覆盖
44+
*
45+
* 分步禁用级别 (NAPCAT_BYPASS_DISABLE_LEVEL):
46+
* 0: 使用配置文件原始值(全部启用或用户自定义)
47+
* 1: 强制禁用 hook
48+
* 2: 强制禁用 hook + module
49+
* 3: 强制禁用全部 bypass
50+
*/
51+
function loadBypassConfig (configPath: string, logger: LogWrapper): BypassOptions {
52+
const defaultOptions: BypassOptions = {
53+
hook: true,
54+
module: true,
55+
window: true,
56+
js: true,
57+
container: true,
58+
maps: true,
59+
};
60+
61+
let options = { ...defaultOptions };
62+
63+
try {
64+
const configFile = path.join(configPath, 'napcat.json');
65+
if (fs.existsSync(configFile)) {
66+
const content = fs.readFileSync(configFile, 'utf-8');
67+
const config = json5.parse(content);
68+
if (config.bypass && typeof config.bypass === 'object') {
69+
options = { ...defaultOptions, ...config.bypass };
70+
}
71+
}
72+
} catch (e) {
73+
logger.logWarn('[NapCat] 读取 bypass 配置失败,使用默认值:', e);
74+
}
75+
76+
// 根据分步禁用级别覆盖配置
77+
const disableLevel = parseInt(process.env['NAPCAT_BYPASS_DISABLE_LEVEL'] || '0', 10);
78+
if (disableLevel > 0) {
79+
const levelDescriptions = ['全部启用', '禁用 hook', '禁用 hook + module', '全部禁用 bypass'];
80+
logger.logWarn(`[NapCat] 崩溃恢复:当前 bypass 禁用级别 ${disableLevel} (${levelDescriptions[disableLevel] ?? '未知'})`);
81+
82+
if (disableLevel >= 1) {
83+
options.hook = false;
84+
}
85+
if (disableLevel >= 2) {
86+
options.module = false;
87+
}
88+
if (disableLevel >= 3) {
89+
options.hook = false;
90+
options.module = false;
91+
options.window = false;
92+
options.js = false;
93+
options.container = false;
94+
options.maps = false;
95+
}
96+
}
97+
98+
return options;
99+
}
40100
// NapCat Shell App ES 入口文件
41101
async function handleUncaughtExceptions (logger: LogWrapper) {
42102
process.on('uncaughtException', (err) => {
@@ -406,7 +466,9 @@ export async function NCoreInitShell () {
406466
}
407467
// wrapper.node 加载后立刻启用 Bypass(可通过环境变量禁用)
408468
if (process.env['NAPCAT_DISABLE_BYPASS'] !== '1') {
409-
const bypassEnabled = napi2nativeLoader.nativeExports.enableAllBypasses?.();
469+
const bypassOptions = loadBypassConfig(pathWrapper.configPath, logger);
470+
logger.logDebug('[NapCat] Bypass 配置:', bypassOptions);
471+
const bypassEnabled = napi2nativeLoader.nativeExports.enableAllBypasses?.(bypassOptions);
410472
if (bypassEnabled) {
411473
logger.log('[NapCat] Napi2NativeLoader: 已启用Bypass');
412474
}
@@ -463,6 +525,13 @@ export async function NCoreInitShell () {
463525
o3Service.reportAmgomWeather('login', 'a1', [dataTimestape, '0', '0']);
464526

465527
const selfInfo = await handleLogin(loginService, logger, pathWrapper, quickLoginUin, historyLoginList);
528+
529+
// 登录成功后通知 Master 进程(用于切换崩溃重试策略)
530+
if (typeof process.send === 'function') {
531+
process.send({ type: 'login-success' });
532+
logger.log('[NapCat] 已通知主进程登录成功');
533+
}
534+
466535
const amgomDataPiece = 'eb1fd6ac257461580dc7438eb099f23aae04ca679f4d88f53072dc56e3bb1129';
467536
o3Service.setAmgomDataPiece(basicInfoWrapper.QQVersionAppid, new Uint8Array(Buffer.from(amgomDataPiece, 'hex')));
468537

0 commit comments

Comments
 (0)