Add Registry20 GUID management and DPAPI support

Introduce tools and UI to read, write, backup and restore QQ Registry20 GUIDs using Windows DPAPI. Adds a Python CLI (guid_tool.py) for local Registry20 operations and a new TypeScript package napcat-dpapi with native bindings for DPAPI. Implements Registry20Utils in the webui-backend to protect/unprotect Registry20, plus backup/restore/delete helpers. Exposes new backend API handlers and routes (get/set GUID, backups, restore, reset, restart) and integrates frontend GUIDManager component and qq_manager controller methods. Propagates QQ data path via WebUiDataRuntime (setter/getter) and wires it up in framework/shell; updates Vite alias and package.json to include the new dpapi workspace. Includes native addon binaries for win32 x64/arm64 and basic tsconfig/readme/license for the new package.
This commit is contained in:
手瓜一十雪
2026-02-12 17:08:24 +08:00
parent 82d0c51716
commit 2f8569f30c
21 changed files with 776 additions and 1 deletions

View File

@@ -4,6 +4,22 @@ import { WebUiDataRuntime } from '@/napcat-webui-backend/src/helper/Data';
import { WebUiConfig } from '@/napcat-webui-backend/index';
import { isEmpty } from '@/napcat-webui-backend/src/utils/check';
import { sendError, sendSuccess } from '@/napcat-webui-backend/src/utils/response';
import { Registry20Utils } from '@/napcat-webui-backend/src/utils/guid';
// 获取 Registry20 路径的辅助函数
const getRegistryPath = () => {
// 优先从 WebUiDataRuntime 获取早期设置的 dataPath
let dataPath = WebUiDataRuntime.getQQDataPath();
if (!dataPath) {
// 回退: 从 OneBotContext 获取
const oneBotContext = WebUiDataRuntime.getOneBotContext();
dataPath = oneBotContext?.core?.dataPath;
}
if (!dataPath) {
throw new Error('QQ data path not available yet');
}
return Registry20Utils.getRegistryPath(dataPath);
};
// 获取QQ登录二维码
export const QQGetQRcodeHandler: RequestHandler = async (_, res) => {
@@ -147,3 +163,106 @@ export const QQPasswordLoginHandler: RequestHandler = async (req, res) => {
}
return sendSuccess(res, null);
};
// 重置设备信息
export const QQResetDeviceIDHandler: RequestHandler = async (_, res) => {
try {
const registryPath = getRegistryPath();
// 自动备份
try {
await Registry20Utils.backup(registryPath);
} catch (e) {
// 忽略备份错误(例如文件不存在)
}
await Registry20Utils.delete(registryPath);
return sendSuccess(res, { message: 'Device ID reset successfully (Registry20 deleted)' });
} catch (e) {
return sendError(res, `Failed to reset Device ID: ${(e as Error).message}`);
}
};
// 获取设备 GUID
export const QQGetDeviceGUIDHandler: RequestHandler = async (_, res) => {
try {
const registryPath = getRegistryPath();
const guid = await Registry20Utils.readGuid(registryPath);
return sendSuccess(res, { guid });
} catch (e) {
// 可能是文件不存在,或者非 Windows 平台,或者解密失败
return sendError(res, `Failed to get GUID: ${(e as Error).message}`);
}
};
// 设置设备 GUID
export const QQSetDeviceGUIDHandler: RequestHandler = async (req, res) => {
const { guid } = req.body;
if (!guid || typeof guid !== 'string' || guid.length !== 32) {
return sendError(res, 'Invalid GUID format, must be 32 hex characters');
}
try {
const registryPath = getRegistryPath();
// 自动备份
try {
await Registry20Utils.backup(registryPath);
} catch { }
await Registry20Utils.writeGuid(registryPath, guid);
return sendSuccess(res, { message: 'GUID set successfully' });
} catch (e) {
return sendError(res, `Failed to set GUID: ${(e as Error).message}`);
}
};
// 获取备份列表
export const QQGetGUIDBackupsHandler: RequestHandler = async (_, res) => {
try {
const registryPath = getRegistryPath();
const backups = Registry20Utils.getBackups(registryPath);
return sendSuccess(res, backups);
} catch (e) {
return sendError(res, `Failed to get backups: ${(e as Error).message}`);
}
};
// 恢复备份
export const QQRestoreGUIDBackupHandler: RequestHandler = async (req, res) => {
const { backupName } = req.body;
if (!backupName) {
return sendError(res, 'Backup name is required');
}
try {
const registryPath = getRegistryPath();
await Registry20Utils.restore(registryPath, backupName);
return sendSuccess(res, { message: 'Restored successfully' });
} catch (e) {
return sendError(res, `Failed to restore: ${(e as Error).message}`);
}
};
// 创建备份
export const QQCreateGUIDBackupHandler: RequestHandler = async (_, res) => {
try {
const registryPath = getRegistryPath();
const backupPath = await Registry20Utils.backup(registryPath);
return sendSuccess(res, { message: 'Backup created', path: backupPath });
} catch (e) {
return sendError(res, `Failed to backup: ${(e as Error).message}`);
}
};
// 重启NapCat
export const QQRestartNapCatHandler: RequestHandler = async (_, res) => {
try {
const result = await WebUiDataRuntime.requestRestartProcess();
if (result.result) {
return sendSuccess(res, { message: result.message || 'Restart initiated' });
} else {
return sendError(res, result.message || 'Restart failed');
}
} catch (e) {
return sendError(res, `Restart error: ${(e as Error).message}`);
}
};