Compare commits

..

12 Commits

Author SHA1 Message Date
手瓜一十雪
522a123f9a Update native MoeHoo binaries for Linux
Replaces MoeHoo.linux.arm64.node and MoeHoo.linux.x64.node with new versions. This may include bug fixes, performance improvements, or compatibility updates for the native packet module.
2025-11-11 20:30:33 +08:00
Mlikiowa
ec9f8d6e12 release: v4.9.35 2025-11-10 04:04:07 +00:00
手瓜一十雪
3c750c75a9 Add Linux native modules for arm64 and x64
Added prebuilt native binaries MoeHoo.linux.arm64.node and MoeHoo.linux.x64.node to support Linux on arm64 and x64 architectures.
2025-11-10 12:03:42 +08:00
手瓜一十雪
5b2b1f499b Remove Linux native packet binaries
Deleted MoeHoo.linux.arm64.node and MoeHoo.linux.x64.node from src/native/packet. These binaries are no longer needed or are being replaced.
2025-11-10 12:02:58 +08:00
手瓜一十雪
531ffcd55d Update MoeHoo.linux.x64.node binary
Replaces the MoeHoo.linux.x64.node native module with a new version. Details of the changes depend on the updated binary implementation.
2025-11-10 12:02:45 +08:00
Mlikiowa
068e4d8bb5 release: v4.9.33 2025-11-09 04:54:38 +00:00
手瓜一十雪
5dc33e78ad Add napiloader scripts and binaries, update Vite config
Introduces napiloader batch scripts and binaries (napiloader.dll, napimain.exe) for Windows integration. Updates vite.config.ts to include napiloader files in the Framework build output and refactors plugin target lists for improved distribution packaging.
2025-11-09 12:54:15 +08:00
Mlikiowa
d76a2170a0 release: v4.9.32 2025-11-08 04:26:49 +00:00
手瓜一十雪
ec2af3120c Refactor FFmpeg file conversion logic and API
Unified file conversion in FFmpegAddonAdapter to use decodeAudioToFmt for all formats, updated FFmpeg interface and service to support new conversion method, and added adapter name checks in GetRecord and DownloadFileRecordStream for optimized conversion flow. Updated native addon binaries to support these changes.
2025-11-08 12:22:51 +08:00
Mlikiowa
8de49a3109 release: v4.9.30 2025-11-08 02:30:01 +00:00
手瓜一十雪
dbb5a0022e Update ffmpeg native binaries for all platforms
Replaces ffmpegAddon binaries for Darwin ARM64, Linux ARM64, Linux x64, and Windows x64 with new versions. Ensures compatibility and includes latest native changes.
2025-11-08 10:28:59 +08:00
Mlikiowa
cb8c8d6b57 release: v4.9.29 2025-11-07 12:01:08 +00:00
22 changed files with 267 additions and 194 deletions

View File

@@ -4,7 +4,7 @@
"name": "NapCatQQ",
"slug": "NapCat.Framework",
"description": "高性能的 OneBot 11 协议实现",
"version": "4.9.28",
"version": "4.9.35",
"icon": "./logo.png",
"authors": [
{

View File

@@ -0,0 +1,30 @@
@echo off
chcp 65001
set NAPCAT_INJECT_PATH=%cd%\napiloader.dll
set NAPCAT_LAUNCHER_PATH=%cd%\napimain.exe
set NAPCAT_MAIN_PATH=%cd%\nativeLoader.cjs
set NAPCAT_DEBUG_CONSOLE=1
:loop_read
for /f "tokens=2*" %%a in ('reg query "HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\QQ" /v "UninstallString"') do (
set "RetString=%%~b"
goto :napcat_boot
)
:napcat_boot
for %%a in ("%RetString%") do (
set "pathWithoutUninstall=%%~dpa"
)
set "QQPath=%pathWithoutUninstall%QQ.exe"
if not exist "%QQpath%" (
echo provided QQ path is invalid
pause
exit /b
)
set NAPCAT_MAIN_PATH=%NAPCAT_MAIN_PATH:\=/%
"%NAPCAT_LAUNCHER_PATH%" "%QQPath%" "%NAPCAT_INJECT_PATH%" "%NAPCAT_MAIN_PATH%"
pause

27
napiloader/napiLoader.bat Normal file
View File

@@ -0,0 +1,27 @@
@echo off
chcp 65001
set NAPCAT_INJECT_PATH=%cd%\napiloader.dll
set NAPCAT_LAUNCHER_PATH=%cd%\napimain.exe
set NAPCAT_MAIN_PATH=%cd%\nativeLoader.cjs
:loop_read
for /f "tokens=2*" %%a in ('reg query "HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\QQ" /v "UninstallString"') do (
set "RetString=%%~b"
goto :napcat_boot
)
:napcat_boot
for %%a in ("%RetString%") do (
set "pathWithoutUninstall=%%~dpa"
)
set "QQPath=%pathWithoutUninstall%QQ.exe"
if not exist "%QQpath%" (
echo provided QQ path is invalid
pause
exit /b
)
set NAPCAT_MAIN_PATH=%NAPCAT_MAIN_PATH:\=/%
start "" "%NAPCAT_LAUNCHER_PATH%" "%QQPath%" "%NAPCAT_INJECT_PATH%" "%NAPCAT_MAIN_PATH%"

BIN
napiloader/napiloader.dll Normal file

Binary file not shown.

BIN
napiloader/napimain.exe Normal file

Binary file not shown.

View File

@@ -2,7 +2,7 @@
"name": "napcat",
"private": true,
"type": "module",
"version": "4.9.28",
"version": "4.9.35",
"scripts": {
"build:universal": "npm run build:webui && npm run dev:universal || exit 1",
"build:framework": "npm run build:webui && npm run dev:framework || exit 1",

View File

@@ -68,11 +68,13 @@ export class FFmpegAddonAdapter implements IFFmpegAdapter {
const addon = this.ensureAddon();
const info = await addon.getVideoInfo(videoPath, 'bmp24');
let format = info.format.includes(',') ? info.format.split(',')[0] ?? info.format : info.format;
console.log('[FFmpegAddonAdapter] Detected format:', format);
return {
width: info.width,
height: info.height,
duration: info.duration,
format: info.format,
format: format,
thumbnail: info.image,
};
}
@@ -88,7 +90,7 @@ export class FFmpegAddonAdapter implements IFFmpegAdapter {
/**
* 转换为 PCM
*/
async convertToPCM (filePath: string, pcmPath: string): Promise<{ result: boolean, sampleRate: number }> {
async convertToPCM (filePath: string, pcmPath: string): Promise<{ result: boolean, sampleRate: number; }> {
const addon = this.ensureAddon();
const result = await addon.decodeAudioToPCM(filePath, pcmPath, 24000);
@@ -100,13 +102,8 @@ export class FFmpegAddonAdapter implements IFFmpegAdapter {
*/
async convertFile (inputFile: string, outputFile: string, format: string): Promise<void> {
const addon = this.ensureAddon();
if (format === 'silk' || format === 'ntsilk') {
// 使用 Addon 的 NTSILK 转换
await addon.convertToNTSilkTct(inputFile, outputFile);
} else {
throw new Error(`Format '${format}' is not supported by FFmpeg Addon`);
}
console.log('[FFmpegAddonAdapter] Converting file:', inputFile, 'to', outputFile, 'as', format);
await addon.decodeAudioToFmt(inputFile, outputFile, format);
}
/**

View File

@@ -49,23 +49,25 @@ export interface AudioPCMResult {
* FFmpeg interface providing all audio/video processing methods
*/
export interface FFmpeg {
convertFile (inputFile: string, outputFile: string, format: string): Promise<{ success: boolean; }>;
/**
* Get video information including resolution, duration, format, codec and first frame thumbnail
*/
getVideoInfo(filePath: string, format?: 'bmp' | 'bmp24'): Promise<VideoInfo>;
getVideoInfo (filePath: string, format?: 'bmp' | 'bmp24'): Promise<VideoInfo>;
/**
* Get duration of audio or video file in seconds
*/
getDuration(filePath: string): Promise<number>;
getDuration (filePath: string): Promise<number>;
/**
* Convert audio file to NTSILK format (WeChat voice message format)
*/
convertToNTSilkTct(inputPath: string, outputPath: string): Promise<void>;
convertToNTSilkTct (inputPath: string, outputPath: string): Promise<void>;
/**
* Decode audio file to raw PCM data
*/
decodeAudioToPCM(filePath: string, pcmPath: string, sampleRate?: number): Promise<{ result: boolean, sampleRate: number }>;
decodeAudioToPCM (filePath: string, pcmPath: string, sampleRate?: number): Promise<{ result: boolean, sampleRate: number; }>;
decodeAudioToFmt (filePath: string, pcmPath: string, format: string): Promise<{ channels: number; sampleRate: number; format: string; }>;
}

View File

@@ -48,6 +48,14 @@ export class FFmpegService {
this.initialized = true;
}
public static getAdapterName (): string {
if (!this.adapter) {
throw new Error('FFmpeg service not initialized. Please call FFmpegService.init() first.');
}
return this.adapter.name;
}
/**
* 获取 FFmpeg 适配器
*/

View File

@@ -1 +1 @@
export const napCatVersion = '4.9.28';
export const napCatVersion = '4.9.35';

View File

@@ -7,7 +7,7 @@ import { FFmpegService } from '@/common/ffmpeg';
const out_format = ['mp3', 'amr', 'wma', 'm4a', 'spx', 'ogg', 'wav', 'flac'];
type Payload = {
out_format: string
out_format: string;
} & GetFilePayload;
export default class GetRecord extends GetFileBase {
@@ -28,8 +28,12 @@ export default class GetRecord extends GetFileBase {
try {
await fs.access(outputFile);
} catch {
await this.decodeFile(inputFile, pcmFile);
await FFmpegService.convertFile(pcmFile, outputFile, payload.out_format);
if (FFmpegService.getAdapterName() === 'FFmpegAddon') {
await FFmpegService.convertFile(inputFile, outputFile, payload.out_format);
} else {
await this.decodeFile(inputFile, pcmFile);
await FFmpegService.convertFile(pcmFile, outputFile, payload.out_format);
}
}
const base64Data = await fs.readFile(outputFile, { encoding: 'base64' });
res.file = outputFile;

View File

@@ -47,8 +47,12 @@ export class DownloadFileRecordStream extends BaseDownloadStream<Payload, Downlo
streamPath = outputFile;
} catch {
// 尝试解码 silk 到 pcm 再用 ffmpeg 转换
await this.decodeFile(downloadPath, pcmFile);
await FFmpegService.convertFile(pcmFile, outputFile, payload.out_format);
if (FFmpegService.getAdapterName() === 'FFmpegAddon') {
await FFmpegService.convertFile(downloadPath, outputFile, payload.out_format);
} else {
await this.decodeFile(downloadPath, pcmFile);
await FFmpegService.convertFile(pcmFile, outputFile, payload.out_format);
}
streamPath = outputFile;
}
}

View File

@@ -6,204 +6,205 @@ import { builtinModules } from 'module';
import { performanceMonitorPlugin } from './vite-plugin-performance-monitor';
//依赖排除
const external = [
'silk-wasm',
'ws',
'express'
'silk-wasm',
'ws',
'express'
];
const nodeModules = [...builtinModules, builtinModules.map((m) => `node:${m}`)].flat();
let startScripts: string[] | undefined = undefined;
if (process.env.NAPCAT_BUILDSYS == 'linux') {
startScripts = [];
startScripts = [];
} else if (process.env.NAPCAT_BUILDSYS == 'win32') {
startScripts = ['./script/KillQQ.bat'];
startScripts = ['./script/KillQQ.bat'];
} else {
startScripts = ['./script/KillQQ.bat'];
startScripts = ['./script/KillQQ.bat'];
}
const UniversalBaseConfigPlugin: PluginOption[] = [
// performanceMonitorPlugin({
// enabled: process.env.NODE_ENV !== 'production',
// exclude: [/node_modules/, /\.min\./, /performance-monitor/],
// include: [/\.ts$/, /\.js$/]
// }),
cp({
targets: [
{ src: './manifest.json', dest: 'dist' },
{ src: './src/core/external/napcat.json', dest: 'dist/config/' },
{ src: './src/native/', dest: 'dist/native', flatten: false },
{ src: './napcat.webui/dist/', dest: 'dist/static/', flatten: false },
{ src: './src/framework/liteloader.cjs', dest: 'dist' },
{ src: './src/framework/napcat.cjs', dest: 'dist' },
{ src: './src/framework/preload.cjs', dest: 'dist' },
{ src: './src/framework/renderer.js', dest: 'dist' },
{ src: './package.json', dest: 'dist' },
{ src: './logo.png', dest: 'dist' },
{ src: './launcher/', dest: 'dist', flatten: true },
...startScripts.map((startScript) => {
return { src: startScript, dest: 'dist' };
}),
],
}),
nodeResolve(),
// performanceMonitorPlugin({
// enabled: process.env.NODE_ENV !== 'production',
// exclude: [/node_modules/, /\.min\./, /performance-monitor/],
// include: [/\.ts$/, /\.js$/]
// }),
cp({
targets: [
{ src: './manifest.json', dest: 'dist' },
{ src: './src/core/external/napcat.json', dest: 'dist/config/' },
{ src: './src/native/', dest: 'dist/native', flatten: false },
{ src: './napcat.webui/dist/', dest: 'dist/static/', flatten: false },
{ src: './src/framework/liteloader.cjs', dest: 'dist' },
{ src: './src/framework/napcat.cjs', dest: 'dist' },
{ src: './src/framework/preload.cjs', dest: 'dist' },
{ src: './src/framework/renderer.js', dest: 'dist' },
{ src: './package.json', dest: 'dist' },
{ src: './logo.png', dest: 'dist' },
{ src: './launcher/', dest: 'dist', flatten: true },
...startScripts.map((startScript) => {
return { src: startScript, dest: 'dist' };
}),
],
}),
nodeResolve(),
];
const FrameworkBaseConfigPlugin: PluginOption[] = [
// performanceMonitorPlugin({
// enabled: process.env.NODE_ENV !== 'production',
// exclude: [/node_modules/, /\.min\./, /performance-monitor/],
// include: [/\.ts$/, /\.js$/]
// }),
cp({
targets: [
{ src: './src/native/', dest: 'dist/native', flatten: false },
{ src: './manifest.json', dest: 'dist' },
{ src: './src/core/external/napcat.json', dest: 'dist/config/' },
{ src: './napcat.webui/dist/', dest: 'dist/static/', flatten: false },
{ src: './src/framework/liteloader.cjs', dest: 'dist' },
{ src: './src/framework/napcat.cjs', dest: 'dist' },
{ src: './src/framework/nativeLoader.cjs', dest: 'dist' },
{ src: './src/framework/preload.cjs', dest: 'dist' },
{ src: './src/framework/renderer.js', dest: 'dist' },
{ src: './package.json', dest: 'dist' },
{ src: './logo.png', dest: 'dist' },
],
}),
nodeResolve(),
// performanceMonitorPlugin({
// enabled: process.env.NODE_ENV !== 'production',
// exclude: [/node_modules/, /\.min\./, /performance-monitor/],
// include: [/\.ts$/, /\.js$/]
// }),
cp({
targets: [
{ src: './napiloader/', dest: 'dist', flatten: true },
{ src: './src/native/', dest: 'dist/native', flatten: false },
{ src: './manifest.json', dest: 'dist' },
{ src: './src/core/external/napcat.json', dest: 'dist/config/' },
{ src: './napcat.webui/dist/', dest: 'dist/static/', flatten: false },
{ src: './src/framework/liteloader.cjs', dest: 'dist' },
{ src: './src/framework/napcat.cjs', dest: 'dist' },
{ src: './src/framework/nativeLoader.cjs', dest: 'dist' },
{ src: './src/framework/preload.cjs', dest: 'dist' },
{ src: './src/framework/renderer.js', dest: 'dist' },
{ src: './package.json', dest: 'dist' },
{ src: './logo.png', dest: 'dist' },
],
}),
nodeResolve(),
];
const ShellBaseConfigPlugin: PluginOption[] = [
// performanceMonitorPlugin({
// enabled: process.env.NODE_ENV !== 'production',
// exclude: [/node_modules/, /\.min\./, /performance-monitor/],
// include: [/\.ts$/, /\.js$/]
// }),
cp({
targets: [
{ src: './src/native/', dest: 'dist/native', flatten: false },
{ src: './napcat.webui/dist/', dest: 'dist/static/', flatten: false },
{ src: './src/core/external/napcat.json', dest: 'dist/config/' },
{ src: './package.json', dest: 'dist' },
{ src: './launcher/', dest: 'dist', flatten: true },
...startScripts.map((startScript) => {
return { src: startScript, dest: 'dist' };
}),
],
}),
nodeResolve(),
// performanceMonitorPlugin({
// enabled: process.env.NODE_ENV !== 'production',
// exclude: [/node_modules/, /\.min\./, /performance-monitor/],
// include: [/\.ts$/, /\.js$/]
// }),
cp({
targets: [
{ src: './src/native/', dest: 'dist/native', flatten: false },
{ src: './napcat.webui/dist/', dest: 'dist/static/', flatten: false },
{ src: './src/core/external/napcat.json', dest: 'dist/config/' },
{ src: './package.json', dest: 'dist' },
{ src: './launcher/', dest: 'dist', flatten: true },
...startScripts.map((startScript) => {
return { src: startScript, dest: 'dist' };
}),
],
}),
nodeResolve(),
];
const UniversalBaseConfig = () =>
defineConfig({
resolve: {
conditions: ['node', 'default'],
alias: {
'@/core': resolve(__dirname, './src/core'),
'@': resolve(__dirname, './src'),
'@webapi': resolve(__dirname, './src/webui/src'),
},
defineConfig({
resolve: {
conditions: ['node', 'default'],
alias: {
'@/core': resolve(__dirname, './src/core'),
'@': resolve(__dirname, './src'),
'@webapi': resolve(__dirname, './src/webui/src'),
},
},
build: {
sourcemap: false,
target: 'esnext',
minify: false,
lib: {
entry: {
napcat: 'src/universal/napcat.ts',
'audio-worker': 'src/common/audio-worker.ts',
'worker/conoutSocketWorker': 'src/pty/worker/conoutSocketWorker.ts',
},
build: {
sourcemap: false,
target: 'esnext',
minify: false,
lib: {
entry: {
napcat: 'src/universal/napcat.ts',
'audio-worker': 'src/common/audio-worker.ts',
'worker/conoutSocketWorker': 'src/pty/worker/conoutSocketWorker.ts',
},
formats: ['es'],
fileName: (_, entryName) => `${entryName}.mjs`,
},
rollupOptions: {
external: [...nodeModules, ...external],
},
},
});
formats: ['es'],
fileName: (_, entryName) => `${entryName}.mjs`,
},
rollupOptions: {
external: [...nodeModules, ...external],
},
},
});
const ShellBaseConfig = () =>
defineConfig({
resolve: {
conditions: ['node', 'default'],
alias: {
'@/core': resolve(__dirname, './src/core'),
'@': resolve(__dirname, './src'),
'@webapi': resolve(__dirname, './src/webui/src'),
},
defineConfig({
resolve: {
conditions: ['node', 'default'],
alias: {
'@/core': resolve(__dirname, './src/core'),
'@': resolve(__dirname, './src'),
'@webapi': resolve(__dirname, './src/webui/src'),
},
},
build: {
sourcemap: false,
target: 'esnext',
minify: false,
lib: {
entry: {
napcat: 'src/shell/napcat.ts',
'audio-worker': 'src/common/audio-worker.ts',
'worker/conoutSocketWorker': 'src/pty/worker/conoutSocketWorker.ts',
},
build: {
sourcemap: false,
target: 'esnext',
minify: false,
lib: {
entry: {
napcat: 'src/shell/napcat.ts',
'audio-worker': 'src/common/audio-worker.ts',
'worker/conoutSocketWorker': 'src/pty/worker/conoutSocketWorker.ts',
},
formats: ['es'],
fileName: (_, entryName) => `${entryName}.mjs`,
},
rollupOptions: {
external: [...nodeModules, ...external],
},
},
});
formats: ['es'],
fileName: (_, entryName) => `${entryName}.mjs`,
},
rollupOptions: {
external: [...nodeModules, ...external],
},
},
});
const FrameworkBaseConfig = () =>
defineConfig({
resolve: {
conditions: ['node', 'default'],
alias: {
'@/core': resolve(__dirname, './src/core'),
'@': resolve(__dirname, './src'),
'@webapi': resolve(__dirname, './src/webui/src'),
},
defineConfig({
resolve: {
conditions: ['node', 'default'],
alias: {
'@/core': resolve(__dirname, './src/core'),
'@': resolve(__dirname, './src'),
'@webapi': resolve(__dirname, './src/webui/src'),
},
},
build: {
sourcemap: false,
target: 'esnext',
minify: false,
lib: {
entry: {
napcat: 'src/framework/napcat.ts',
'audio-worker': 'src/common/audio-worker.ts',
'worker/conoutSocketWorker': 'src/pty/worker/conoutSocketWorker.ts',
},
build: {
sourcemap: false,
target: 'esnext',
minify: false,
lib: {
entry: {
napcat: 'src/framework/napcat.ts',
'audio-worker': 'src/common/audio-worker.ts',
'worker/conoutSocketWorker': 'src/pty/worker/conoutSocketWorker.ts',
},
formats: ['es'],
fileName: (_, entryName) => `${entryName}.mjs`,
},
rollupOptions: {
external: [...nodeModules, ...external],
},
},
});
formats: ['es'],
fileName: (_, entryName) => `${entryName}.mjs`,
},
rollupOptions: {
external: [...nodeModules, ...external],
},
},
});
export default defineConfig(({ mode }): UserConfig => {
if (mode === 'shell') {
return {
...ShellBaseConfig(),
plugins: [...ShellBaseConfigPlugin],
};
} else if (mode == 'universal') {
return {
...UniversalBaseConfig(),
plugins: [...UniversalBaseConfigPlugin],
};
} else if (mode == 'shell-analysis') {
return {
...ShellBaseConfig(),
plugins: [
performanceMonitorPlugin({
exclude: [/node_modules/, /\.min\./, /performance-monitor\.ts$/, /packet/],
include: [/\.ts$/, /\.js$/]
}),
...ShellBaseConfigPlugin
],
};
} else
return {
...FrameworkBaseConfig(),
plugins: [...FrameworkBaseConfigPlugin],
};
if (mode === 'shell') {
return {
...ShellBaseConfig(),
plugins: [...ShellBaseConfigPlugin],
};
} else if (mode == 'universal') {
return {
...UniversalBaseConfig(),
plugins: [...UniversalBaseConfigPlugin],
};
} else if (mode == 'shell-analysis') {
return {
...ShellBaseConfig(),
plugins: [
performanceMonitorPlugin({
exclude: [/node_modules/, /\.min\./, /performance-monitor\.ts$/, /packet/],
include: [/\.ts$/, /\.js$/]
}),
...ShellBaseConfigPlugin
],
};
} else
return {
...FrameworkBaseConfig(),
plugins: [...FrameworkBaseConfigPlugin],
};
});