mirror of
https://github.com/NapNeko/NapCatQQ.git
synced 2026-03-03 09:10:25 +00:00
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:
@@ -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}`);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user