feat: 修复部分电脑无法看到 Bot 状态问题

This commit is contained in:
zhiyu1998 2024-11-22 20:20:14 +08:00
parent 618982e865
commit caf1d7e550
19 changed files with 164 additions and 184 deletions

56
apps/webUI.js Normal file
View File

@ -0,0 +1,56 @@
import { REDIS_YUNZAI_WEBUI } from "../constants/constant.js";
import config from "../model/config.js";
import { redisSetKey } from "../utils/redis-util.js";
import { getBotLoginInfo, getBotStatus, getBotVersionInfo } from "../utils/yunzai-util.js";
export class WebUI extends plugin {
constructor() {
super({
name: "R插件 WebUI 开关",
dsc: "R插件 WebUI 开关",
event: "message",
priority: 4000,
rule: [
{
reg: "^#(r|R)wss$",
fnc: "rWebSwitch",
permission: "master",
},
{
reg: "^#(r|R)ws$",
fnc: "rWebStatus",
permission: "master",
}
]
});
// 配置文件
this.toolsConfig = config.getConfig("tools");
// 加载WebUI开关
this.isOpenWebUI = this.toolsConfig.isOpenWebUI;
}
async rWebSwitch(e) {
config.updateField("tools", "isOpenWebUI", !this.isOpenWebUI);
const realIsOpenWebUI = config.getConfig("tools").isOpenWebUI;
if (realIsOpenWebUI) {
Promise.all([getBotStatus(e), getBotVersionInfo(e), getBotLoginInfo(e)]).then(values => {
const status = values[0].data;
const versionInfo = values[1].data;
const loginInfo = values[2].data;
redisSetKey(REDIS_YUNZAI_WEBUI, {
...status,
...versionInfo,
...loginInfo
})
})
}
// 这里有点延迟,需要写反
e.reply(`R插件 WebUI${ realIsOpenWebUI ? "开启\n🚀 请重启以启动 WebUI" : "关闭" }`);
return true;
}
async rWebStatus(e) {
e.reply(`R插件 WebUI${ this.toolsConfig.isOpenWebUI ? "开启" : "关闭" }`);
return true;
}
}

View File

@ -93,10 +93,10 @@ export const REDIS_YUNZAI_CLOUDSONGLIST = "Yz:rconsole:tools:cloudsonglist";
export const REDIS_YUNZAI_WHITELIST = "Yz:rconsole:tools:whitelist"; export const REDIS_YUNZAI_WHITELIST = "Yz:rconsole:tools:whitelist";
/** /**
* 番剧列表缓存 * WEBUI需要数据的缓存
* @type {string} * @type {string}
*/ */
export const REDIS_YUNZAI_ANIMELIST = "Yz:rconsole:tools:anime"; export const REDIS_YUNZAI_WEBUI = "Yz:rconsole:tools:webui";
export const TWITTER_BEARER_TOKEN = ""; export const TWITTER_BEARER_TOKEN = "";

View File

@ -11,6 +11,7 @@
"axios": "^1.3.4", "axios": "^1.3.4",
"chart.js": "^4.4.6", "chart.js": "^4.4.6",
"form-data": "^4.0.1", "form-data": "^4.0.1",
"ioredis": "^5.4.1",
"js-yaml": "^4.1.0", "js-yaml": "^4.1.0",
"next": "^14.2.16", "next": "^14.2.16",
"node-id3": "^0.2.6", "node-id3": "^0.2.6",

View File

@ -1,3 +1,12 @@
import Header from "../components/header.jsx";
import Sidebar from "../components/sidebar.jsx";
import { DrawerProvider } from "../contexts/drawer-context.js";
export default function Page() { export default function Page() {
return <h1><a href="/r">进入控制面板</a></h1> return (
<DrawerProvider>
<Header/>
<Sidebar />
</DrawerProvider>
)
} }

View File

@ -0,0 +1,12 @@
import { REDIS_YUNZAI_WEBUI } from "../../../../../constants/constant.js";
import { redis } from "../../../../utils/redis.js";
export async function GET(req, res) {
const botInfo = JSON.parse(await redis.get(REDIS_YUNZAI_WEBUI));
return new Response(JSON.stringify(botInfo), {
status: 200,
headers: { 'Content-Type': 'application/json' },
});
}

View File

@ -1,10 +0,0 @@
import axiosInstance from "../../../../../utils/axiosInstance.js";
export async function GET(request, { params }) {
const { pid } = params;
const napcatResp = await axiosInstance.get(`/${ pid }`);
return new Response(JSON.stringify(napcatResp), {
status: 200,
headers: { 'Content-Type': 'application/json' },
});
}

View File

@ -1,12 +0,0 @@
import Sidebar from "../../components/sidebar.jsx";
import Header from "../../components/header.jsx";
import { DrawerProvider } from "../../contexts/drawer-context.js";
export default function Page() {
return (
<DrawerProvider>
<Header/>
<Sidebar />
</DrawerProvider>
)
}

View File

@ -1,17 +1,21 @@
"use client" "use client"
import { useState, useEffect } from 'react'; import { useState, useEffect } from 'react';
import { BOT_INFO_URL } from "../constants/api.js";
import { useDrawer } from "../contexts/drawer-context.js"; import { useDrawer } from "../contexts/drawer-context.js";
import { getUserInfo } from "../utils/napact.js";
import ThemeToggle from "./ThemeToggle.jsx"; import ThemeToggle from "./ThemeToggle.jsx";
export default function Header () { export default function Header () {
const { toggleDrawer } = useDrawer(); const { toggleDrawer } = useDrawer();
const [user, setUser] = useState({ user_id: null, nickname: '' }); const [user, setUser] = useState(null);
useEffect(() => { useEffect(() => {
getUserInfo().then(setUser); fetch(BOT_INFO_URL)
.then(response => {
return response.json();
})
.then(data => setUser(data))
}, []); }, []);
return ( return (
@ -49,12 +53,12 @@ export default function Header () {
<div className="w-10 rounded-full"> <div className="w-10 rounded-full">
<img <img
alt="头像" alt="头像"
src={`http://q1.qlogo.cn/g?b=qq&nk=${user.user_id}&s=100`}/> src={`http://q1.qlogo.cn/g?b=qq&nk=${user?.user_id}&s=100`}/>
</div> </div>
</div> </div>
<div className="mt-1.5"> <div className="mt-1.5">
<div className="font-bold">{user.nickname || "未获取"}</div> <div className="font-bold">{user?.nickname || "未获取"}</div>
<div className="text-sm opacity-50">{user.user_id || "NaN"}</div> <div className="text-sm opacity-50">{user?.user_id || "NaN"}</div>
</div> </div>
</div> </div>
</div> </div>

View File

@ -1,18 +1,16 @@
import { getStatus, getUserInfo, getVersionInfo } from "../../utils/napact.js";
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import { BOT_INFO_URL } from "../../constants/api.js";
export function BotItem() { export function BotItem() {
const [user, setUser] = useState({ user_id: null, nickname: '' }); const [user, setUser] = useState(null);
const [status, setStatus] = useState({ online: false, good: false, stat: {} });
const [versionInfo, setVersionInfo] = useState({ app_name: "", app_version: "", protocol_version: "" });
useEffect(() => { useEffect(() => {
getUserInfo().then(setUser); fetch(BOT_INFO_URL)
getStatus().then(setStatus); .then(response => {
getVersionInfo().then(setVersionInfo); return response.json();
})
.then(data => setUser(data))
}, []); }, []);
return ( return (
@ -20,22 +18,22 @@ export function BotItem() {
<div className="card-body"> <div className="card-body">
<h2 className="card-title">🐔状态</h2> <h2 className="card-title">🐔状态</h2>
<div className="flex flex-row pt-5 justify-between items-center"> <div className="flex flex-row pt-5 justify-between items-center">
<div className={ `avatar ${ status.online ? "online" : "offline" }` }> <div className={ `avatar ${ user?.online ? "online" : "offline" }` }>
<div className="w-24 rounded-full"> <div className="w-24 rounded-full">
<img src={ `http://q1.qlogo.cn/g?b=qq&nk=${ user.user_id }&s=100` }/> <img src={ `http://q1.qlogo.cn/g?b=qq&nk=${ user?.user_id }&s=100` }/>
</div> </div>
</div> </div>
<div className="flex flex-col ml-12 space-y-2"> <div className="flex flex-col ml-12 space-y-2">
<div className="space-y-2"> <div className="space-y-2">
<div className="font-bold">昵称{ user.nickname || "未获取" }</div> <div className="font-bold">昵称{ user?.nickname || "未获取" }</div>
<div className="text-sm opacity-50">QQ号{ user.user_id || "NaN" }</div> <div className="text-sm opacity-50">QQ号{ user?.user_id || "NaN" }</div>
</div> </div>
<div className="space-y-2"> <div className="space-y-2">
<div className="font-bold">协议信息</div> <div className="font-bold">协议信息</div>
<div className="space-x-1"> <div className="space-x-1">
<div className="badge badge-ghost">{ versionInfo.app_name }</div> <div className="badge badge-ghost">{ user?.app_name }</div>
<div className="badge badge-ghost">{ versionInfo.app_version }</div> <div className="badge badge-ghost">{ user?.app_version }</div>
<div className="badge badge-ghost">{ versionInfo.protocol_version }</div> <div className="badge badge-ghost">{ user?.protocol_version }</div>
</div> </div>
</div> </div>
</div> </div>

View File

@ -1,5 +1,5 @@
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import { NETWORK_BASE_URL } from "../../constants/system.js"; import { NETWORK_BASE_URL } from "../../constants/api.js";
// //
const TESTING_LINKS = [ const TESTING_LINKS = [

View File

@ -1,7 +1,7 @@
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { CircularProgressbar, buildStyles } from 'react-circular-progressbar'; import { CircularProgressbar, buildStyles } from 'react-circular-progressbar';
import 'react-circular-progressbar/dist/styles.css'; import 'react-circular-progressbar/dist/styles.css';
import { SYSTEM_BASE_URL } from "../../constants/system.js"; import { SYSTEM_BASE_URL } from "../../constants/api.js";
export default function System() { export default function System() {
const [systemInfo, setSystemInfo] = useState(null); const [systemInfo, setSystemInfo] = useState(null);

View File

@ -3,3 +3,5 @@ const BASE_URL = "/r/api";
export const SYSTEM_BASE_URL = `${BASE_URL}/system`; export const SYSTEM_BASE_URL = `${BASE_URL}/system`;
export const NETWORK_BASE_URL = `${BASE_URL}/network?url=`; export const NETWORK_BASE_URL = `${BASE_URL}/network?url=`;
export const BOT_INFO_URL = `${ BASE_URL }/bot`;

View File

@ -1,11 +0,0 @@
export const NAPCAT_BASE_URL = "/r/api/napcat";
/**
* 获取登录号信息
* @type {string}
*/
export const NAPCAT_GET_LOGIN_INFO = `${NAPCAT_BASE_URL}/get_login_info`;
export const NAPCAT_GET_STATUS = `${ NAPCAT_BASE_URL }/get_status`;
export const NAPCAT_GET_VERSION_INFO = `${ NAPCAT_BASE_URL }/get_version_info`;

View File

@ -1,50 +0,0 @@
import axios from 'axios';
// 创建 Axios 实例
const axiosInstance = axios.create({
baseURL: 'http://192.168.31.230:2537', // 基础请求地址
timeout: 5000, // 设置请求超时时间,可根据需要调整
headers: {
'Content-Type': 'application/json',
},
});
// 请求拦截器
axiosInstance.interceptors.request.use(
(config) => {
// 这里可以添加请求前的处理逻辑,例如添加 token
// const token = localStorage.getItem('token');
// if (token) {
// config.headers.Authorization = `Bearer ${token}`;
// }
return config;
},
(error) => {
// 请求错误处理
return Promise.reject(error);
}
);
// 响应拦截器
axiosInstance.interceptors.response.use(
(response) => {
// 响应成功处理
return response.data;
},
(error) => {
// 响应错误处理
if (error.response) {
// 服务器返回的错误
console.error('Error:', error.response.status, error.response.data);
} else if (error.request) {
// 请求未收到服务器响应
console.error('No response received:', error.request);
} else {
// 设置请求时发生的错误
console.error('Request setup error:', error.message);
}
return Promise.reject(error);
}
);
export default axiosInstance;

View File

@ -1,17 +0,0 @@
import { NAPCAT_GET_LOGIN_INFO, NAPCAT_GET_STATUS, NAPCAT_GET_VERSION_INFO } from "../constants/napcat.js";
export async function getUserInfo() {
const userInfo = await fetch(NAPCAT_GET_LOGIN_INFO).then(resp => resp.json());
const { user_id, nickname } = userInfo.data;
return { user_id, nickname }
}
export async function getStatus() {
const status = await fetch(NAPCAT_GET_STATUS).then(resp => resp.json());
return status.data;
}
export async function getVersionInfo() {
const versionInfo = await fetch(NAPCAT_GET_VERSION_INFO).then(resp => resp.json());
return versionInfo.data;
}

17
server/utils/redis.js Normal file
View File

@ -0,0 +1,17 @@
import fs from "fs";
import Redis from "ioredis";
import yaml from "js-yaml";
import path from "path";
const configPath = path.join(process.cwd(), "../../../", "config", 'config', 'redis.yaml');
const yamlContent = await fs.promises.readFile(configPath, 'utf8');
const config = yaml.load(yamlContent);
export const redis = new Redis({
port: config.port,
host: config.host,
username: config.username,
password: config.password,
db: config.db,
})

View File

@ -1,8 +1,10 @@
import { spawn } from 'child_process'; import { spawn } from 'child_process';
import child_process from "node:child_process";
import { getBotLoginInfo, getBotStatus, getBotVersionInfo } from "./utils/yunzai-util.js";
logger.info(`[R插件][Next.js监测], 父进程 PID: ${process.pid}`); logger.info(`[R插件][Next.js监测], 父进程 PID: ${process.pid}`);
let childProcess = null; let nextjsProcess = null;
// 构建应用程序 // 构建应用程序
export const buildNextJs = () => { export const buildNextJs = () => {
@ -10,7 +12,7 @@ export const buildNextJs = () => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const buildProcess = spawn('pnpm', ['run', 'build'], { const buildProcess = spawn('pnpm', ['run', 'build'], {
cwd: './plugins/rconsole-plugin/server', cwd: './plugins/rconsole-plugin/server',
stdio: 'inherit', stdio: 'ignore',
shell: true, shell: true,
}); });
@ -32,24 +34,24 @@ export const startNextJs = (mode = 'start') => {
logger.info(logger.yellow(`[R插件][Next.js监测],启动 Next.js ${mode} 进程...`)); logger.info(logger.yellow(`[R插件][Next.js监测],启动 Next.js ${mode} 进程...`));
childProcess = spawn('pnpm', ['run', script], { nextjsProcess = spawn('pnpm', ['run', script], {
cwd: './plugins/rconsole-plugin', // 指定工作目录 cwd: './plugins/rconsole-plugin', // 指定工作目录
stdio: 'inherit', // 继承父进程的标准输入输出 stdio: ['ignore', 'ignore', 'ignore', 'ipc'], // 继承父进程的标准输入输出
shell: true, shell: true,
}); });
// 子进程异常退出时捕获信号 // 子进程异常退出时捕获信号
childProcess.on('close', (code) => { nextjsProcess.on('close', (code) => {
logger.error(`[R插件][Next.js监测]Next.js 进程发生异常 ${code}`); logger.error(`[R插件][Next.js监测]Next.js 进程发生异常 ${code}`);
childProcess = null; nextjsProcess = null;
}); });
}; };
// 捕获父进程退出信号 // 捕获父进程退出信号
const cleanup = () => { const cleanup = () => {
logger.info(logger.yellow('[R插件][Next.js监测] 父进程退出,终止子进程...')); logger.info(logger.yellow('[R插件][Next.js监测] 父进程退出,终止子进程...'));
if (childProcess) { if (nextjsProcess) {
childProcess.kill(); // 终止子进程 nextjsProcess.kill(); // 终止子进程
} }
process.exit(); process.exit();
}; };

View File

@ -86,51 +86,3 @@ export async function redisExistAndUpdateObject(key, updateKey, updateObj) {
await redisSetKey(key, objs); await redisSetKey(key, objs);
} }
} }
/**
* 删除某个key
* @param key
* @returns {Promise<number>}
* @example
* const result = await redisDeleteKey('myKey');
* console.log(result); // 1 if key was deleted, 0 if key did not exist
*/
export async function redisDeleteKey(key) {
return redis.del(key);
}
/**
* 获取所有的key
* @returns {Promise<Array<string>>}
* @example
* const keys = await redisGetAllKeys();
* console.log(keys); // ['key1', 'key2', ...]
*/
export async function redisGetAllKeys() {
return redis.keys('*');
}
/**
* 设置某个key的过期时间
* @param key
* @param seconds
* @returns {Promise<boolean>}
* @example
* const result = await redisExpireKey('myKey', 3600);
* console.log(result); // true if timeout was set, false if key does not exist
*/
export async function redisExpireKey(key, seconds) {
return redis.expire(key, seconds);
}
/**
* 获取某个key的剩余生存时间
* @param key
* @returns {Promise<number>}
* @example
* const ttl = await redisTTLKey('myKey');
* console.log(ttl); // time to live in seconds, -1 if key does not have timeout, -2 if key does not exist
*/
export async function redisTTLKey(key) {
return redis.ttl(key);
}

View File

@ -124,4 +124,31 @@ export async function getReplyMsg(e) {
"message_id" : msgId "message_id" : msgId
}) })
return msg.data return msg.data
} }
/**
* 获取机器人信息
* @param e
* @returns {Promise<*>}
*/
export async function getBotLoginInfo(e) {
return await e.bot.sendApi("get_login_info");
}
/**
* 获取运行状态
* @param e
* @returns {Promise<*>}
*/
export async function getBotStatus(e) {
return await e.bot.sendApi("get_status");
}
/**
* 获取版本信息
* @param e
* @returns {Promise<*>}
*/
export async function getBotVersionInfo(e) {
return await e.bot.sendApi("get_version_info");
}