mirror of
https://github.com/NapNeko/NapCatQQ.git
synced 2026-03-01 00:00:26 +00:00
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.
149 lines
4.3 KiB
TypeScript
149 lines
4.3 KiB
TypeScript
import { Input } from '@heroui/input';
|
||
import { Button } from '@heroui/button';
|
||
import { Divider } from '@heroui/divider';
|
||
import { useRequest } from 'ahooks';
|
||
import { useEffect, useState } from 'react';
|
||
import { Controller, useForm } from 'react-hook-form';
|
||
import toast from 'react-hot-toast';
|
||
|
||
import SaveButtons from '@/components/button/save_buttons';
|
||
import GUIDManager from '@/components/guid_manager';
|
||
import PageLoading from '@/components/page_loading';
|
||
|
||
import QQManager from '@/controllers/qq_manager';
|
||
import ProcessManager from '@/controllers/process_manager';
|
||
import { waitForBackendReady } from '@/utils/process_utils';
|
||
|
||
const LoginConfigCard = () => {
|
||
const [isRestarting, setIsRestarting] = useState(false);
|
||
const {
|
||
data: quickLoginData,
|
||
loading: quickLoginLoading,
|
||
error: quickLoginError,
|
||
refreshAsync: refreshQuickLogin,
|
||
} = useRequest(QQManager.getQuickLoginQQ);
|
||
const {
|
||
control,
|
||
handleSubmit: handleOnebotSubmit,
|
||
formState: { isSubmitting },
|
||
setValue: setOnebotValue,
|
||
} = useForm<{
|
||
quickLoginQQ: string;
|
||
}>({
|
||
defaultValues: {
|
||
quickLoginQQ: '',
|
||
},
|
||
});
|
||
|
||
const reset = () => {
|
||
setOnebotValue('quickLoginQQ', quickLoginData ?? '');
|
||
};
|
||
|
||
const onSubmit = handleOnebotSubmit(async (data) => {
|
||
try {
|
||
await QQManager.setQuickLoginQQ(data.quickLoginQQ);
|
||
toast.success('保存成功');
|
||
} catch (error) {
|
||
const msg = (error as Error).message;
|
||
toast.error(`保存失败: ${msg}`);
|
||
}
|
||
});
|
||
|
||
const onRefresh = async () => {
|
||
try {
|
||
await refreshQuickLogin();
|
||
toast.success('刷新成功');
|
||
} catch (error) {
|
||
const msg = (error as Error).message;
|
||
toast.error(`刷新失败: ${msg}`);
|
||
}
|
||
};
|
||
|
||
const onRestartProcess = async () => {
|
||
setIsRestarting(true);
|
||
try {
|
||
const result = await ProcessManager.restartProcess();
|
||
toast.success(result.message || '进程重启请求已发送');
|
||
|
||
// 轮询探测后端是否恢复
|
||
const isReady = await waitForBackendReady(
|
||
30000, // 30秒超时
|
||
() => {
|
||
setIsRestarting(false);
|
||
toast.success('进程重启完成');
|
||
},
|
||
() => {
|
||
setIsRestarting(false);
|
||
toast.error('后端在 30 秒内未响应,请检查 NapCat 运行日志');
|
||
}
|
||
);
|
||
|
||
if (!isReady) {
|
||
setIsRestarting(false);
|
||
}
|
||
} catch (error) {
|
||
const msg = (error as Error).message;
|
||
toast.error(`进程重启失败: ${msg}`);
|
||
setIsRestarting(false);
|
||
}
|
||
};
|
||
|
||
useEffect(() => {
|
||
reset();
|
||
}, [quickLoginData]);
|
||
|
||
if (quickLoginLoading) return <PageLoading loading />;
|
||
|
||
return (
|
||
<>
|
||
<title>OneBot配置 - NapCat WebUI</title>
|
||
<div className='flex-shrink-0 w-full'>快速登录QQ</div>
|
||
<Controller
|
||
control={control}
|
||
name='quickLoginQQ'
|
||
render={({ field }) => (
|
||
<Input
|
||
{...field}
|
||
label='快速登录QQ'
|
||
placeholder='请输入QQ号'
|
||
isDisabled={!!quickLoginError}
|
||
errorMessage={quickLoginError ? '获取快速登录QQ失败' : undefined}
|
||
/>
|
||
)}
|
||
/>
|
||
<SaveButtons
|
||
onSubmit={onSubmit}
|
||
reset={reset}
|
||
isSubmitting={isSubmitting || quickLoginLoading}
|
||
refresh={onRefresh}
|
||
/>
|
||
<div className='flex-shrink-0 w-full mt-6 pt-6 border-t border-divider'>
|
||
<div className='mb-3 text-sm text-default-600'>进程管理</div>
|
||
<Button
|
||
color='warning'
|
||
variant='flat'
|
||
onPress={onRestartProcess}
|
||
isLoading={isRestarting}
|
||
isDisabled={isRestarting}
|
||
fullWidth
|
||
>
|
||
{isRestarting ? '正在重启进程...' : '重启进程'}
|
||
</Button>
|
||
<div className='mt-2 text-xs text-default-500'>
|
||
重启进程将关闭当前 Worker 进程,等待 3 秒后启动新进程
|
||
</div>
|
||
</div>
|
||
<Divider className='mt-6' />
|
||
<div className='flex-shrink-0 w-full mt-4'>
|
||
<div className='mb-3 text-sm text-default-600'>设备 GUID 管理</div>
|
||
<div className='text-xs text-default-400 mb-3'>
|
||
GUID 是设备登录唯一识别码,存储在 Registry20 文件中。修改后需重启生效。
|
||
</div>
|
||
<GUIDManager showRestart={false} />
|
||
</div>
|
||
</>
|
||
);
|
||
};
|
||
|
||
export default LoginConfigCard;
|