fix: repair Windows release dependencies and loader paths
Bundle required crypto and SSL DLLs and gate releases on a Windows native-load smoke test. Resolve loader paths independently of the working directory and encode file URLs correctly. Include the quick-login launcher built from Windows-Boot 0ff70ce10e34ac26411f9e6bcedaedfacc1e74f0. Validated builds, five path regressions, and live Shell and Windows Node startup to QR login.
This commit is contained in:
@@ -153,9 +153,14 @@ jobs:
|
||||
# -----------------------------
|
||||
# 5) 拷贝 QQ 文件到 NapCat.Shell.Windows.Node
|
||||
# -----------------------------
|
||||
QQ_TARGETS=("avif_convert.dll" "broadcast_ipc.dll" "config.json" "libglib-2.0-0.dll" "libgobject-2.0-0.dll" "libvips-42.dll" "ncnn.dll" "opencv.dll" "package.json" "QBar.dll" "wrapper.node" "LightQuic.dll")
|
||||
QQ_TARGETS=("avif_convert.dll" "broadcast_ipc.dll" "config.json" "crypto.dll" "ssl.dll" "libglib-2.0-0.dll" "libgobject-2.0-0.dll" "libvips-42.dll" "ncnn.dll" "opencv.dll" "package.json" "QBar.dll" "wrapper.node" "LightQuic.dll")
|
||||
for name in "${QQ_TARGETS[@]}"; do
|
||||
find "$QQ_EXTRACT" -iname "$name" -exec cp -a {} "$OUT_DIR" \; || true
|
||||
source_file=$(find "$QQ_EXTRACT" -type f -iname "$name" -print -quit)
|
||||
if [ -z "$source_file" ]; then
|
||||
echo "Missing required QQ file: $name" >&2
|
||||
exit 1
|
||||
fi
|
||||
cp -a "$source_file" "$OUT_DIR/"
|
||||
done
|
||||
|
||||
# -----------------------------
|
||||
@@ -183,8 +188,26 @@ jobs:
|
||||
name: NapCat.Shell.Windows.Node
|
||||
path: NapCat.Shell.Windows.Node
|
||||
|
||||
validate-windows-node:
|
||||
needs: assemble-windows-node
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- uses: actions/download-artifact@v7
|
||||
with:
|
||||
name: NapCat.Shell.Windows.Node
|
||||
path: windows-node
|
||||
|
||||
- name: Verify bundled Node and QQ native dependencies
|
||||
shell: pwsh
|
||||
working-directory: windows-node
|
||||
run: |
|
||||
& .\node.exe --check .\index.js
|
||||
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
|
||||
& .\node.exe -e "const wrapper = require('./wrapper.node'); if (!wrapper.NodeQQNTWrapperUtil) throw new Error('Missing NodeQQNTWrapperUtil'); console.log('wrapper.node native-load PASS'); process.exit(0);"
|
||||
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
|
||||
|
||||
release-napcat:
|
||||
needs: [build, assemble-windows-node]
|
||||
needs: [build, assemble-windows-node, validate-windows-node]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
|
||||
@@ -1 +1,5 @@
|
||||
node.exe ./index.js
|
||||
@echo off
|
||||
setlocal
|
||||
cd /d "%~dp0" || exit /b 1
|
||||
"%~dp0node.exe" "%~dp0index.js" %*
|
||||
exit /b %errorlevel%
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
const process = require('process');
|
||||
const os = require('os');
|
||||
const path = require('path');
|
||||
const { pathToFileURL } = require('url');
|
||||
|
||||
// 保存原始dlopen
|
||||
const dlopenOrig = process.dlopen;
|
||||
@@ -50,7 +51,7 @@ 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'));
|
||||
const { NCoreInitFramework, getWebUiUrl } = await import(pathToFileURL(path.join(currentPath, './napcat.mjs')).href);
|
||||
getWebUiUrlFunc = getWebUiUrl;
|
||||
await NCoreInitFramework(wrapperSession, wrapperLoginService, (callback) => { ncCallback = callback; });
|
||||
} catch (error) {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// const fs = require('fs');
|
||||
const path = require('path');
|
||||
const { pathToFileURL } = require('url');
|
||||
|
||||
async function initializeNapCat (session, loginService, registerCallback) {
|
||||
// const logFile = path.join(currentPath, 'napcat.log');
|
||||
@@ -12,7 +13,7 @@ async function initializeNapCat (session, loginService, registerCallback) {
|
||||
|
||||
try {
|
||||
const currentPath = path.dirname(__filename);
|
||||
const { NCoreInitFramework } = await import('file://' + path.join(currentPath, './napcat.mjs'));
|
||||
const { NCoreInitFramework } = await import(pathToFileURL(path.join(currentPath, './napcat.mjs')).href);
|
||||
await NCoreInitFramework(session, loginService, (callback) => { registerCallback(callback); });
|
||||
} catch (error) {
|
||||
console.log('[NapCat] [Error] 初始化NapCat', error);
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
@echo off
|
||||
setlocal DisableDelayedExpansion
|
||||
cd /d "%~dp0" || exit /b 1
|
||||
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_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 (
|
||||
@@ -27,4 +29,4 @@ set NAPCAT_MAIN_PATH=%NAPCAT_MAIN_PATH:\=/%
|
||||
|
||||
"%NAPCAT_LAUNCHER_PATH%" "%QQPath%" "%NAPCAT_INJECT_PATH%" "%NAPCAT_MAIN_PATH%"
|
||||
|
||||
pause
|
||||
pause
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
@echo off
|
||||
setlocal DisableDelayedExpansion
|
||||
cd /d "%~dp0" || exit /b 1
|
||||
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_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"
|
||||
@@ -24,4 +26,4 @@ if not exist "%QQpath%" (
|
||||
|
||||
set NAPCAT_MAIN_PATH=%NAPCAT_MAIN_PATH:\=/%
|
||||
|
||||
start "" "%NAPCAT_LAUNCHER_PATH%" "%QQPath%" "%NAPCAT_INJECT_PATH%" "%NAPCAT_MAIN_PATH%"
|
||||
start "" "%NAPCAT_LAUNCHER_PATH%" "%QQPath%" "%NAPCAT_INJECT_PATH%" "%NAPCAT_MAIN_PATH%"
|
||||
|
||||
Binary file not shown.
@@ -1,10 +1,12 @@
|
||||
@echo off
|
||||
setlocal DisableDelayedExpansion
|
||||
cd /d "%~dp0" || exit /b 1
|
||||
chcp 65001 >nul
|
||||
set NAPCAT_PATCH_PACKAGE=%cd%\qqnt.json
|
||||
set NAPCAT_LOAD_PATH=%cd%\loadNapCat.js
|
||||
set NAPCAT_INJECT_PATH=%cd%\NapCatWinBootHook.dll
|
||||
set NAPCAT_LAUNCHER_PATH=%cd%\NapCatWinBootMain.exe
|
||||
set NAPCAT_MAIN_PATH=%cd%\napcat.mjs
|
||||
set "NAPCAT_PATCH_PACKAGE=%cd%\qqnt.json"
|
||||
set "NAPCAT_LOAD_PATH=%cd%\loadNapCat.js"
|
||||
set "NAPCAT_INJECT_PATH=%cd%\NapCatWinBootHook.dll"
|
||||
set "NAPCAT_LAUNCHER_PATH=%cd%\NapCatWinBootMain.exe"
|
||||
set "NAPCAT_MAIN_PATH=%cd%\napcat.mjs"
|
||||
: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"
|
||||
@@ -24,9 +26,6 @@ if not exist "%QQpath%" (
|
||||
exit /b
|
||||
)
|
||||
|
||||
set NAPCAT_MAIN_PATH=%NAPCAT_MAIN_PATH:\=/%
|
||||
echo (async () =^> {await import("file:///%NAPCAT_MAIN_PATH%")})() > "%NAPCAT_LOAD_PATH%"
|
||||
|
||||
"%NAPCAT_LAUNCHER_PATH%" "%QQPath%" "%NAPCAT_INJECT_PATH%" %*
|
||||
|
||||
pause
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
@echo off
|
||||
setlocal DisableDelayedExpansion
|
||||
cd /d "%~dp0" || exit /b 1
|
||||
chcp 65001 >nul
|
||||
set NAPCAT_PATCH_PACKAGE=%cd%\qqnt.json
|
||||
set NAPCAT_LOAD_PATH=%cd%\loadNapCat.js
|
||||
set NAPCAT_INJECT_PATH=%cd%\NapCatWinBootHook.dll
|
||||
set NAPCAT_LAUNCHER_PATH=%cd%\NapCatWinBootMain.exe
|
||||
set NAPCAT_MAIN_PATH=%cd%\napcat.mjs
|
||||
set "NAPCAT_PATCH_PACKAGE=%cd%\qqnt.json"
|
||||
set "NAPCAT_LOAD_PATH=%cd%\loadNapCat.js"
|
||||
set "NAPCAT_INJECT_PATH=%cd%\NapCatWinBootHook.dll"
|
||||
set "NAPCAT_LAUNCHER_PATH=%cd%\NapCatWinBootMain.exe"
|
||||
set "NAPCAT_MAIN_PATH=%cd%\napcat.mjs"
|
||||
: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"
|
||||
@@ -23,9 +25,6 @@ if not exist "%QQpath%" (
|
||||
pause
|
||||
exit /b
|
||||
)
|
||||
set NAPCAT_MAIN_PATH=%NAPCAT_MAIN_PATH:\=/%
|
||||
echo (async () =^> {await import("file:///%NAPCAT_MAIN_PATH%")})() > "%NAPCAT_LOAD_PATH%"
|
||||
|
||||
"%NAPCAT_LAUNCHER_PATH%" "%QQPath%" "%NAPCAT_INJECT_PATH%" %*
|
||||
|
||||
REM Optional: -q <QQ_NUMBER> for quick login, omit for QR code login
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
@echo off
|
||||
setlocal DisableDelayedExpansion
|
||||
cd /d "%~dp0" || exit /b 1
|
||||
chcp 65001 >nul
|
||||
net session >nul 2>&1
|
||||
if %ERRORLEVEL% == 0 (
|
||||
@@ -9,11 +11,11 @@ if %ERRORLEVEL% == 0 (
|
||||
exit
|
||||
)
|
||||
|
||||
set NAPCAT_PATCH_PACKAGE=%cd%\qqnt.json
|
||||
set NAPCAT_LOAD_PATH=%cd%\loadNapCat.js
|
||||
set NAPCAT_INJECT_PATH=%cd%\NapCatWinBootHook.dll
|
||||
set NAPCAT_LAUNCHER_PATH=%cd%\NapCatWinBootMain.exe
|
||||
set NAPCAT_MAIN_PATH=%cd%\napcat.mjs
|
||||
set "NAPCAT_PATCH_PACKAGE=%cd%\qqnt.json"
|
||||
set "NAPCAT_LOAD_PATH=%cd%\loadNapCat.js"
|
||||
set "NAPCAT_INJECT_PATH=%cd%\NapCatWinBootHook.dll"
|
||||
set "NAPCAT_LAUNCHER_PATH=%cd%\NapCatWinBootMain.exe"
|
||||
set "NAPCAT_MAIN_PATH=%cd%\napcat.mjs"
|
||||
: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"
|
||||
@@ -32,9 +34,6 @@ if not exist "%QQPath%" (
|
||||
pause
|
||||
exit /b
|
||||
)
|
||||
set NAPCAT_MAIN_PATH=%NAPCAT_MAIN_PATH:\=/%
|
||||
echo (async () =^> {await import("file:///%NAPCAT_MAIN_PATH%")})() > "%NAPCAT_LOAD_PATH%"
|
||||
|
||||
"%NAPCAT_LAUNCHER_PATH%" "%QQPath%" "%NAPCAT_INJECT_PATH%" %*
|
||||
|
||||
REM Optional: -q <QQ_NUMBER> for quick login, omit for QR code login
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
@echo off
|
||||
setlocal DisableDelayedExpansion
|
||||
cd /d "%~dp0" || exit /b 1
|
||||
chcp 65001 >nul
|
||||
net session >nul 2>&1
|
||||
if %ERRORLEVEL% == 0 (
|
||||
@@ -9,11 +11,11 @@ if %ERRORLEVEL% == 0 (
|
||||
exit
|
||||
)
|
||||
|
||||
set NAPCAT_PATCH_PACKAGE=%cd%\qqnt.json
|
||||
set NAPCAT_LOAD_PATH=%cd%\loadNapCat.js
|
||||
set NAPCAT_INJECT_PATH=%cd%\NapCatWinBootHook.dll
|
||||
set NAPCAT_LAUNCHER_PATH=%cd%\NapCatWinBootMain.exe
|
||||
set NAPCAT_MAIN_PATH=%cd%\napcat.mjs
|
||||
set "NAPCAT_PATCH_PACKAGE=%cd%\qqnt.json"
|
||||
set "NAPCAT_LOAD_PATH=%cd%\loadNapCat.js"
|
||||
set "NAPCAT_INJECT_PATH=%cd%\NapCatWinBootHook.dll"
|
||||
set "NAPCAT_LAUNCHER_PATH=%cd%\NapCatWinBootMain.exe"
|
||||
set "NAPCAT_MAIN_PATH=%cd%\napcat.mjs"
|
||||
: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"
|
||||
@@ -33,7 +35,4 @@ if not exist "%QQPath%" (
|
||||
exit /b
|
||||
)
|
||||
|
||||
set NAPCAT_MAIN_PATH=%NAPCAT_MAIN_PATH:\=/%
|
||||
echo (async () =^> {await import("file:///%NAPCAT_MAIN_PATH%")})() > "%NAPCAT_LOAD_PATH%"
|
||||
|
||||
"%NAPCAT_LAUNCHER_PATH%" "%QQPath%" "%NAPCAT_INJECT_PATH%" %*
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
const path = require('path');
|
||||
const CurrentPath = path.dirname(__filename);
|
||||
const { pathToFileURL } = require('url');
|
||||
const mainPath = process.env.NAPCAT_MAIN_PATH || path.join(__dirname, 'napcat.mjs');
|
||||
(async () => {
|
||||
await import('file://' + path.join(CurrentPath, './napcat/napcat.mjs'));
|
||||
await import(pathToFileURL(mainPath).href);
|
||||
})();
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
import { afterEach, describe, expect, test } from 'vitest';
|
||||
import { copyFileSync, mkdtempSync, mkdirSync, rmSync, writeFileSync } from 'node:fs';
|
||||
import { execFileSync } from 'node:child_process';
|
||||
import { tmpdir } from 'node:os';
|
||||
import path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
const packageRoot = fileURLToPath(new URL('../', import.meta.url));
|
||||
const fixtureRoots: string[] = [];
|
||||
|
||||
function createFixture () {
|
||||
const fixtureRoot = mkdtempSync(path.join(tmpdir(), 'napcat-loader-'));
|
||||
fixtureRoots.push(fixtureRoot);
|
||||
const installDir = path.join(fixtureRoot, '中文 space # percent%');
|
||||
mkdirSync(installDir);
|
||||
return { fixtureRoot, installDir };
|
||||
}
|
||||
|
||||
afterEach(() => {
|
||||
for (const fixtureRoot of fixtureRoots.splice(0)) {
|
||||
rmSync(fixtureRoot, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
describe('loader paths', () => {
|
||||
test('Shell loads the flat package from an unrelated working directory', () => {
|
||||
const { fixtureRoot, installDir } = createFixture();
|
||||
const loaderPath = path.join(installDir, 'loadNapCat.cjs');
|
||||
copyFileSync(path.join(packageRoot, 'napcat-shell-loader/loadNapCat.js'), loaderPath);
|
||||
writeFileSync(path.join(installDir, 'napcat.mjs'), 'console.log("SHELL_LOADED");');
|
||||
const environment = { ...process.env };
|
||||
delete environment['NAPCAT_MAIN_PATH'];
|
||||
const output = execFileSync(process.execPath, [loaderPath], { cwd: fixtureRoot, env: environment, encoding: 'utf8' });
|
||||
expect(output).toContain('SHELL_LOADED');
|
||||
});
|
||||
|
||||
test('Shell uses the actual entry path when the loader is redirected into QQ', () => {
|
||||
const { fixtureRoot, installDir } = createFixture();
|
||||
const loaderPath = path.join(fixtureRoot, 'loadNapCat.cjs');
|
||||
const entryPath = path.join(installDir, 'napcat.mjs');
|
||||
copyFileSync(path.join(packageRoot, 'napcat-shell-loader/loadNapCat.js'), loaderPath);
|
||||
writeFileSync(entryPath, 'console.log("REDIRECTED_SHELL_LOADED");');
|
||||
const output = execFileSync(process.execPath, [loaderPath], {
|
||||
cwd: fixtureRoot,
|
||||
env: { ...process.env, NAPCAT_MAIN_PATH: entryPath },
|
||||
encoding: 'utf8',
|
||||
});
|
||||
expect(output).toContain('REDIRECTED_SHELL_LOADED');
|
||||
});
|
||||
|
||||
test('Framework native loader preserves URL-sensitive path characters', () => {
|
||||
const { fixtureRoot, installDir } = createFixture();
|
||||
const loaderPath = path.join(installDir, 'nativeLoader.cjs');
|
||||
copyFileSync(path.join(packageRoot, 'napcat-framework/nativeLoader.cjs'), loaderPath);
|
||||
writeFileSync(path.join(installDir, 'napcat.mjs'), 'export async function NCoreInitFramework () { console.log("FRAMEWORK_LOADED"); }');
|
||||
const output = execFileSync(process.execPath, ['-e', 'require(process.argv[1]).initializeNapCat({}, {}, () => {});', loaderPath], {
|
||||
cwd: fixtureRoot,
|
||||
encoding: 'utf8',
|
||||
});
|
||||
expect(output).toContain('FRAMEWORK_LOADED');
|
||||
expect(output).not.toContain('[Error]');
|
||||
});
|
||||
|
||||
test('Framework dlopen loader preserves URL-sensitive path characters', () => {
|
||||
const { fixtureRoot, installDir } = createFixture();
|
||||
const loaderPath = path.join(installDir, 'napcat.cjs');
|
||||
copyFileSync(path.join(packageRoot, 'napcat-framework/napcat.cjs'), loaderPath);
|
||||
writeFileSync(path.join(installDir, 'napcat.mjs'), 'export async function NCoreInitFramework () { console.log("DLOPEN_FRAMEWORK_LOADED"); } export function getWebUiUrl () { return ""; }');
|
||||
const script = 'process.dlopen = (module) => { module.exports = { NodeIKernelLoginService: { get: () => ({}) }, NodeIQQNTWrapperSession: { create: () => ({}) } }; }; require(process.argv[1]); process.dlopen({ exports: {} }, "wrapper.node");';
|
||||
const output = execFileSync(process.execPath, ['-e', script, loaderPath], { cwd: fixtureRoot, encoding: 'utf8' });
|
||||
expect(output).toContain('DLOPEN_FRAMEWORK_LOADED');
|
||||
expect(output).not.toContain('[Error]');
|
||||
});
|
||||
|
||||
test.skipIf(process.platform !== 'win32')('Node batch uses the bundled runtime, forwards arguments and returns the exit code', () => {
|
||||
const { fixtureRoot, installDir } = createFixture();
|
||||
copyFileSync(process.execPath, path.join(installDir, 'node.exe'));
|
||||
copyFileSync(path.join(packageRoot, 'napcat-develop/napcat.bat'), path.join(installDir, 'napcat.bat'));
|
||||
writeFileSync(path.join(installDir, 'index.js'), 'console.log(JSON.stringify({ cwd: process.cwd(), args: process.argv.slice(2) })); process.exitCode = 7;');
|
||||
let exitStatus: number | null = null;
|
||||
let output = '';
|
||||
try {
|
||||
output = execFileSync('cmd.exe', ['/d', '/s', '/c', `""${path.join(installDir, 'napcat.bat')}" -q 123456"`], {
|
||||
cwd: fixtureRoot,
|
||||
encoding: 'utf8',
|
||||
windowsVerbatimArguments: true,
|
||||
});
|
||||
} catch (error) {
|
||||
const result = error as { status: number; stdout: string };
|
||||
exitStatus = result.status;
|
||||
output = result.stdout;
|
||||
}
|
||||
expect(exitStatus).toBe(7);
|
||||
expect(JSON.parse(output)).toEqual({ cwd: installDir, args: ['-q', '123456'] });
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user