mirror of
https://github.com/NapNeko/NapCatQQ.git
synced 2026-02-06 13:05:09 +00:00
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:
19
packages/napcat-framework/liteloader.cjs
Normal file
19
packages/napcat-framework/liteloader.cjs
Normal 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);
|
||||
});
|
||||
});
|
||||
29
packages/napcat-framework/manifest.json
Normal file
29
packages/napcat-framework/manifest.json
Normal 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"
|
||||
}
|
||||
}
|
||||
92
packages/napcat-framework/napcat.cjs
Normal file
92
packages/napcat-framework/napcat.cjs
Normal 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();
|
||||
},
|
||||
};
|
||||
110
packages/napcat-framework/napcat.ts
Normal file
110
packages/napcat-framework/napcat.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
25
packages/napcat-framework/nativeLoader.cjs
Normal file
25
packages/napcat-framework/nativeLoader.cjs
Normal 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,
|
||||
};
|
||||
31
packages/napcat-framework/package.json
Normal file
31
packages/napcat-framework/package.json
Normal 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"
|
||||
}
|
||||
}
|
||||
14
packages/napcat-framework/preload.cjs
Normal file
14
packages/napcat-framework/preload.cjs
Normal 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);
|
||||
41
packages/napcat-framework/renderer.js
Normal file
41
packages/napcat-framework/renderer.js
Normal 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);
|
||||
}
|
||||
});
|
||||
};
|
||||
49
packages/napcat-framework/tsconfig.json
Normal file
49
packages/napcat-framework/tsconfig.json
Normal 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"
|
||||
]
|
||||
}
|
||||
68
packages/napcat-framework/vite.config.ts
Normal file
68
packages/napcat-framework/vite.config.ts
Normal 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],
|
||||
};
|
||||
});
|
||||
Reference in New Issue
Block a user