feat: 新增看门狗汪汪汪

This commit is contained in:
时瑾 2026-01-14 23:25:54 +08:00
parent 0918b17257
commit b97a224a14
No known key found for this signature in database
GPG Key ID: 023F70A1B8F8C196
8 changed files with 133 additions and 7 deletions

View File

@ -86,6 +86,7 @@ import { GetGroupMemberList } from './group/GetGroupMemberList';
import { GetGroupFileUrl } from '@/napcat-onebot/action/file/GetGroupFileUrl';
import { GetPacketStatus } from '@/napcat-onebot/action/packet/GetPacketStatus';
import { GetCredentials } from './system/GetCredentials';
import { SetRestart } from './system/SetRestart';
import { SendGroupSign, SetGroupSign } from './extends/SetGroupSign';
import { GoCQHTTPGetGroupAtAllRemain } from './go-cqhttp/GetGroupAtAllRemain';
import { GoCQHTTPCheckUrlSafely } from './go-cqhttp/GoCQHTTPCheckUrlSafely';
@ -266,6 +267,7 @@ export function createActionMap (obContext: NapCatOneBot11Adapter, core: NapCatC
new GetGroupFileSystemInfo(obContext, core),
new GetGroupFilesByFolder(obContext, core),
new GetPacketStatus(obContext, core),
new SetRestart(obContext, core),
new GroupPoke(obContext, core),
new FriendPoke(obContext, core),
new GetUserStatus(obContext, core),

View File

@ -81,7 +81,7 @@ export const ActionName = {
CanSendRecord: 'can_send_record',
GetStatus: 'get_status',
GetVersionInfo: 'get_version_info',
// Reboot : 'set_restart',
Reboot: 'set_restart',
CleanCache: 'clean_cache',
Exit: 'bot_exit',
// go-cqhttp

View File

@ -0,0 +1,15 @@
import { ActionName } from '@/napcat-onebot/action/router';
import { OneBotAction } from '../OneBotAction';
import { writeFileSync } from 'fs';
import { join } from 'path';
export class SetRestart extends OneBotAction<void, void> {
override actionName = ActionName.Reboot;
async _handle () {
setTimeout(() => {
writeFileSync(join(this.obContext.context.pathWrapper.binaryPath, 'napcat.restart'), Date.now().toString());
process.exit(51);
}, 5);
}
}

View File

@ -0,0 +1,54 @@
@echo off
chcp 65001 >nul
net session >nul 2>&1
if %ERRORLEVEL% == 0 (
echo Administrator mode detected.
) else (
echo Please run this script in administrator mode.
powershell -Command "Start-Process 'wt.exe' -ArgumentList 'cmd /k cd /d \"%cd%\" && \"%~f0\" %*' -Verb runAs"
exit
)
setlocal enabledelayedexpansion
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_RESTART_SIGNAL=%cd%\napcat.restart
: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 "ST_MAIN_PATH=%NAPCAT_MAIN_PATH:\=/%"
echo (async () =^> {await import("file:///%ST_MAIN_PATH%")})() > "%NAPCAT_LOAD_PATH%"
if exist "%NAPCAT_RESTART_SIGNAL%" del "%NAPCAT_RESTART_SIGNAL%"
echo [%date% %time%] [Watchdog] Starting NapCat...
"%NAPCAT_LAUNCHER_PATH%" "%QQPath%" "%NAPCAT_INJECT_PATH%" %*
:watchdog
timeout /t 3 /nobreak >nul
if exist "%NAPCAT_RESTART_SIGNAL%" (
echo [%date% %time%] [Watchdog] Restart signal received. Restarting...
del "%NAPCAT_RESTART_SIGNAL%"
goto napcat_boot
)
goto watchdog

View File

@ -1,9 +1,11 @@
import { RequestHandler } from 'express';
import { writeFileSync } from 'fs';
import { join } from 'path';
import { WebUiDataRuntime } from '@/napcat-webui-backend/src/helper/Data';
import { webUiPathWrapper, 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 { WebUiConfig } from '@/napcat-webui-backend/index';
// 获取QQ登录二维码
export const QQGetQRcodeHandler: RequestHandler = async (_, res) => {
@ -108,3 +110,12 @@ export const QQRefreshQRcodeHandler: RequestHandler = async (_, res) => {
await WebUiDataRuntime.refreshQRCode();
return sendSuccess(res, null);
};
// 退出以重启重新登录
export const QQRestartHandler: RequestHandler = async (_, res) => {
sendSuccess(res, null);
setTimeout(() => {
writeFileSync(join(webUiPathWrapper.binaryPath, 'napcat.restart'), Date.now().toString());
process.exit(51);
}, 100);
};

View File

@ -10,6 +10,7 @@ import {
getAutoLoginAccountHandler,
setAutoLoginAccountHandler,
QQRefreshQRcodeHandler,
QQRestartHandler,
} from '@/napcat-webui-backend/src/api/QQLogin';
const router = Router();
@ -31,5 +32,7 @@ router.post('/GetQuickLoginQQ', getAutoLoginAccountHandler);
router.post('/SetQuickLoginQQ', setAutoLoginAccountHandler);
// router:刷新QQ登录二维码
router.post('/RefreshQRcode', QQRefreshQRcodeHandler);
// router:重启QQ
router.post('/Restart', QQRestartHandler);
export { router as QQLoginRouter };

View File

@ -1,3 +1,4 @@
import { AxiosRequestConfig } from 'axios';
import { serverRequest } from '@/utils/request';
import { SelfInfo } from '@/types/user';
@ -71,9 +72,11 @@ export default class QQManager {
});
}
public static async getQQLoginInfo () {
public static async getQQLoginInfo (config?: AxiosRequestConfig) {
const data = await serverRequest.post<ServerResponse<SelfInfo>>(
'/QQLogin/GetQQLoginInfo'
'/QQLogin/GetQQLoginInfo',
{},
config
);
return data.data.data;
}
@ -90,4 +93,8 @@ export default class QQManager {
uin,
});
}
public static async reboot () {
await serverRequest.post<ServerResponse<null>>('/QQLogin/Restart');
}
}

View File

@ -3,7 +3,7 @@ import { Button } from '@heroui/button';
import { useLocalStorage } from '@uidotdev/usehooks';
import clsx from 'clsx';
import { AnimatePresence, motion } from 'motion/react';
import { useEffect, useMemo, useRef } from 'react';
import { useEffect, useMemo, useRef, useState } from 'react';
import { ErrorBoundary } from 'react-error-boundary';
import { MdMenu, MdMenuOpen } from 'react-icons/md';
import { useLocation, useNavigate } from 'react-router-dom';
@ -11,7 +11,7 @@ import { useLocation, useNavigate } from 'react-router-dom';
import key from '@/const/key';
import errorFallbackRender from '@/components/error_fallback';
// import PageLoading from "@/components/Loading/PageLoading";
import PageLoading from '@/components/page_loading';
import SideBar from '@/components/sidebar';
import useAuth from '@/hooks/auth';
@ -52,6 +52,7 @@ const Layout: React.FC<{ children: React.ReactNode; }> = ({ children }) => {
const { isAuth, revokeAuth } = useAuth();
const dialog = useDialog();
const isOnlineRef = useRef(true);
const [isRestarting, setIsRestarting] = useState(false);
// 定期检查 QQ 在线状态,掉线时弹窗提示
useEffect(() => {
@ -68,7 +69,39 @@ const Layout: React.FC<{ children: React.ReactNode; }> = ({ children }) => {
content: '您的 QQ 账号已下线,请重新登录。',
confirmText: '重新登陆',
cancelText: '退出账户',
onConfirm: () => navigate('/qq_login'),
onConfirm: async () => {
setIsRestarting(true);
try {
await QQManager.reboot();
} catch (_e) {
// 忽略错误,因为后端正在重启关闭连接
}
// 开始轮询探测后端是否启动
const startTime = Date.now();
const maxWaitTime = 15000; // 15秒总超时
const timer = setInterval(async () => {
try {
// 尝试请求后端,设置一个较短的请求超时避免挂起
await QQManager.getQQLoginInfo({ timeout: 500 });
// 如果能走到这一步说明请求成功了
clearInterval(timer);
setIsRestarting(false);
window.location.reload();
} catch (_e) {
// 如果请求失败(后端没起来),检查是否超时
if (Date.now() - startTime > maxWaitTime) {
clearInterval(timer);
setIsRestarting(false);
dialog.alert({
title: '启动超时',
content: '后端在 15 秒内未响应,请检查 NapCat 运行日志或手动重启。',
});
}
}
}, 500); // 每 500ms 探测一次
},
onCancel: () => {
revokeAuth();
navigate('/web_login');
@ -123,6 +156,7 @@ const Layout: React.FC<{ children: React.ReactNode; }> = ({ children }) => {
backgroundPosition: 'center',
}}
>
<PageLoading loading={isRestarting} />
<SideBar
items={menus}
open={openSideBar}