refactor: 整体重构 (#1381)

* feat: pnpm new

* Refactor build and release workflows, update dependencies

Switch build scripts and workflows from npm to pnpm, update build and artifact paths, and simplify release workflow by removing version detection and changelog steps. Add new dependencies (silk-wasm, express, ws, node-pty-prebuilt-multiarch), update exports in package.json files, and add vite config for napcat-framework. Also, rename manifest.json for framework package and fix static asset copying in shell build config.
This commit is contained in:
手瓜一十雪
2025-11-13 15:39:42 +08:00
committed by GitHub
parent c3d1892545
commit ed19c52f25
778 changed files with 2356 additions and 26391 deletions

View File

@@ -0,0 +1,19 @@
// LiteLoader需要提供部分IPC接口以便于其他插件调用
const { ipcMain, BrowserWindow } = require('electron');
const napcat = require('./napcat.cjs');
const { shell } = require('electron');
ipcMain.handle('napcat_get_webui', async () => {
return napcat.NCgetWebUiUrl();
});
ipcMain.on('open_external_url', (event, url) => {
shell.openExternal(url);
});
ipcMain.on('napcat_open_inner_url', (event, url) => {
const win = new BrowserWindow({
autoHideMenuBar: true,
});
win.loadURL(url);
win.webContents.setWindowOpenHandler(details => {
win.loadURL(details.url);
});
});

View File

@@ -0,0 +1,29 @@
{
"manifest_version": 4,
"type": "extension",
"name": "NapCatQQ",
"slug": "NapCat.Framework",
"description": "高性能的 OneBot 11 协议实现",
"version": "4.9.42",
"icon": "./logo.png",
"authors": [
{
"name": "NapNeko",
"link": "https://github.com/NapNeko"
}
],
"repository": {
"repo": "NapNeko/NapCatQQ",
"branch": "main"
},
"platform": [
"win32",
"linux",
"darwin"
],
"injects": {
"renderer": "./renderer.js",
"main": "./liteloader.cjs",
"preload": "./preload.cjs"
}
}

View File

@@ -0,0 +1,92 @@
const process = require('process');
const os = require('os');
const path = require('path');
// 保存原始dlopen
const dlopenOrig = process.dlopen;
let wrapperSession;
let wrapperNodeApi;
let wrapperLoginService;
let getWebUiUrlFunc;
let ncCallback = () => { };
let napCatInitialized = false; // 添加一个标志
function createServiceProxy (ServiceName) {
return new Proxy(() => { }, {
get: (target, FunctionName) => {
if (ServiceName === 'NodeIQQNTWrapperSession' && FunctionName === 'create') {
return () => new Proxy({}, {
get: function (target, ClassFunName, receiver) {
return function () {
if (ClassFunName === 'init') {
const origin = arguments[3].onOpentelemetryInit;
arguments[3].onOpentelemetryInit = function (result) {
origin(...arguments);
if (result.is_init) {
ncCallback();
}
};
}
const ret = wrapperSession[ClassFunName](...arguments);
return ret;
};
},
});
}
if (ServiceName === 'NodeIKernelLoginService' && FunctionName === 'get') {
return () => wrapperLoginService;
}
return wrapperNodeApi[ServiceName][FunctionName];
},
});
}
function clearHook () {
process.dlopen = dlopenOrig;
}
async function initializeNapCat () {
console.log('[NapCat] [Info] 开始初始化NapCat');
try {
const currentPath = path.dirname(__filename);
const { NCoreInitFramework, getWebUiUrl } = await import('file://' + path.join(currentPath, './napcat.mjs'));
getWebUiUrlFunc = getWebUiUrl;
await NCoreInitFramework(wrapperSession, wrapperLoginService, (callback) => { ncCallback = callback; });
} catch (error) {
console.log('[NapCat] [Error] 初始化NapCat', error);
}
}
process.dlopen = function (module, filename, flags = os.constants.dlopen.RTLD_LAZY) {
const dlopenRet = dlopenOrig(module, filename, flags);
if (!filename.includes('wrapper.node') || napCatInitialized) return dlopenRet;
napCatInitialized = true; // 初始化完成后设置标志
clearHook();
wrapperNodeApi = module.exports;
wrapperLoginService = wrapperNodeApi.NodeIKernelLoginService.get();
wrapperSession = wrapperNodeApi.NodeIQQNTWrapperSession.create();
initializeNapCat().then().catch();
module.exports = new Proxy({}, {
get: (target, ServiceName) => {
if (ServiceName === 'NodeIKernelLoginService' || ServiceName === 'NodeIQQNTWrapperSession') {
return createServiceProxy(ServiceName);
}
return wrapperNodeApi[ServiceName];
},
});
return dlopenRet;
};
module.exports = {
NCgetWebUiUrl: async () => {
if (!getWebUiUrlFunc) {
console.log('[NapCat] [Error] 未初始化完成');
return '';
}
return await getWebUiUrlFunc();
},
};

View File

@@ -0,0 +1,110 @@
import { NapCatPathWrapper } from 'napcat-common/src/path';
import { LogWrapper } from 'napcat-common/src/log';
import { proxiedListenerOf } from 'napcat-common/src/proxy-handler';
import { QQBasicInfoWrapper } from 'napcat-common/src/qq-basic-info';
import { InstanceContext, loadQQWrapper, NapCatCore, NapCatCoreWorkingEnv } from 'napcat-core/index';
import { SelfInfo } from 'napcat-core/types';
import { NodeIKernelLoginListener } from 'napcat-core/listeners';
import { NodeIKernelLoginService } from 'napcat-core/services';
import { NodeIQQNTWrapperSession, WrapperNodeApi } from 'napcat-core/wrapper';
import { InitWebUi, WebUiConfig, webUiRuntimePort } from 'napcat-webui-backend/src/index';
import { NapCatOneBot11Adapter } from 'napcat-onebot/index';
import { FFmpegService } from 'napcat-common/src/ffmpeg';
import { NativePacketHandler } from 'napcat-core/packet/handler/client';
// Framework ES入口文件
export async function getWebUiUrl () {
const WebUiConfigData = (await WebUiConfig.GetWebUIConfig());
return 'http://127.0.0.1:' + webUiRuntimePort + '/webui/?token=' + encodeURIComponent(WebUiConfigData.token);
}
export async function NCoreInitFramework (
session: NodeIQQNTWrapperSession,
loginService: NodeIKernelLoginService,
registerInitCallback: (callback: () => void) => void
) {
// 在进入本层前是否登录未进行判断
console.log('NapCat Framework App Loading...');
process.on('uncaughtException', (err) => {
console.log('[NapCat] [Error] Unhandled Exception:', err.message);
});
process.on('unhandledRejection', (reason) => {
console.log('[NapCat] [Error] unhandledRejection:', reason);
});
const pathWrapper = new NapCatPathWrapper();
const logger = new LogWrapper(pathWrapper.logsPath);
const basicInfoWrapper = new QQBasicInfoWrapper({ logger });
const wrapper = loadQQWrapper(basicInfoWrapper.getFullQQVersion());
const nativePacketHandler = new NativePacketHandler({ logger }); // 初始化 NativePacketHandler 用于后续使用
// nativePacketHandler.onAll((packet) => {
// console.log('[Packet]', packet.uin, packet.cmd, packet.hex_data);
// });
await nativePacketHandler.init(basicInfoWrapper.getFullQQVersion());
// 在 init 之后注册监听器
// 初始化 FFmpeg 服务
await FFmpegService.init(pathWrapper.binaryPath, logger);
// 直到登录成功后,执行下一步
// const selfInfo = {
// uid: 'u_FUSS0_x06S_9Tf4na_WpUg',
// uin: '3684714082',
// nick: '',
// online: true
// }
const selfInfo = await new Promise<SelfInfo>((resolve) => {
const loginListener = new NodeIKernelLoginListener();
loginListener.onQRCodeLoginSucceed = async (loginResult) => {
await new Promise<void>(resolve => {
registerInitCallback(() => resolve());
});
resolve({
uid: loginResult.uid,
uin: loginResult.uin,
nick: '', // 获取不到
online: true,
});
};
loginService.addKernelLoginListener(proxiedListenerOf(loginListener, logger));
});
// 过早进入会导致addKernelMsgListener等Listener添加失败
// await sleep(2500);
// 初始化 NapCatFramework
const loaderObject = new NapCatFramework(wrapper, session, logger, loginService, selfInfo, basicInfoWrapper, pathWrapper, nativePacketHandler);
await loaderObject.core.initCore();
// 启动WebUi
InitWebUi(logger, pathWrapper).then().catch(e => logger.logError(e));
// 初始化LLNC的Onebot实现
await new NapCatOneBot11Adapter(loaderObject.core, loaderObject.context, pathWrapper).InitOneBot();
}
export class NapCatFramework {
public core: NapCatCore;
context: InstanceContext;
constructor (
wrapper: WrapperNodeApi,
session: NodeIQQNTWrapperSession,
logger: LogWrapper,
loginService: NodeIKernelLoginService,
selfInfo: SelfInfo,
basicInfoWrapper: QQBasicInfoWrapper,
pathWrapper: NapCatPathWrapper,
packetHandler: NativePacketHandler
) {
this.context = {
packetHandler,
workingEnv: NapCatCoreWorkingEnv.Framework,
wrapper,
session,
logger,
loginService,
basicInfoWrapper,
pathWrapper,
};
this.core = new NapCatCore(this.context, selfInfo);
}
}

View File

@@ -0,0 +1,25 @@
// const fs = require('fs');
const path = require('path');
async function initializeNapCat (session, loginService, registerCallback) {
// const logFile = path.join(currentPath, 'napcat.log');
console.log('[NapCat] [Info] 开始初始化NapCat');
// fs.writeFileSync(logFile, '', { flag: 'w' });
// fs.writeFileSync(logFile, '[NapCat] [Info] NapCat 初始化成功\n', { flag: 'a' });
try {
const currentPath = path.dirname(__filename);
const { NCoreInitFramework } = await import('file://' + path.join(currentPath, './napcat.mjs'));
await NCoreInitFramework(session, loginService, (callback) => { registerCallback(callback); });
} catch (error) {
console.log('[NapCat] [Error] 初始化NapCat', error);
// fs.writeFileSync(logFile, `[NapCat] [Error] 初始化NapCat失败: ${error.message}\n`, { flag: 'a' });
}
}
module.exports = {
initializeNapCat,
};

View File

@@ -0,0 +1,31 @@
{
"name": "napcat-framework",
"version": "0.0.1",
"private": true,
"type": "module",
"main": "index.ts",
"scripts": {
"build": "vite build"
},
"exports": {
".": {
"import": "./index.ts"
},
"./*": {
"import": "./*"
}
},
"dependencies": {
"napcat-core": "workspace:*",
"napcat-common": "workspace:*",
"napcat-onebot": "workspace:*",
"napcat-webui-backend": "workspace:*",
"napcat-qrcode": "workspace:*"
},
"devDependencies": {
"@types/node": "^22.0.1"
},
"engines": {
"node": ">=18.0.0"
}
}

View File

@@ -0,0 +1,14 @@
const { contextBridge, ipcRenderer } = require('electron');
const napcat = {
getWebUiUrl: async () => {
return ipcRenderer.invoke('napcat_get_webui');
},
openExternalUrl: async (url) => {
ipcRenderer.send('open_external_url', url);
},
openInnerUrl: async (url) => {
ipcRenderer.send('napcat_open_inner_url', url);
},
};
// 在window对象下导出只读对象
contextBridge.exposeInMainWorld('napcat', napcat);

View File

@@ -0,0 +1,41 @@
export const onSettingWindowCreated = async (view) => {
const webui = await window.napcat.getWebUiUrl();
view.innerHTML = `
<setting-section data-title="">
<setting-panel>
<setting-list data-direction="column">
<setting-item>
<setting-button data-type="primary" class="nc_openwebui">在QQ内打开配置页面</setting-button>
<setting-button data-type="primary" class="nc_openwebui_ex">在默认浏览器打开配置页面</setting-button>
</setting-item>
<setting-item>
<div>
<setting-text>WebUi远程地址可以点击下方复制哦~</setting-text>
<setting-text class="nc_webui">WebUi</setting-text>
</div>
</setting-item>
</setting-list>
</setting-panel>
</setting-section>
`;
view.querySelector('.nc_openwebui').addEventListener('click', () => {
window.napcat.openInnerUrl(webui);
});
view.querySelector('.nc_openwebui_ex').addEventListener('click', () => {
window.napcat.openExternalUrl(webui);
});
view.querySelector('.nc_webui').innerText = webui;
// 添加点击复制功能
view.querySelector('.nc_webui').addEventListener('click', async () => {
try {
await navigator.clipboard.writeText(webui);
// eslint-disable-next-line no-undef
alert('WebUi URL 已复制到剪贴板');
} catch (err) {
console.error('复制到剪贴板失败: ', err);
}
});
};

View File

@@ -0,0 +1,49 @@
{
"compilerOptions": {
"target": "ES2021",
"module": "ESNext",
"moduleResolution": "Node",
"lib": [
"ES2021"
],
"typeRoots": [
"./node_modules/@types"
],
"esModuleInterop": true,
"outDir": "dist",
"rootDir": ".",
"noEmit": false,
"sourceMap": true,
"strict": true,
"noImplicitAny": false,
"strictFunctionTypes": true,
"strictBindCallApply": true,
"alwaysStrict": true,
"noImplicitThis": true,
"noImplicitReturns": true,
"noPropertyAccessFromIndexSignature": true,
"noUncheckedIndexedAccess": true,
"exactOptionalPropertyTypes": false,
"forceConsistentCasingInFileNames": true,
"useUnknownInCatchVariables": true,
"noImplicitOverride": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"useDefineForClassFields": true,
"experimentalDecorators": true,
"allowSyntheticDefaultImports": true,
"resolveJsonModule": true,
"baseUrl": ".",
"skipLibCheck": true,
"skipDefaultLibCheck": true
},
"include": [
"*.ts",
"**/*.ts"
],
"exclude": [
"node_modules",
"dist"
]
}

View File

@@ -0,0 +1,68 @@
import cp from 'vite-plugin-cp';
import { defineConfig, PluginOption, UserConfig } from 'vite';
import path, { resolve } from 'path';
import nodeResolve from '@rollup/plugin-node-resolve';
import { builtinModules } from 'module';
//依赖排除
const external = [
'silk-wasm',
'ws',
'express'
];
const nodeModules = [...builtinModules, builtinModules.map((m) => `node:${m}`)].flat();
const FrameworkBaseConfigPlugin: PluginOption[] = [
cp({
targets: [
{ src: '../napcat-napi-loader/', dest: 'dist', flatten: true },
{ src: '../napcat-native/', dest: 'dist/native', flatten: false },
{ src: './manifest.json', dest: 'dist' },
{ src: '../napcat-core/external/napcat.json', dest: 'dist/config/' },
{ src: '../napcat-webui-frontend/dist/', dest: 'dist/static/', flatten: false },
{ src: './liteloader.cjs', dest: 'dist' },
{ src: './napcat.cjs', dest: 'dist' },
{ src: './nativeLoader.cjs', dest: 'dist' },
{ src: './preload.cjs', dest: 'dist' },
{ src: './renderer.js', dest: 'dist' },
{ src: '../../package.json', dest: 'dist' },
{ src: '../../logo.png', dest: 'dist' },
],
}),
nodeResolve(),
];
const FrameworkBaseConfig = () =>
defineConfig({
resolve: {
conditions: ['node', 'default'],
alias: {
'@/napcat-core': resolve(__dirname, '../napcat-core'),
'@/napcat-common': resolve(__dirname, '../napcat-common/src'),
'@/napcat-onebot': resolve(__dirname, '../napcat-onebot'),
'@/napcat-pty': resolve(__dirname, '../napcat-pty'),
'@/napcat-webui-backend': resolve(__dirname, '../napcat-webui-backend/src'),
'@/image-size': resolve(__dirname, '../image-size'),
},
},
build: {
sourcemap: false,
target: 'esnext',
minify: false,
lib: {
entry: {
napcat: path.resolve(__dirname, 'napcat.ts'),
'audio-worker': path.resolve(__dirname, '../napcat-common/src/audio-worker.ts'),
'worker/conoutSocketWorker': path.resolve(__dirname, '../napcat-pty/worker/conoutSocketWorker.ts'),
},
formats: ['es'],
fileName: (_, entryName) => `${entryName}.mjs`,
},
rollupOptions: {
external: [...nodeModules, ...external],
},
},
});
export default defineConfig((): UserConfig => {
return {
...FrameworkBaseConfig(),
plugins: [...FrameworkBaseConfigPlugin],
};
});