diff --git a/packages/napcat-core/external/napcat.json b/packages/napcat-core/external/napcat.json index 4e4bf7cd..f8249a36 100644 --- a/packages/napcat-core/external/napcat.json +++ b/packages/napcat-core/external/napcat.json @@ -5,5 +5,13 @@ "consoleLogLevel": "info", "packetBackend": "auto", "packetServer": "", - "o3HookMode": 1 + "o3HookMode": 1, + "bypass": { + "hook": true, + "module": true, + "window": true, + "js": true, + "container": true, + "maps": true + } } \ No newline at end of file diff --git a/packages/napcat-core/helper/config.ts b/packages/napcat-core/helper/config.ts index ae911bca..7060abfe 100644 --- a/packages/napcat-core/helper/config.ts +++ b/packages/napcat-core/helper/config.ts @@ -3,6 +3,15 @@ import { NapCatCore } from '@/napcat-core/index'; import { Type, Static } from '@sinclair/typebox'; import { AnySchema } from 'ajv'; +export const BypassOptionsSchema = Type.Object({ + hook: Type.Boolean({ default: true }), + module: Type.Boolean({ default: true }), + window: Type.Boolean({ default: true }), + js: Type.Boolean({ default: true }), + container: Type.Boolean({ default: true }), + maps: Type.Boolean({ default: true }), +}); + export const NapcatConfigSchema = Type.Object({ fileLog: Type.Boolean({ default: false }), consoleLog: Type.Boolean({ default: true }), @@ -11,6 +20,7 @@ export const NapcatConfigSchema = Type.Object({ packetBackend: Type.String({ default: 'auto' }), packetServer: Type.String({ default: '' }), o3HookMode: Type.Number({ default: 0 }), + bypass: Type.Optional(BypassOptionsSchema), }); export type NapcatConfig = Static; diff --git a/packages/napcat-core/packet/handler/napi2nativeLoader.ts b/packages/napcat-core/packet/handler/napi2nativeLoader.ts index 77d33f3b..8e301954 100644 --- a/packages/napcat-core/packet/handler/napi2nativeLoader.ts +++ b/packages/napcat-core/packet/handler/napi2nativeLoader.ts @@ -4,10 +4,19 @@ import fs from 'fs'; import { constants } from 'node:os'; import { LogWrapper } from '../../helper/log'; +export interface BypassOptions { + hook?: boolean; + module?: boolean; + window?: boolean; + js?: boolean; + container?: boolean; + maps?: boolean; +} + export interface Napi2NativeExportType { initHook?: (send: string, recv: string) => boolean; setVerbose?: (verbose: boolean) => void; // 默认关闭日志 - enableAllBypasses?: () => void; + enableAllBypasses?: (options?: BypassOptions) => boolean; } export class Napi2NativeLoader { diff --git a/packages/napcat-framework/napcat.ts b/packages/napcat-framework/napcat.ts index 175801f7..153d110d 100644 --- a/packages/napcat-framework/napcat.ts +++ b/packages/napcat-framework/napcat.ts @@ -2,7 +2,10 @@ 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 } from 'napcat-core/packet/handler/napi2nativeLoader'; +import { Napi2NativeLoader, BypassOptions } from 'napcat-core/packet/handler/napi2nativeLoader'; +import path from 'path'; +import fs from 'fs'; +import json5 from 'json5'; 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'; @@ -44,10 +47,29 @@ export async function NCoreInitFramework ( const napi2nativeLoader = new Napi2NativeLoader({ logger }); // 初始化 Napi2NativeLoader 用于后续使用 //console.log('[NapCat] [Napi2NativeLoader]', napi2nativeLoader.nativeExports.enableAllBypasses?.()); if (process.env['NAPCAT_DISABLE_BYPASS'] !== '1') { - const bypassEnabled = napi2nativeLoader.nativeExports.enableAllBypasses?.(); - if (bypassEnabled) { - logger.log('[NapCat] Napi2NativeLoader: 已启用Bypass'); + // 读取 napcat.json 配置 + let bypassOptions: BypassOptions = { + hook: false, + module: false, + window: false, + js: false, + container: false, + maps: false, + }; + 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 = { ...bypassOptions, ...config.bypass }; + } + } + } catch (e) { + logger.logWarn('[NapCat] 读取 napcat.json bypass 配置失败,已全部禁用:', e); } + const bypassEnabled = napi2nativeLoader.nativeExports.enableAllBypasses?.(bypassOptions); + logger.log('[NapCat] Napi2NativeLoader: Framework模式Bypass配置:', bypassOptions); } else { logger.log('[NapCat] Napi2NativeLoader: Bypass已通过环境变量禁用'); } diff --git a/packages/napcat-framework/package.json b/packages/napcat-framework/package.json index 382efebd..c3325c4e 100644 --- a/packages/napcat-framework/package.json +++ b/packages/napcat-framework/package.json @@ -22,7 +22,8 @@ "napcat-adapter": "workspace:*", "napcat-webui-backend": "workspace:*", "napcat-vite": "workspace:*", - "napcat-qrcode": "workspace:*" + "napcat-qrcode": "workspace:*", + "json5": "^3.2.2" }, "devDependencies": { "@types/node": "^22.0.1" diff --git a/packages/napcat-native/napi2native/ffmpeg.dll b/packages/napcat-native/napi2native/ffmpeg.dll index 89b594e3..8d949050 100644 Binary files a/packages/napcat-native/napi2native/ffmpeg.dll and b/packages/napcat-native/napi2native/ffmpeg.dll differ diff --git a/packages/napcat-native/napi2native/napi2native.linux.arm64.node b/packages/napcat-native/napi2native/napi2native.linux.arm64.node index cc5821df..439a2497 100644 Binary files a/packages/napcat-native/napi2native/napi2native.linux.arm64.node and b/packages/napcat-native/napi2native/napi2native.linux.arm64.node differ diff --git a/packages/napcat-native/napi2native/napi2native.linux.x64.node b/packages/napcat-native/napi2native/napi2native.linux.x64.node index 24a666a9..adceefbd 100644 Binary files a/packages/napcat-native/napi2native/napi2native.linux.x64.node and b/packages/napcat-native/napi2native/napi2native.linux.x64.node differ diff --git a/packages/napcat-native/napi2native/napi2native.win32.x64.node b/packages/napcat-native/napi2native/napi2native.win32.x64.node index a259f1cd..223aeb46 100644 Binary files a/packages/napcat-native/napi2native/napi2native.win32.x64.node and b/packages/napcat-native/napi2native/napi2native.win32.x64.node differ diff --git a/packages/napcat-shell/base.ts b/packages/napcat-shell/base.ts index 5efe12ed..63c6cd51 100644 --- a/packages/napcat-shell/base.ts +++ b/packages/napcat-shell/base.ts @@ -20,6 +20,7 @@ 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'; @@ -30,13 +31,72 @@ 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 } from 'napcat-core/packet/handler/napi2nativeLoader'; +import { Napi2NativeLoader, BypassOptions } from 'napcat-core/packet/handler/napi2nativeLoader'; 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'; 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 { + const defaultOptions: BypassOptions = { + hook: true, + module: true, + window: true, + js: true, + container: true, + maps: true, + }; + + let options = { ...defaultOptions }; + + 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 = { ...defaultOptions, ...config.bypass }; + } + } + } catch (e) { + logger.logWarn('[NapCat] 读取 bypass 配置失败,使用默认值:', e); + } + + // 根据分步禁用级别覆盖配置 + const disableLevel = parseInt(process.env['NAPCAT_BYPASS_DISABLE_LEVEL'] || '0', 10); + if (disableLevel > 0) { + const levelDescriptions = ['全部启用', '禁用 hook', '禁用 hook + module', '全部禁用 bypass']; + logger.logWarn(`[NapCat] 崩溃恢复:当前 bypass 禁用级别 ${disableLevel} (${levelDescriptions[disableLevel] ?? '未知'})`); + + if (disableLevel >= 1) { + options.hook = false; + } + if (disableLevel >= 2) { + options.module = false; + } + if (disableLevel >= 3) { + options.hook = false; + options.module = false; + options.window = false; + options.js = false; + options.container = false; + options.maps = false; + } + } + + return options; +} // NapCat Shell App ES 入口文件 async function handleUncaughtExceptions (logger: LogWrapper) { process.on('uncaughtException', (err) => { @@ -406,7 +466,9 @@ export async function NCoreInitShell () { } // wrapper.node 加载后立刻启用 Bypass(可通过环境变量禁用) if (process.env['NAPCAT_DISABLE_BYPASS'] !== '1') { - const bypassEnabled = napi2nativeLoader.nativeExports.enableAllBypasses?.(); + const bypassOptions = loadBypassConfig(pathWrapper.configPath, logger); + logger.logDebug('[NapCat] Bypass 配置:', bypassOptions); + const bypassEnabled = napi2nativeLoader.nativeExports.enableAllBypasses?.(bypassOptions); if (bypassEnabled) { logger.log('[NapCat] Napi2NativeLoader: 已启用Bypass'); } @@ -463,6 +525,13 @@ export async function NCoreInitShell () { o3Service.reportAmgomWeather('login', 'a1', [dataTimestape, '0', '0']); const selfInfo = await handleLogin(loginService, logger, pathWrapper, quickLoginUin, historyLoginList); + + // 登录成功后通知 Master 进程(用于切换崩溃重试策略) + if (typeof process.send === 'function') { + process.send({ type: 'login-success' }); + logger.log('[NapCat] 已通知主进程登录成功'); + } + const amgomDataPiece = 'eb1fd6ac257461580dc7438eb099f23aae04ca679f4d88f53072dc56e3bb1129'; o3Service.setAmgomDataPiece(basicInfoWrapper.QQVersionAppid, new Uint8Array(Buffer.from(amgomDataPiece, 'hex'))); diff --git a/packages/napcat-shell/napcat.ts b/packages/napcat-shell/napcat.ts index 6e272f09..fc54228a 100644 --- a/packages/napcat-shell/napcat.ts +++ b/packages/napcat-shell/napcat.ts @@ -45,7 +45,7 @@ const ENV = { // Worker 消息类型 interface WorkerMessage { - type: 'restart' | 'restart-prepare' | 'shutdown'; + type: 'restart' | 'restart-prepare' | 'shutdown' | 'login-success'; secretKey?: string; port?: number; } @@ -65,6 +65,16 @@ const recentCrashTimestamps: number[] = []; const CRASH_TIME_WINDOW = 10000; // 10秒时间窗口 const MAX_CRASHES_IN_WINDOW = 3; // 最大崩溃次数 +// 分步禁用策略:记录当前禁用级别 (0-3) +// 0: 全部启用 +// 1: 禁用 hook +// 2: 禁用 hook + module +// 3: 全部禁用 +let bypassDisableLevel = 0; + +// 是否已登录成功(登录后不再使用分步禁用策略) +let isLoggedIn = false; + /** * 获取进程类型名称(用于日志) */ @@ -154,6 +164,8 @@ async function cleanupOrphanedProcesses (excludePids: number[]): Promise { */ export async function restartWorker (secretKey?: string, port?: number): Promise { isRestarting = true; + isLoggedIn = false; + bypassDisableLevel = 0; if (!currentWorker) { logger.logWarn('[NapCat] [Process] 没有运行中的Worker进程'); @@ -246,6 +258,7 @@ async function startWorker (passQuickLogin: boolean = true, secretKey?: string, NAPCAT_WORKER_PROCESS: '1', ...(secretKey ? { NAPCAT_WEBUI_JWT_SECRET_KEY: secretKey } : {}), ...(preferredPort ? { NAPCAT_WEBUI_PREFERRED_PORT: String(preferredPort) } : {}), + ...(bypassDisableLevel > 0 ? { NAPCAT_BYPASS_DISABLE_LEVEL: String(bypassDisableLevel) } : {}), }, stdio: isElectron ? 'pipe' : ['inherit', 'pipe', 'pipe', 'ipc'], }); @@ -275,6 +288,9 @@ async function startWorker (passQuickLogin: boolean = true, secretKey?: string, restartWorker(message.secretKey, message.port).catch(e => { logger.logError(`[NapCat] [${processType}] 重启Worker进程失败:`, e); }); + } else if (message.type === 'login-success') { + isLoggedIn = true; + logger.log(`[NapCat] [${processType}] Worker进程已登录成功,切换到正常重试策略`); } } }); @@ -297,13 +313,34 @@ async function startWorker (passQuickLogin: boolean = true, secretKey?: string, // 记录本次崩溃 recentCrashTimestamps.push(now); - // 检查是否超过崩溃阈值 - if (recentCrashTimestamps.length >= MAX_CRASHES_IN_WINDOW) { - logger.logError(`[NapCat] [${processType}] Worker进程在 ${CRASH_TIME_WINDOW / 1000} 秒内异常退出 ${MAX_CRASHES_IN_WINDOW} 次,主进程退出`); - process.exit(1); + // 登录前:使用分步禁用策略 + if (!isLoggedIn) { + // 每次崩溃提升禁用级别 + bypassDisableLevel = Math.min(bypassDisableLevel + 1, 3); + + const levelDescriptions = [ + '全部启用', + '禁用 hook', + '禁用 hook + module', + '全部禁用 bypass' + ]; + + if (bypassDisableLevel >= 3 && recentCrashTimestamps.length >= MAX_CRASHES_IN_WINDOW) { + logger.logError(`[NapCat] [${processType}] Worker进程在 ${CRASH_TIME_WINDOW / 1000} 秒内异常退出 ${MAX_CRASHES_IN_WINDOW} 次,已尝试全部禁用策略,主进程退出`); + process.exit(1); + } + + logger.logWarn(`[NapCat] [${processType}] Worker进程意外退出 (${recentCrashTimestamps.length}/${MAX_CRASHES_IN_WINDOW}),切换到禁用级别 ${bypassDisableLevel}: ${levelDescriptions[bypassDisableLevel]},正在尝试重新拉起...`); + } else { + // 登录后:使用正常重试策略 + if (recentCrashTimestamps.length >= MAX_CRASHES_IN_WINDOW) { + logger.logError(`[NapCat] [${processType}] Worker进程在 ${CRASH_TIME_WINDOW / 1000} 秒内异常退出 ${MAX_CRASHES_IN_WINDOW} 次,主进程退出`); + process.exit(1); + } + + logger.logWarn(`[NapCat] [${processType}] Worker进程意外退出 (${recentCrashTimestamps.length}/${MAX_CRASHES_IN_WINDOW}),正在尝试重新拉起...`); } - logger.logWarn(`[NapCat] [${processType}] Worker进程意外退出 (${recentCrashTimestamps.length}/${MAX_CRASHES_IN_WINDOW}),正在尝试重新拉起...`); startWorker(true).catch(e => { logger.logError(`[NapCat] [${processType}] 重新拉起Worker进程失败:`, e); }); diff --git a/packages/napcat-shell/package.json b/packages/napcat-shell/package.json index 47ebd68a..5a30079a 100644 --- a/packages/napcat-shell/package.json +++ b/packages/napcat-shell/package.json @@ -25,6 +25,7 @@ "napcat-qrcode": "workspace:*" }, "devDependencies": { + "json5": "^2.2.3", "@types/node": "^22.0.1", "napcat-vite": "workspace:*" }, diff --git a/packages/napcat-webui-backend/src/api/NapCatConfig.ts b/packages/napcat-webui-backend/src/api/NapCatConfig.ts new file mode 100644 index 00000000..affa7950 --- /dev/null +++ b/packages/napcat-webui-backend/src/api/NapCatConfig.ts @@ -0,0 +1,97 @@ +import { RequestHandler } from 'express'; +import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs'; +import { resolve } from 'node:path'; +import { webUiPathWrapper } from '@/napcat-webui-backend/index'; +import { sendError, sendSuccess } from '@/napcat-webui-backend/src/utils/response'; +import json5 from 'json5'; + +// NapCat 配置默认值 +const defaultNapcatConfig = { + fileLog: false, + consoleLog: true, + fileLogLevel: 'debug', + consoleLogLevel: 'info', + packetBackend: 'auto', + packetServer: '', + o3HookMode: 1, + bypass: { + hook: true, + module: true, + window: true, + js: true, + container: true, + maps: true, + }, +}; + +/** + * 获取 napcat 配置文件路径 + */ +function getNapcatConfigPath (): string { + return resolve(webUiPathWrapper.configPath, './napcat.json'); +} + +/** + * 读取 napcat 配置 + */ +function readNapcatConfig (): Record { + const configPath = getNapcatConfigPath(); + try { + if (existsSync(configPath)) { + const content = readFileSync(configPath, 'utf-8'); + return { ...defaultNapcatConfig, ...json5.parse(content) }; + } + } catch (_e) { + // 读取失败,使用默认值 + } + return { ...defaultNapcatConfig }; +} + +/** + * 写入 napcat 配置 + */ +function writeNapcatConfig (config: Record): void { + const configPath = resolve(webUiPathWrapper.configPath, './napcat.json'); + mkdirSync(webUiPathWrapper.configPath, { recursive: true }); + writeFileSync(configPath, JSON.stringify(config, null, 2), 'utf-8'); +} + +// 获取 NapCat 配置 +export const NapCatGetConfigHandler: RequestHandler = (_, res) => { + try { + const config = readNapcatConfig(); + return sendSuccess(res, config); + } catch (e) { + return sendError(res, 'Config Get Error: ' + (e as Error).message); + } +}; + +// 设置 NapCat 配置 +export const NapCatSetConfigHandler: RequestHandler = (req, res) => { + try { + const newConfig = req.body; + if (!newConfig || typeof newConfig !== 'object') { + return sendError(res, 'config is empty or invalid'); + } + + // 读取当前配置并合并 + const currentConfig = readNapcatConfig(); + const mergedConfig = { ...currentConfig, ...newConfig }; + + // 验证 bypass 字段 + if (mergedConfig.bypass && typeof mergedConfig.bypass === 'object') { + const bypass = mergedConfig.bypass as Record; + const validKeys = ['hook', 'module', 'window', 'js', 'container', 'maps']; + for (const key of validKeys) { + if (key in bypass && typeof bypass[key] !== 'boolean') { + return sendError(res, `bypass.${key} must be boolean`); + } + } + } + + writeNapcatConfig(mergedConfig); + return sendSuccess(res, null); + } catch (e) { + return sendError(res, 'Config Set Error: ' + (e as Error).message); + } +}; diff --git a/packages/napcat-webui-backend/src/router/NapCatConfig.ts b/packages/napcat-webui-backend/src/router/NapCatConfig.ts new file mode 100644 index 00000000..15f08f40 --- /dev/null +++ b/packages/napcat-webui-backend/src/router/NapCatConfig.ts @@ -0,0 +1,12 @@ +import { Router } from 'express'; + +import { NapCatGetConfigHandler, NapCatSetConfigHandler } from '@/napcat-webui-backend/src/api/NapCatConfig'; + +const router: Router = Router(); + +// router:获取 NapCat 配置 +router.get('/GetConfig', NapCatGetConfigHandler); +// router:设置 NapCat 配置 +router.post('/SetConfig', NapCatSetConfigHandler); + +export { router as NapCatConfigRouter }; diff --git a/packages/napcat-webui-backend/src/router/index.ts b/packages/napcat-webui-backend/src/router/index.ts index 8c98ff9f..853e48f5 100644 --- a/packages/napcat-webui-backend/src/router/index.ts +++ b/packages/napcat-webui-backend/src/router/index.ts @@ -19,6 +19,7 @@ import DebugRouter from '@/napcat-webui-backend/src/api/Debug'; import { ProcessRouter } from './Process'; import { PluginRouter } from './Plugin'; import { MirrorRouter } from './Mirror'; +import { NapCatConfigRouter } from './NapCatConfig'; const router: Router = Router(); @@ -53,5 +54,7 @@ router.use('/Process', ProcessRouter); router.use('/Plugin', PluginRouter); // router:镜像管理相关路由 router.use('/Mirror', MirrorRouter); +// router:NapCat配置相关路由 +router.use('/NapCatConfig', NapCatConfigRouter); export { router as ALLRouter }; diff --git a/packages/napcat-webui-frontend/src/controllers/qq_manager.ts b/packages/napcat-webui-frontend/src/controllers/qq_manager.ts index 88826932..b0da8488 100644 --- a/packages/napcat-webui-frontend/src/controllers/qq_manager.ts +++ b/packages/napcat-webui-frontend/src/controllers/qq_manager.ts @@ -178,5 +178,23 @@ export default class QQManager { public static async resetLinuxDeviceID () { await serverRequest.post>('/QQLogin/ResetLinuxDeviceID'); } + + // ============================================================ + // NapCat 配置管理 + // ============================================================ + + public static async getNapCatConfig () { + const { data } = await serverRequest.get>( + '/NapCatConfig/GetConfig' + ); + return data.data; + } + + public static async setNapCatConfig (config: Partial) { + await serverRequest.post>( + '/NapCatConfig/SetConfig', + config + ); + } } diff --git a/packages/napcat-webui-frontend/src/pages/dashboard/config/bypass.tsx b/packages/napcat-webui-frontend/src/pages/dashboard/config/bypass.tsx new file mode 100644 index 00000000..bca17915 --- /dev/null +++ b/packages/napcat-webui-frontend/src/pages/dashboard/config/bypass.tsx @@ -0,0 +1,169 @@ +import { useEffect, useState } from 'react'; +import { Controller, useForm } from 'react-hook-form'; +import toast from 'react-hot-toast'; + +import SaveButtons from '@/components/button/save_buttons'; +import PageLoading from '@/components/page_loading'; +import SwitchCard from '@/components/switch_card'; + +import QQManager from '@/controllers/qq_manager'; + +interface BypassFormData { + hook: boolean; + module: boolean; + window: boolean; + js: boolean; + container: boolean; + maps: boolean; +} + +const defaultBypass: BypassFormData = { + hook: true, + module: true, + window: true, + js: true, + container: true, + maps: true, +}; + +const BypassConfigCard = () => { + const [loading, setLoading] = useState(true); + const { + control, + handleSubmit, + formState: { isSubmitting }, + setValue, + } = useForm({ + defaultValues: defaultBypass, + }); + + const loadConfig = async (showTip = false) => { + try { + setLoading(true); + const config = await QQManager.getNapCatConfig(); + const bypass = config.bypass ?? defaultBypass; + setValue('hook', bypass.hook ?? true); + setValue('module', bypass.module ?? true); + setValue('window', bypass.window ?? true); + setValue('js', bypass.js ?? true); + setValue('container', bypass.container ?? true); + setValue('maps', bypass.maps ?? true); + if (showTip) toast.success('刷新成功'); + } catch (error) { + const msg = (error as Error).message; + toast.error(`获取配置失败: ${msg}`); + } finally { + setLoading(false); + } + }; + + const onSubmit = handleSubmit(async (data) => { + try { + await QQManager.setNapCatConfig({ bypass: data }); + toast.success('保存成功,重启后生效'); + } catch (error) { + const msg = (error as Error).message; + toast.error(`保存失败: ${msg}`); + } + }); + + const onReset = () => { + loadConfig(); + }; + + const onRefresh = async () => { + await loadConfig(true); + }; + + useEffect(() => { + loadConfig(); + }, []); + + if (loading) return ; + + return ( + <> + 反检测配置 - NapCat WebUI +
+

反检测开关配置

+

+ 控制 Napi2Native 模块的各项反检测功能,修改后需重启生效。 +

+
+ ( + + )} + /> + ( + + )} + /> + ( + + )} + /> + ( + + )} + /> + ( + + )} + /> + ( + + )} + /> + + + ); +}; + +export default BypassConfigCard; diff --git a/packages/napcat-webui-frontend/src/pages/dashboard/config/index.tsx b/packages/napcat-webui-frontend/src/pages/dashboard/config/index.tsx index 1274b879..0180fe4b 100644 --- a/packages/napcat-webui-frontend/src/pages/dashboard/config/index.tsx +++ b/packages/napcat-webui-frontend/src/pages/dashboard/config/index.tsx @@ -14,6 +14,7 @@ import SSLConfigCard from './ssl'; import ThemeConfigCard from './theme'; import WebUIConfigCard from './webui'; import BackupConfigCard from './backup'; +import BypassConfigCard from './bypass'; export interface ConfigPageProps { children?: React.ReactNode; @@ -114,6 +115,11 @@ export default function ConfigPage () { + + + + + ); diff --git a/packages/napcat-webui-frontend/src/types/napcat_conf.d.ts b/packages/napcat-webui-frontend/src/types/napcat_conf.d.ts new file mode 100644 index 00000000..fd52fa40 --- /dev/null +++ b/packages/napcat-webui-frontend/src/types/napcat_conf.d.ts @@ -0,0 +1,19 @@ +interface BypassOptions { + hook: boolean; + module: boolean; + window: boolean; + js: boolean; + container: boolean; + maps: boolean; +} + +interface NapCatConfig { + fileLog: boolean; + consoleLog: boolean; + fileLogLevel: string; + consoleLogLevel: string; + packetBackend: string; + packetServer: string; + o3HookMode: number; + bypass?: BypassOptions; +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9f6d8116..885a3776 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -136,6 +136,9 @@ importers: packages/napcat-framework: dependencies: + json5: + specifier: ^2.2.3 + version: 2.2.3 napcat-adapter: specifier: workspace:* version: link:../napcat-adapter @@ -357,6 +360,9 @@ importers: '@types/node': specifier: ^22.0.1 version: 22.19.1 + json5: + specifier: ^2.2.3 + version: 2.2.3 napcat-vite: specifier: workspace:* version: link:../napcat-vite @@ -1907,89 +1913,105 @@ packages: resolution: {integrity: sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==} cpu: [arm64] os: [linux] + libc: [glibc] '@img/sharp-libvips-linux-arm@1.2.4': resolution: {integrity: sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==} cpu: [arm] os: [linux] + libc: [glibc] '@img/sharp-libvips-linux-ppc64@1.2.4': resolution: {integrity: sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==} cpu: [ppc64] os: [linux] + libc: [glibc] '@img/sharp-libvips-linux-riscv64@1.2.4': resolution: {integrity: sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==} cpu: [riscv64] os: [linux] + libc: [glibc] '@img/sharp-libvips-linux-s390x@1.2.4': resolution: {integrity: sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==} cpu: [s390x] os: [linux] + libc: [glibc] '@img/sharp-libvips-linux-x64@1.2.4': resolution: {integrity: sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==} cpu: [x64] os: [linux] + libc: [glibc] '@img/sharp-libvips-linuxmusl-arm64@1.2.4': resolution: {integrity: sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==} cpu: [arm64] os: [linux] + libc: [musl] '@img/sharp-libvips-linuxmusl-x64@1.2.4': resolution: {integrity: sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==} cpu: [x64] os: [linux] + libc: [musl] '@img/sharp-linux-arm64@0.34.5': resolution: {integrity: sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm64] os: [linux] + libc: [glibc] '@img/sharp-linux-arm@0.34.5': resolution: {integrity: sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm] os: [linux] + libc: [glibc] '@img/sharp-linux-ppc64@0.34.5': resolution: {integrity: sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [ppc64] os: [linux] + libc: [glibc] '@img/sharp-linux-riscv64@0.34.5': resolution: {integrity: sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [riscv64] os: [linux] + libc: [glibc] '@img/sharp-linux-s390x@0.34.5': resolution: {integrity: sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [s390x] os: [linux] + libc: [glibc] '@img/sharp-linux-x64@0.34.5': resolution: {integrity: sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [linux] + libc: [glibc] '@img/sharp-linuxmusl-arm64@0.34.5': resolution: {integrity: sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm64] os: [linux] + libc: [musl] '@img/sharp-linuxmusl-x64@0.34.5': resolution: {integrity: sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [linux] + libc: [musl] '@img/sharp-wasm32@0.34.5': resolution: {integrity: sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==} @@ -2740,56 +2762,67 @@ packages: resolution: {integrity: sha512-EPlb95nUsz6Dd9Qy13fI5kUPXNSljaG9FiJ4YUGU1O/Q77i5DYFW5KR8g1OzTcdZUqQQ1KdDqsTohdFVwCwjqg==} cpu: [arm] os: [linux] + libc: [glibc] '@rollup/rollup-linux-arm-musleabihf@4.53.2': resolution: {integrity: sha512-BOmnVW+khAUX+YZvNfa0tGTEMVVEerOxN0pDk2E6N6DsEIa2Ctj48FOMfNDdrwinocKaC7YXUZ1pHlKpnkja/Q==} cpu: [arm] os: [linux] + libc: [musl] '@rollup/rollup-linux-arm64-gnu@4.53.2': resolution: {integrity: sha512-Xt2byDZ+6OVNuREgBXr4+CZDJtrVso5woFtpKdGPhpTPHcNG7D8YXeQzpNbFRxzTVqJf7kvPMCub/pcGUWgBjA==} cpu: [arm64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-arm64-musl@4.53.2': resolution: {integrity: sha512-+LdZSldy/I9N8+klim/Y1HsKbJ3BbInHav5qE9Iy77dtHC/pibw1SR/fXlWyAk0ThnpRKoODwnAuSjqxFRDHUQ==} cpu: [arm64] os: [linux] + libc: [musl] '@rollup/rollup-linux-loong64-gnu@4.53.2': resolution: {integrity: sha512-8ms8sjmyc1jWJS6WdNSA23rEfdjWB30LH8Wqj0Cqvv7qSHnvw6kgMMXRdop6hkmGPlyYBdRPkjJnj3KCUHV/uQ==} cpu: [loong64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-ppc64-gnu@4.53.2': resolution: {integrity: sha512-3HRQLUQbpBDMmzoxPJYd3W6vrVHOo2cVW8RUo87Xz0JPJcBLBr5kZ1pGcQAhdZgX9VV7NbGNipah1omKKe23/g==} cpu: [ppc64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-riscv64-gnu@4.53.2': resolution: {integrity: sha512-fMjKi+ojnmIvhk34gZP94vjogXNNUKMEYs+EDaB/5TG/wUkoeua7p7VCHnE6T2Tx+iaghAqQX8teQzcvrYpaQA==} cpu: [riscv64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-riscv64-musl@4.53.2': resolution: {integrity: sha512-XuGFGU+VwUUV5kLvoAdi0Wz5Xbh2SrjIxCtZj6Wq8MDp4bflb/+ThZsVxokM7n0pcbkEr2h5/pzqzDYI7cCgLQ==} cpu: [riscv64] os: [linux] + libc: [musl] '@rollup/rollup-linux-s390x-gnu@4.53.2': resolution: {integrity: sha512-w6yjZF0P+NGzWR3AXWX9zc0DNEGdtvykB03uhonSHMRa+oWA6novflo2WaJr6JZakG2ucsyb+rvhrKac6NIy+w==} cpu: [s390x] os: [linux] + libc: [glibc] '@rollup/rollup-linux-x64-gnu@4.53.2': resolution: {integrity: sha512-yo8d6tdfdeBArzC7T/PnHd7OypfI9cbuZzPnzLJIyKYFhAQ8SvlkKtKBMbXDxe1h03Rcr7u++nFS7tqXz87Gtw==} cpu: [x64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-x64-musl@4.53.2': resolution: {integrity: sha512-ah59c1YkCxKExPP8O9PwOvs+XRLKwh/mV+3YdKqQ5AMQ0r4M4ZDuOrpWkUaqO7fzAHdINzV9tEVu8vNw48z0lA==} cpu: [x64] os: [linux] + libc: [musl] '@rollup/rollup-openharmony-arm64@4.53.2': resolution: {integrity: sha512-4VEd19Wmhr+Zy7hbUsFZ6YXEiP48hE//KPLCSVNY5RMGX2/7HZ+QkN55a3atM1C/BZCGIgqN+xrVgtdak2S9+A==} @@ -2864,24 +2897,28 @@ packages: engines: {node: '>=10'} cpu: [arm64] os: [linux] + libc: [glibc] '@swc/core-linux-arm64-musl@1.15.1': resolution: {integrity: sha512-fKzP9mRQGbhc5QhJPIsqKNNX/jyWrZgBxmo3Nz1SPaepfCUc7RFmtcJQI5q8xAun3XabXjh90wqcY/OVyg2+Kg==} engines: {node: '>=10'} cpu: [arm64] os: [linux] + libc: [musl] '@swc/core-linux-x64-gnu@1.15.1': resolution: {integrity: sha512-ZLjMi138uTJxb+1wzo4cB8mIbJbAsSLWRNeHc1g1pMvkERPWOGlem+LEYkkzaFzCNv1J8aKcL653Vtw8INHQeg==} engines: {node: '>=10'} cpu: [x64] os: [linux] + libc: [glibc] '@swc/core-linux-x64-musl@1.15.1': resolution: {integrity: sha512-jvSI1IdsIYey5kOITzyajjofXOOySVitmLxb45OPUjoNojql4sDojvlW5zoHXXFePdA6qAX4Y6KbzAOV3T3ctA==} engines: {node: '>=10'} cpu: [x64] os: [linux] + libc: [musl] '@swc/core-win32-arm64-msvc@1.15.1': resolution: {integrity: sha512-X/FcDtNrDdY9r4FcXHt9QxUqC/2FbQdvZobCKHlHe8vTSKhUHOilWl5EBtkFVfsEs4D5/yAri9e3bJbwyBhhBw==} @@ -3246,41 +3283,49 @@ packages: resolution: {integrity: sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ==} cpu: [arm64] os: [linux] + libc: [glibc] '@unrs/resolver-binding-linux-arm64-musl@1.11.1': resolution: {integrity: sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w==} cpu: [arm64] os: [linux] + libc: [musl] '@unrs/resolver-binding-linux-ppc64-gnu@1.11.1': resolution: {integrity: sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA==} cpu: [ppc64] os: [linux] + libc: [glibc] '@unrs/resolver-binding-linux-riscv64-gnu@1.11.1': resolution: {integrity: sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ==} cpu: [riscv64] os: [linux] + libc: [glibc] '@unrs/resolver-binding-linux-riscv64-musl@1.11.1': resolution: {integrity: sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew==} cpu: [riscv64] os: [linux] + libc: [musl] '@unrs/resolver-binding-linux-s390x-gnu@1.11.1': resolution: {integrity: sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg==} cpu: [s390x] os: [linux] + libc: [glibc] '@unrs/resolver-binding-linux-x64-gnu@1.11.1': resolution: {integrity: sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w==} cpu: [x64] os: [linux] + libc: [glibc] '@unrs/resolver-binding-linux-x64-musl@1.11.1': resolution: {integrity: sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA==} cpu: [x64] os: [linux] + libc: [musl] '@unrs/resolver-binding-wasm32-wasi@1.11.1': resolution: {integrity: sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ==}