UNPKG

koishi-plugin-nezha-api

Version:
1,316 lines (1,302 loc) 61.8 kB
var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __hasOwnProp = Object.prototype.hasOwnProperty; var __name = (target, value) => __defProp(target, "name", { value, configurable: true }); var __export = (target, all) => { for (var name2 in all) __defProp(target, name2, { get: all[name2], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/index.ts var src_exports = {}; __export(src_exports, { Config: () => Config, apply: () => apply, inject: () => inject, logger: () => logger, name: () => name, usage: () => usage }); module.exports = __toCommonJS(src_exports); var import_koishi = require("koishi"); var import_node_net = require("node:net"); var ValidTypes = ["v0", "v1", "komari"]; function isSiteType(type) { return ValidTypes.includes(type); } __name(isSiteType, "isSiteType"); var name = "nezha-api"; var inject = { required: ["database"], optional: ["server"] }; var logger = new import_koishi.Logger("nezha-api"); var Config = import_koishi.Schema.object({ responseTimeout: import_koishi.Schema.number().default(15e3).min(5e3).max(6e4).step(1e3).description("交互式输入时,等待消息回复的时间(单位为毫秒)"), showChangedData: import_koishi.Schema.boolean().default(true).description("站点数据发生改动时,返回的消息中是否包含数据改动"), channelRecall: import_koishi.Schema.boolean().default(true).description("是否开启群聊自动撤回"), recallTime: import_koishi.Schema.number().default(15e3).min(5e3).max(6e4).step(1e3).description("群聊自动撤回的延迟时间(单位为毫秒)"), aliveThreshold: import_koishi.Schema.number().default(300).min(5).max(3600).step(1).description("判断服务器是否在线的时间间隔(单位为秒)"), alertNotify: import_koishi.Schema.object({ enable: import_koishi.Schema.boolean().default(true).description("是否开启告警通知监听"), path: import_koishi.Schema.string().default("/nezha/notify").description("告警通知监听路径"), bodyContent: import_koishi.Schema.object({ Nezha: import_koishi.Schema.string().default(`# 探针通知\\n\\n时间:#DATETIME#\\n\\n#NEZHA#`).description("Nezha面板使用的告警通知内容模板"), Komari: import_koishi.Schema.string().default(`# 探针通知\\n\\n{{title}}\\n{{message}}`).description("Komari面板使用的告警通知内容模板") }).description("告警通知请求的body参数内容") }).description("告警通知") }); var usage = ` ## 使用说明 ### 指令:nezha * 基本语法:\`nezha\` * 指令功能:输出插件的简易信息 ### 指令:nezha help * 基本语法:\`nezha help\` * 指令功能:等价于 \`help nezha\` ### 指令:nezha add * 基本语法:\`nezha add <type:string> [url:string] [input1:string] [input2:string]\` * 指令功能:添加 \`NezhaV0\` / \`NezhaV1\` / \`Komari\` 站点的 \`url\` 和 \`token\` / \`username\` & \`password\` 至数据库,请确保 \`url\` 和 \`token\` / \`username\` & \`password\` 均有效 * 使用限制:**仅私聊可用**,支持交互式输入 ### 指令:nezha delete/del * 基本语法:\`nezha del <type:string>\` * 指令功能:删除已保存的站点数据 * 使用限制:**仅私聊可用**,支持交互式输入 ### 指令:nezha url * 基本语法:\`nezha url <type:string> [url:string]\` * 指令功能:修改数据库中记录的指定类型的站点 \`url\` ,请确保已使用 \`nezha add\` 添加过数据 * 使用限制:**仅私聊可用**,支持交互式输入 ### 指令:nezha token * 基本语法:\`nezha url [token:string]\` * 指令功能:修改数据库中记录 \`NezhaV0\` 的站点 \`token\` ,请确保已使用 \`nezha add\` 添加过数据 * 使用限制:**仅私聊可用**,支持交互式输入 ### 指令:nezha info * 基本语法:\`nezha info\` * 指令功能:查看数据库中记录的所有站点 \`url\` 和 \`token\` / \`username\` & \`password\` * 使用限制:**仅私聊可用** ### 指令:nezha all * 基本语法:\`nezha all [type:string] [tag:string]\` * 指令功能:获取 \`tag\` 分组下所有服务器的统计数据摘要,留空则返回所有数据,当且仅当 \`tag\` 为 \`untagged\` 时返回未分组的数据 * 使用限制:\`tag\` 参数仅支持 \`NezhaV0\` 站点 ### 指令:nezha list * 基本语法:\`nezha list [type:string] [tag:string]\` * 指令功能:获取 \`tag\` 分组下所有服务器的状态信息摘要,留空则返回所有数据,当且仅当 \`tag\` 为 \`untagged\` 时返回未分组的数据 * 使用限制:\`tag\` 参数仅支持 \`NezhaV0\` 站点 ### 指令:nezha id * 基本语法:\`nezha id <type:string> [id:number]\` * 指令功能:获取ID为 \`id\` 的服务器详细信息 * 使用限制:仅支持 \`NezhaV0\` 和 \`NezhaV1\` 站点,支持交互式输入 ### 指令:nezha uuid * 基本语法:\`nezha uuid [uuid:string]\` * 指令功能:获取UUID为 \`uuid\` 的服务器详细信息 * 使用限制:仅支持 \`Komari\` 站点,支持交互式输入 ### 指令:nezha search * 基本语法:\`nezha search <name:string>\` * 指令功能:搜索所有站点中名称包含关键字 \`name\` 的服务器状态信息摘要 ### 指令:nezha notify * 基本语法:\`nezha notify <type:string>\` * 指令功能:**需要公网部署**,获取不同站点的告警通知请求的部分参数,便于新增通知方式 `; function apply(ctx, config) { ctx.model.extend("nezha_site_v1", { userId: "unsigned", type: "string", url: "string", token: "string", username: "string", password: "string" }, { primary: ["userId", "type"] }); const layerName = "nezha-notify"; const processRequest = /* @__PURE__ */ __name(async (_ctx, next) => { _ctx.body = "OK"; const { platform, userId, groupId, content } = _ctx.request.body; if (platform && (userId || groupId) && content) { for (let bot of ctx.bots) { if (bot.platform === platform) { try { if (groupId && userId) { await bot.sendMessage(groupId, (0, import_koishi.h)("at", { id: userId }) + "<br/>" + content); } else if (userId) { await bot.sendPrivateMessage(userId, import_koishi.h.parse(content)); } } catch (error) { logger.error(error); } } } } return next(); }, "processRequest"); ctx.on("ready", () => { if (config.alertNotify.enable && ctx.server) { ctx.server.post(layerName, config.alertNotify.path, processRequest); if (ctx.database.tables["nezha_site"] !== void 0) { ctx.model.migrate("nezha_site", {}, async (database) => { const oldData = await database.get("nezha_site", {}); const newData = oldData.map((item) => ({ ...item, type: "v0", username: "", password: "" })); await database.upsert("nezha_site_v1", newData); database.drop("nezha_site"); }); } } }); ctx.on("dispose", () => { if (ctx.server) { ctx.server.stack = ctx.server.stack.filter((layer) => layer.name !== layerName); } }); const truncationUrl = /* @__PURE__ */ __name((url) => { const siteReg = new RegExp("^https?://", "i"); url = url.replace(siteReg, ""); while (url.endsWith("/")) { url = url.substring(0, url.length - 1); } return url; }, "truncationUrl"); const mainCmd = ctx.command("nezha", "用于查询哪吒站点服务器详细信息").action(async ({ session }) => { const { id, name: name2 } = await ctx.database.getUser(session.platform, session.userId, ["id", "name"]); const datas = await ctx.database.get("nezha_site_v1", { userId: id }); datas.sort((a, b) => ValidTypes.indexOf(a.type) - ValidTypes.indexOf(b.type)); let details = [ `Hi ${name2 || session.author.nick || session.author.name || session.username}!`, "此插件用于查询哪吒面板服务器详细信息", "免责声明:", "本机器人保证您敏感信息的安全性。", "请自行知悉潜在风险。", "===========================" ]; if (datas !== void 0 && datas.length > 0) { details.push("您的哪吒面板是:"); for (let i = 0; i < datas.length; i++) { const data = datas[i]; details.push(`- ${data.type}:${truncationUrl(data.url)}`); } details.push("使用 nezha all 开始统计数据摘要吧!"); } else { details.push("没有保存的数据。"); details.push("使用 nezha add 开始添加你的站点数据!"); } await sendQueuedWithRecall(session, details.join("\n")); return; }); mainCmd.subcommand(".help", "获取 nezha 相关指令的帮助信息").action(async ({ session }) => { return session.execute("help nezha"); }); const checkValid = /* @__PURE__ */ __name((input) => { return typeof input === "string" && input.length !== 0; }, "checkValid"); const processType = /* @__PURE__ */ __name(async (session, inputType) => { if (!isSiteType(inputType)) { session.sendQueued(`请输入站点类型,支持的类型有:${ValidTypes.join(", ")}`); inputType = await session.prompt(config.responseTimeout); if (!isSiteType(inputType)) { return "站点类型输入超时或无效"; } } return { type: inputType, bUseToken: inputType === "v0" }; }, "processType"); const processUrl = /* @__PURE__ */ __name(async (session, inputUrl) => { if (!checkValid(inputUrl)) { session.sendQueued("站点地址无效,请重新输入站点地址"); inputUrl = await session.prompt(config.responseTimeout); if (!checkValid(inputUrl)) { return "站点地址输入超时"; } } const siteReg = new RegExp("^https?://", "g"); if (!siteReg.test(inputUrl)) { return "站点地址必须以 http:// 或 https:// 开头"; } return { url: inputUrl }; }, "processUrl"); const processToken = /* @__PURE__ */ __name(async (session, inputToken) => { if (!checkValid(inputToken)) { session.sendQueued("请输入站点Token"); inputToken = await session.prompt(config.responseTimeout); if (!checkValid(inputToken)) { return "站点Token输入超时"; } } return { token: inputToken }; }, "processToken"); const processUsername = /* @__PURE__ */ __name(async (session, inputUsername) => { if (!checkValid(inputUsername)) { session.sendQueued("请输入用户名"); inputUsername = await session.prompt(config.responseTimeout); if (!checkValid(inputUsername)) { return "用户名输入超时"; } } return { username: inputUsername }; }, "processUsername"); const processPassword = /* @__PURE__ */ __name(async (session, inputPassword) => { if (!checkValid(inputPassword)) { session.sendQueued("请输入密码"); inputPassword = await session.prompt(config.responseTimeout); if (!checkValid(inputPassword)) { return "密码输入超时"; } } return { password: inputPassword }; }, "processPassword"); const inChannel = /* @__PURE__ */ __name(async (platform, channelId) => { const channelData = await ctx.database.getChannel(platform, channelId); return channelData !== void 0; }, "inChannel"); const getQueuedMessageId = /* @__PURE__ */ __name((sendResult) => { return Array.isArray(sendResult) ? sendResult[0] : sendResult; }, "getQueuedMessageId"); const sendQueuedWithRecall = /* @__PURE__ */ __name(async (session, content) => { const sendResult = await session.sendQueued(content); const msgId = getQueuedMessageId(sendResult); if (msgId && await inChannel(session.platform, session.channelId) && config.channelRecall) { ctx.setTimeout(async () => { try { await session.bot.deleteMessage(session.channelId, msgId); } catch (error) { logger.warn(error); } }, config.recallTime); } return sendResult; }, "sendQueuedWithRecall"); mainCmd.subcommand(".add <type:string> [url:string] [input1:string] [input2:string]", "添加站点数据").example("nezha add TYPE URL TOKEN|USERNAME PASSWORD").action(async ({ session }, type, url, input1, input2) => { if (await inChannel(session.platform, session.channelId)) { return "该指令仅限私聊可用"; } const procTypeRes = await processType(session, type); if (typeof procTypeRes === "string") { return procTypeRes; } const { id } = await ctx.database.getUser(session.platform, session.userId, ["id"]); const [data] = await ctx.database.get("nezha_site_v1", { userId: id, type: procTypeRes.type }); const procUrlRes = await processUrl(session, url); if (typeof procUrlRes === "string") { return procUrlRes; } let procTokenRes = null; let procUsernameRes = null; let procPasswordRes = null; if (procTypeRes.type === "v0") { procTokenRes = await processToken(session, input1); if (typeof procTokenRes === "string") { return procTokenRes; } } else { procUsernameRes = await processUsername(session, input1); if (typeof procUsernameRes === "string") { return procUsernameRes; } procPasswordRes = await processPassword(session, input2); if (typeof procPasswordRes === "string") { return procPasswordRes; } } if (data === void 0) { await ctx.database.create("nezha_site_v1", { userId: id, type: procTypeRes.type, url: procUrlRes.url, token: procTokenRes !== null ? procTokenRes.token : "", username: procUsernameRes !== null ? procUsernameRes.username : "", password: procPasswordRes !== null ? procPasswordRes.password : "" }); let retMsg = "站点数据添加成功"; if (config.showChangedData) { retMsg += ` 🔗保存的站点地址:${procUrlRes.url}`; if (procTypeRes.bUseToken) { retMsg += ` 🔑保存的站点Token:${procTokenRes.token}`; } else { retMsg += ` 👤保存的用户名:${procUsernameRes.username} 🔒保存的密码:${procPasswordRes.password}`; } } return retMsg; } else { session.sendQueued("检测到站点数据已存在,是否覆盖原有数据(Y/n)?"); const confirmRes = await session.prompt(config.responseTimeout); if (checkValid(confirmRes) && (confirmRes === "Y" || confirmRes === "y")) { await ctx.database.set("nezha_site_v1", { userId: id, type: procTypeRes.type }, { url: procUrlRes.url, token: procTokenRes !== null ? procTokenRes.token : "", username: procUsernameRes !== null ? procUsernameRes.username : "", password: procPasswordRes !== null ? procPasswordRes.password : "" }); let retMsg = "站点数据修改成功"; if (config.showChangedData) { retMsg += ` 🔗站点地址:${truncationUrl(data.url)} ➡ ${procUrlRes.url}`; if (procTypeRes.bUseToken) { retMsg += ` 🔑站点Token:${data.token} ➡ ${procTokenRes.token}`; } else { retMsg += ` 👤用户名:${data.username} ➡ ${procUsernameRes.username} 🔒密码:${data.password} ➡ ${procPasswordRes.password}`; } } return retMsg; } else { return "操作已取消"; } } }); mainCmd.subcommand(".delete <type:string>", "删除已保存的站点数据").alias(".del").example("nezha delete TYPE").action(async ({ session }, type) => { if (await inChannel(session.platform, session.channelId)) { return "该指令仅限私聊可用"; } const procTypeRes = await processType(session, type); if (typeof procTypeRes === "string") { return procTypeRes; } const { id } = await ctx.database.getUser(session.platform, session.userId, ["id"]); const [data] = await ctx.database.get("nezha_site_v1", { userId: id, type: procTypeRes.type }); if (data !== void 0) { await ctx.database.remove("nezha_site_v1", { userId: id, type: procTypeRes.type }); let retMsg = "站点数据删除成功"; if (config.showChangedData) { retMsg += ` 🔗删除的站点地址:${truncationUrl(data.url)}`; if (procTypeRes.bUseToken) { retMsg += ` 🔑删除的站点Token:${data.token}`; } else { retMsg += ` 👤删除的用户名:${data.username} 🔒删除的密码:${data.password}`; } } return retMsg; } else { return "没有站点数据可供删除,请先使用 nezha add 添加站点数据"; } }); mainCmd.subcommand(".url <type:string> [url:string]", "更新站点地址").option("url", "站点地址").action(async ({ session }, type, url) => { const channelData = await ctx.database.getChannel(session.platform, session.channelId); if (channelData !== void 0) { return "该指令仅限私聊可用"; } const procTypeRes = await processType(session, type); if (typeof procTypeRes === "string") { return procTypeRes; } const { id } = await ctx.database.getUser(session.platform, session.userId, ["id"]); const [data] = await ctx.database.get("nezha_site_v1", { userId: id, type: procTypeRes.type }); if (data !== void 0) { const procUrlRes = await processUrl(session, url); if (typeof procUrlRes === "string") { return procUrlRes; } else { await ctx.database.set("nezha_site_v1", { userId: id }, { url: procUrlRes.url }); let retMsg = "站点地址更新成功"; if (config.showChangedData) { retMsg += ` 🔗站点地址:${truncationUrl(data.url)} ➡ ${procUrlRes.url}`; } return retMsg; } } else { return "没有站点数据可供更新,请先使用 nezha add 添加站点数据"; } }); mainCmd.subcommand(".token", "更新站点Token").option("token", "站点Token").action(async ({ session }, token) => { const channelData = await ctx.database.getChannel(session.platform, session.channelId); if (channelData !== void 0) { return "该指令仅限私聊可用"; } const { id } = await ctx.database.getUser(session.platform, session.userId, ["id"]); const [data] = await ctx.database.get("nezha_site_v1", { userId: id, type: "v0" }); if (data !== void 0) { const procTokenRes = await processToken(session, token); if (typeof procTokenRes === "string") { return procTokenRes; } else { await ctx.database.set("nezha_site_v1", { userId: id, type: "v0" }, { token: procTokenRes.token }); let retMsg = "站点Token更新成功"; if (config.showChangedData) { retMsg += ` 🔑站点Token:${data.token} ➡ ${procTokenRes.token}`; } return retMsg; } } else { return "没有站点数据可供更新,请先使用 nezha add 添加站点数据"; } }); mainCmd.subcommand(".info", "查看站点地址和Token").action(async ({ session }) => { if (await inChannel(session.platform, session.channelId)) { return "该指令仅限私聊可用"; } const { id } = await ctx.database.getUser(session.platform, session.userId, ["id"]); const datas = await ctx.database.get("nezha_site_v1", { userId: id }); datas.sort((a, b) => ValidTypes.indexOf(a.type) - ValidTypes.indexOf(b.type)); if (datas !== void 0 && datas.length > 0) { let retMsg = "这是您保存的站点数据:"; for (let i = 0; i < datas.length; i++) { const data = datas[i]; retMsg += ` - ${data.type}: 🔗站点地址:${truncationUrl(data.url)}`; if (data.type === "v0") { retMsg += ` 🔑站点Token:${data.token}`; } else { retMsg += ` 👤用户名:${data.username} 🔒密码:${data.password}`; } } return retMsg; } else { return "没有站点数据可供查询,请先使用 nezha add 添加站点数据"; } }); const listPath = "/api/v1/server/list"; const detailsPath = "/api/v1/server/details"; const untagged = "untagged"; const buildUrl = /* @__PURE__ */ __name((baseUrl, path) => { baseUrl = baseUrl.trim(); while (baseUrl.endsWith("/")) { baseUrl = baseUrl.substring(0, baseUrl.length - 1); } return baseUrl + path; }, "buildUrl"); const buildHeader = /* @__PURE__ */ __name((token) => { let header = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36" }; if (token !== "") { header["Authorization"] = token; } return header; }, "buildHeader"); const getListPath = /* @__PURE__ */ __name((data) => { if (data.type === "v0") { return buildUrl(data.url, "/api/v1/server/list"); } if (data.type === "v1") { return buildUrl(data.url, "/api/v1/server"); } if (data.type === "komari") { return buildUrl(data.url, "/api/nodes"); } return "未知的站点类型"; }, "getListPath"); const getDetailsPath = /* @__PURE__ */ __name((data) => { if (data.type === "v0") { return buildUrl(data.url, "/api/v1/server/details"); } if (data.type === "v1") { return buildUrl(data.url, "/api/v1/server"); } if (data.type === "komari") { return buildUrl(data.url, "/api/nodes"); } return "未知的站点类型"; }, "getDetailsPath"); const isServerAlive = /* @__PURE__ */ __name((lastActive) => { let timeNow = Math.floor(Date.now() / 1e3); return timeNow - lastActive < config.aliveThreshold; }, "isServerAlive"); const getCpuCoreNum = /* @__PURE__ */ __name((cpuInfos) => { let totalCoreNum = 0; if (cpuInfos && cpuInfos.length !== 0) { for (let i = 0; i < cpuInfos.length; i++) { const cpuInfo = cpuInfos[i]; const cpuInfoArr = cpuInfo.split(" "); if (cpuInfoArr.length >= 3) { const coreNum = Number(cpuInfoArr[cpuInfoArr.length - 3]); if (!Number.isNaN(coreNum)) { totalCoreNum += coreNum; } } } } return totalCoreNum; }, "getCpuCoreNum"); const naturalsize = /* @__PURE__ */ __name((value, fractionDigits = 1) => { const base = 1024; const suffixes = "KMGTPEZY"; const absValue = Math.abs(value); let unit, suffix; for (let i = 0; i < suffixes.length; i++) { unit = base ** (i + 2); suffix = suffixes[i]; if (absValue < unit) break; } return (base * value / unit).toFixed(fractionDigits) + suffix; }, "naturalsize"); const percentage = /* @__PURE__ */ __name((value) => { value = Math.min(Math.abs(value), 1); return (value * 100).toFixed(2) + "%"; }, "percentage"); const getNow = /* @__PURE__ */ __name(() => { return new Intl.DateTimeFormat("zh-CN", { year: "numeric", month: "numeric", day: "numeric", hour: "numeric", minute: "numeric", second: "numeric", hour12: false, timeZone: "Asia/Shanghai", timeZoneName: "longOffset" }).format(new Date(Date.now())).replaceAll("/", "-"); }, "getNow"); const authenticate = /* @__PURE__ */ __name(async (data) => { if (data.type === "v0") { return { success: true, token: data.token }; } if (data.type === "v1") { const res = await ctx.http.post(buildUrl(data.url, "/api/v1/login"), { username: data.username, password: data.password }).catch((err) => { return { success: false, token: err.message }; }); if (res !== void 0) { if ("error" in res) { return { success: false, token: `访问站点失败,错误信息:${res.error}` }; } if ("success" in res && res.success === true) { return { success: true, token: `Bearer ${res.data.token}` }; } } return { success: false, token: "认证失败,请检查用户名和密码。" }; } if (data.type === "komari") { return { success: true, token: "" }; } return { success: false, token: "未知的站点类型" }; }, "authenticate"); const checkResponse = /* @__PURE__ */ __name((type, res) => { if (type === "v0") { if ("code" in res && "message" in res) { if (res.code !== 0) { return { success: false, message: res.message }; } if ("result" in res) { if (res.result.length === 0) { return { success: false, message: "未检测到服务器" }; } return { success: true, message: "" }; } } return { success: false, message: "无法解析的响应数据" }; } if (type === "v1") { if ("success" in res && res.success === true) { if ("data" in res) { if (res.data.length === 0) { return { success: false, message: "未检测到服务器" }; } return { success: true, message: "" }; } } return { success: false, message: "无法解析的响应数据" }; } if (type === "komari") { if ("status" in res && "message" in res) { if (res.status !== "success") { return { success: false, message: res.message }; } if ("data" in res) { if (res.data.length === 0) { return { success: false, message: "未检测到服务器" }; } return { success: true, message: "" }; } } return { success: false, message: "无法解析的响应数据" }; } return { success: false, message: "未知的站点类型" }; }, "checkResponse"); const getKomariLatestStatus = /* @__PURE__ */ __name(async (data) => { if (data.type !== "komari") { return null; } const randomId = Math.floor(Math.random() * 1e3); const latestStatusRes = await ctx.http.post(buildUrl(data.url, "/api/rpc2"), { jsonrpc: "2.0", method: "common:getNodesLatestStatus", params: {}, id: randomId }).catch((err) => { return { error: err.message }; }); if (latestStatusRes !== void 0) { if ("error" in latestStatusRes) { return { success: false, message: `访问站点失败,错误信息:${latestStatusRes.error}` }; } if ("result" in latestStatusRes && latestStatusRes.id === randomId) { return { success: true, data: latestStatusRes.result }; } } return { success: false, message: "无法解析的响应数据" }; }, "getKomariLatestStatus"); const generateOverviewInfo = /* @__PURE__ */ __name((data, res, tag, statusData) => { let info = { titlePrefix: "", serverNum: 0, onlineNum: 0, cpuTotal: 0, memUsage: 0, memTotal: 0, memUsed: 0, swapUsage: 0, swapTotal: 0, swapUsed: 0, diskUsage: 0, diskTotal: 0, diskUsed: 0, netInSpeed: 0, netOutSpeed: 0, netInTransfer: 0, netOutTransfer: 0, transParity: 0 }; if (data.type === "v0") { if (tag !== "" && tag !== untagged) { info.titlePrefix = `[${tag}]分组的`; } for (let i = 0; i < res.result.length; i++) { const serverInfo = res.result[i]; if (tag !== "" && serverInfo.tag !== tag && (tag === untagged && serverInfo.tag !== "")) { continue; } info.serverNum += 1; if (isServerAlive(serverInfo.last_active)) { info.onlineNum += 1; } info.cpuTotal += getCpuCoreNum(serverInfo.host.CPU); info.memTotal += serverInfo.host.MemTotal; info.memUsed += serverInfo.status.MemUsed; info.swapTotal += serverInfo.host.SwapTotal; info.swapUsed += serverInfo.status.SwapUsed; info.diskTotal += serverInfo.host.DiskTotal; info.diskUsed += serverInfo.status.DiskUsed; info.netInTransfer += serverInfo.status.NetInTransfer; info.netOutTransfer += serverInfo.status.NetOutTransfer; info.netInSpeed += serverInfo.status.NetInSpeed; info.netOutSpeed += serverInfo.status.NetOutSpeed; } } if (data.type === "v1") { for (let i = 0; i < res.data.length; i++) { const serverInfo = res.data[i]; info.serverNum += 1; if (isServerAlive(new Date(serverInfo.last_active).getTime() / 1e3)) { info.onlineNum += 1; } info.cpuTotal += getCpuCoreNum(serverInfo.host.cpu); info.memTotal += serverInfo.host.mem_total; info.memUsed += serverInfo.state.mem_used; info.swapTotal += serverInfo.host.swap_total; info.swapUsed += serverInfo.state.swap_used; info.diskTotal += serverInfo.host.disk_total; info.diskUsed += serverInfo.state.disk_used; info.netInTransfer += serverInfo.state.net_in_transfer; info.netOutTransfer += serverInfo.state.net_out_transfer; info.netInSpeed += serverInfo.state.net_in_speed; info.netOutSpeed += serverInfo.state.net_out_speed; } } if (data.type === "komari") { for (let i = 0; i < res.data.length; i++) { const serverInfo = res.data[i]; const serverStatus = statusData[serverInfo.uuid]; info.serverNum += 1; if (isServerAlive(new Date(serverInfo.updated_at).getTime() / 1e3)) { info.onlineNum += 1; } info.cpuTotal += serverInfo.cpu_cores; info.memTotal += serverInfo.mem_total; info.memUsed += serverStatus.ram; info.swapTotal += serverInfo.swap_total; info.swapUsed += serverStatus.swap; info.diskTotal += serverInfo.disk_total; info.diskUsed += serverStatus.disk; info.netInTransfer += serverStatus.net_total_down; info.netOutTransfer += serverStatus.net_total_up; info.netInSpeed += serverStatus.net_in; info.netOutSpeed += serverStatus.net_out; } } info.memUsage = info.memTotal !== 0 ? info.memUsed / info.memTotal : 0; info.swapUsage = info.swapTotal !== 0 ? info.swapUsed / info.swapTotal : 0; info.diskUsage = info.diskTotal !== 0 ? info.diskUsed / info.diskTotal : 0; if (info.netOutTransfer * info.netInTransfer === 0) { info.transParity = 0; } else if (info.netOutTransfer >= info.netInTransfer) { info.transParity = info.netInTransfer / info.netOutTransfer; } else { info.transParity = info.netOutTransfer / info.netInTransfer; } return info; }, "generateOverviewInfo"); mainCmd.subcommand(".all [type:string] [tag:string]", "获取所有服务器的统计数据摘要").action(async ({ session }, type, tag) => { if (tag === void 0) { tag = ""; } const { id } = await ctx.database.getUser(session.platform, session.userId, ["id"]); let query = { userId: id }; if (type !== void 0) { const procTypeRes = await processType(session, type); if (typeof procTypeRes === "string") { return procTypeRes; } query["type"] = procTypeRes.type; } const datas = await ctx.database.get("nezha_site_v1", query); datas.sort((a, b) => ValidTypes.indexOf(a.type) - ValidTypes.indexOf(b.type)); if (datas !== void 0 && datas.length > 0) { let retMsg = []; for (let i = 0; i < datas.length; i++) { const data = datas[i]; retMsg.push(`- ${data.type}:`); const authRes = await authenticate(data); if (!authRes.success) { retMsg.push(authRes.token); continue; } const res = await ctx.http.get(getDetailsPath(data), { headers: buildHeader(authRes.token), params: { tag: tag === untagged ? "" : tag } }).catch((err) => { return { error: err.message }; }); if (res !== void 0) { if ("error" in res) { retMsg.push(`访问站点失败,错误信息:${res.error}`); continue; } const check = checkResponse(data.type, res); if (!check.success) { retMsg.push(check.message); continue; } const komariStatus = await getKomariLatestStatus(data); if (data.type === "komari") { if (!komariStatus.success) { retMsg.push(komariStatus.message); continue; } } const overviewInfo = generateOverviewInfo(data, res, tag, komariStatus?.data); let details = [ `${overviewInfo.titlePrefix}服务器统计数据摘要`, "===========================", `服务器数量: ${overviewInfo.serverNum}`, `在线服务器: ${overviewInfo.onlineNum}`, `CPU核心数: ${overviewInfo.cpuTotal}`, `内存: ${percentage(overviewInfo.memUsage)} [${naturalsize(overviewInfo.memUsed)}/${naturalsize(overviewInfo.memTotal)}]`, `交换: ${percentage(overviewInfo.swapUsage)} [${naturalsize(overviewInfo.swapUsed)}/${naturalsize(overviewInfo.swapTotal)}]`, `磁盘: ${percentage(overviewInfo.diskUsage)} [${naturalsize(overviewInfo.diskUsed)}/${naturalsize(overviewInfo.diskTotal)}]`, `下行速度: ↓${naturalsize(overviewInfo.netInSpeed)}/s`, `上行速度: ↑${naturalsize(overviewInfo.netOutSpeed)}/s`, `下行流量: ↓${naturalsize(overviewInfo.netInTransfer)}`, `上行流量: ↑${naturalsize(overviewInfo.netOutTransfer)}`, `流量对等性: ${percentage(overviewInfo.transParity)}`, `` ]; retMsg = retMsg.concat(details); continue; } retMsg.push("访问站点失败,请联系管理员确认错误信息"); } retMsg.push(`更新于: ${getNow()}`); await sendQueuedWithRecall(session, retMsg.join("\n")); return; } else { return "没有站点数据可供使用,请先使用 nezha add 添加站点数据"; } }); const generateDigestInfo = /* @__PURE__ */ __name((data, res, tag) => { let info = []; if (data.type === "v0") { let tagArr = []; let serverNum, offlineNum, dualNum; serverNum = offlineNum = dualNum = 0; res.result.sort((a, b) => { return a.id - b.id; }); for (let i = 0; i < res.result.length; i++) { const serverInfo = res.result[i]; if (tag !== "" && serverInfo.tag !== tag && (tag === untagged && serverInfo.tag !== "")) { continue; } tagArr.push(serverInfo.tag); const alive = isServerAlive(serverInfo.last_active); serverNum += 1; offlineNum += alive ? 0 : 1; dualNum += isDualStackServer(serverInfo.ipv4, serverInfo.ipv6) ? 1 : 0; } info.push(`服务器数量: ${serverNum}`); if (tag === "") { info.push(`分组的数量: ${Array.from(new Set(tagArr)).length}`); } info.push(`离线服务器: ${offlineNum}`); info.push(`双栈服务器: ${dualNum}`); } if (data.type === "v1") { let serverNum, offlineNum, dualNum; serverNum = offlineNum = dualNum = 0; res.data.sort((a, b) => { return a.id - b.id; }); for (let i = 0; i < res.data.length; i++) { const serverInfo = res.data[i]; const alive = isServerAlive(new Date(serverInfo.last_active).getTime() / 1e3); serverNum += 1; offlineNum += alive ? 0 : 1; dualNum += isDualStackServer(serverInfo.geoip.ip.ipv4_addr, serverInfo.geoip.ip.ipv6_addr) ? 1 : 0; } info.push(`服务器数量: ${serverNum}`); info.push(`离线服务器: ${offlineNum}`); info.push(`双栈服务器: ${dualNum}`); } if (data.type === "komari") { let serverNum, offlineNum; serverNum = offlineNum = 0; for (let i = 0; i < res.data.length; i++) { const serverInfo = res.data[i]; serverNum += 1; offlineNum += isServerAlive(new Date(serverInfo.updated_at).getTime() / 1e3) ? 0 : 1; } info.push(`服务器数量: ${serverNum}`); info.push(`离线服务器: ${offlineNum}`); } return info; }, "generateDigestInfo"); const generateListInfo = /* @__PURE__ */ __name((data, res, tag) => { let info = { titlePrefix: "", headerRow: "", serverStates: [] }; if (data.type === "v0") { if (tag !== "" && tag !== untagged) { info.titlePrefix = `[${tag}]分组的`; } info.headerRow = "ID 状态 分组 服务器名"; res.result.sort((a, b) => { return a.id - b.id; }); for (let i = 0; i < res.result.length; i++) { const serverInfo = res.result[i]; if (tag !== "" && serverInfo.tag !== tag && (tag === untagged && serverInfo.tag !== "")) { continue; } const alive = isServerAlive(serverInfo.last_active); const status = alive ? "❇️在线" : "☠️离线"; info.serverStates.push(`${serverInfo.id} ${status} ${serverInfo.tag === "" ? "🈳" : serverInfo.tag} ${serverInfo.name}`); } } if (data.type === "v1") { info.headerRow = "ID 状态 服务器名"; res.data.sort((a, b) => { return a.id - b.id; }); for (let i = 0; i < res.data.length; i++) { const serverInfo = res.data[i]; const alive = isServerAlive(new Date(serverInfo.last_active).getTime() / 1e3); const status = alive ? "❇️在线" : "☠️离线"; info.serverStates.push(`${serverInfo.id} ${status} ${serverInfo.name}`); } } if (data.type === "komari") { info.headerRow = "UUID 状态 服务器名"; for (let i = 0; i < res.data.length; i++) { const serverInfo = res.data[i]; const alive = isServerAlive(new Date(serverInfo.updated_at).getTime() / 1e3); const status = alive ? "❇️在线" : "☠️离线"; info.serverStates.push(`${serverInfo.uuid} ${status} ${serverInfo.name}`); } } return info; }, "generateListInfo"); mainCmd.subcommand(".list [type:string] [tag:string]", "获取所有服务器的状态信息摘要").action(async ({ session }, type, tag) => { if (tag === void 0) { tag = ""; } const { id } = await ctx.database.getUser(session.platform, session.userId, ["id"]); let query = { userId: id }; if (type !== void 0) { const procTypeRes = await processType(session, type); if (typeof procTypeRes === "string") { return procTypeRes; } query["type"] = procTypeRes.type; } const datas = await ctx.database.get("nezha_site_v1", query); datas.sort((a, b) => ValidTypes.indexOf(a.type) - ValidTypes.indexOf(b.type)); if (datas !== void 0 && datas.length > 0) { let retMsg = []; for (let i = 0; i < datas.length; i++) { const data = datas[i]; retMsg.push(`- ${data.type}:`); const authRes = await authenticate(data); if (!authRes.success) { retMsg.push(authRes.token); continue; } const res = await ctx.http.get(getListPath(data), { headers: buildHeader(authRes.token), params: { tag: tag === untagged ? "" : tag } }).catch((err) => { return { error: err.message }; }); if (res !== void 0) { if ("error" in res) { retMsg.push(`访问站点失败,错误信息:${res.error}`); continue; } const check = checkResponse(data.type, res); if (!check.success) { retMsg.push(check.message); continue; } const digestInfo = generateDigestInfo(data, res, tag); const listInfo = generateListInfo(data, res, tag); let details = [ `${listInfo.titlePrefix}服务器状态信息摘要`, "===========================", ...digestInfo, "===========================", listInfo.headerRow, ...listInfo.serverStates, "" ]; retMsg = retMsg.concat(details); continue; } retMsg.push("访问站点失败,请联系管理员确认错误信息"); } await sendQueuedWithRecall(session, retMsg.join("\n")); return; } else { return "没有站点数据可供使用,请先使用 nezha add 添加站点数据"; } }); const getCountryFlag = /* @__PURE__ */ __name((countryCode) => { const symbols = [ "🇦", "🇧", "🇨", "🇩", "🇪", "🇫", "🇬", "🇭", "🇮", "🇯", "🇰", "🇱", "🇲", "🇳", "🇴", "🇵", "🇶", "🇷", "🇸", "🇹", "🇺", "🇻", "🇼", "🇽", "🇾", "🇿" ]; const BASE = "A".charCodeAt(0); let res = []; for (let i = 0; i < countryCode.length; i++) { res.push(symbols[countryCode[i].toUpperCase().charCodeAt(0) - BASE]); } return res.join(""); }, "getCountryFlag"); const maskIPv4 = /* @__PURE__ */ __name((ipv4) => { if (typeof ipv4 !== "string" || ipv4 === "" || ipv4.split(".").length !== 4) { return "🈳"; } let ipv4Arr = ipv4.split("."); for (let i = 2; i < ipv4Arr.length; i++) { ipv4Arr[i] = "**"; } return ipv4Arr.join("."); }, "maskIPv4"); const maskIPv6 = /* @__PURE__ */ __name((ipv6) => { if (typeof ipv6 !== "string" || ipv6 === "") { return "🈳"; } let ipv6Arr = ipv6.split("::"); if (ipv6Arr.length === 1) { ipv6Arr = ipv6.split(":"); if (ipv6.length !== 8) { return "🈳"; } ipv6Arr.splice(4, 4, "**", "**", "**", "**"); return ipv6Arr.join(":"); } else if (ipv6Arr.length === 2) { let front = ipv6Arr[0].split(":"); let end = ipv6Arr[1].split(":"); let maskCount = Math.floor((front.length + end.length) / 2); for (let i = end.length - 1; i >= 0; i--) { if (maskCount === 0) { break; } end[i] = "**"; maskCount--; } for (let i = front.length - 1; i >= 0; i--) { if (maskCount === 0) { break; } front[i] = "**"; maskCount--; } return front.join(":") + "::" + end.join(":"); } else { return "🈳"; } }, "maskIPv6"); const normalizeIpValue = /* @__PURE__ */ __name((value) => { if (typeof value !== "string") { return ""; } const normalized = value.trim(); const lower = normalized.toLowerCase(); if (normalized === "" || lower === "null" || lower === "undefined") { return ""; } return normalized; }, "normalizeIpValue"); const hasIPv4Address = /* @__PURE__ */ __name((value) => { return (0, import_node_net.isIP)(normalizeIpValue(value)) === 4; }, "hasIPv4Address"); const hasIPv6Address = /* @__PURE__ */ __name((value) => { return (0, import_node_net.isIP)(normalizeIpValue(value)) === 6; }, "hasIPv6Address"); const isDualStackServer = /* @__PURE__ */ __name((ipv4, ipv6) => { return hasIPv4Address(ipv4) && hasIPv6Address(ipv6); }, "isDualStackServer"); const convertTime = /* @__PURE__ */ __name((bootTime) => { if (bootTime === 0) { return "未运行"; } let timeNow = Math.floor(Date.now() / 1e3); let time = timeNow - bootTime; const day = 24 * 60 * 60; const hour = 60 * 60; return `${Math.floor(time / day)}天${Math.floor(time % day / hour)}小时`; }, "convertTime"); const convertPeriod = /* @__PURE__ */ __name((seconds) => { if (seconds < 60) { return `${seconds}秒`; } const minute = 60; const hour = 60 * minute; const day = 24 * hour; if (seconds < hour) { return `${Math.floor(seconds / minute)}分${seconds % minute}秒`; } if (seconds < day) { return `${Math.floor(seconds / hour)}时${Math.floor(seconds % hour / minute)}分`; } return `${Math.floor(seconds / day)}天${Math.floor(seconds % day / hour)}时`; }, "convertPeriod"); const generateDetailsById = /* @__PURE__ */ __name((data, res, serverId) => { let details = []; if (data.type === "v0") { const serverInfo = res.result.find((item) => item?.id === serverId); if (!serverInfo) { return details; } const alive = isServerAlive(serverInfo.last_active); const status = alive ? "❇️在线" : "☠️离线"; let cpuInfo = ""; if (serverInfo.host.CPU !== null && serverInfo.host.CPU.length !== 0) { cpuInfo = serverInfo.host.CPU[0]; } const memTotal = serverInfo.host.MemTotal; const memUsed = serverInfo.status.MemUsed; const swapTotal = serverInfo.host.SwapTotal; const swapUsed = serverInfo.status.SwapUsed; const diskTotal = serverInfo.host.DiskTotal; const diskUsed = serverInfo.status.DiskUsed; const netInTransfer = serverInfo.status.NetInTransfer; const netOutTransfer = serverInfo.status.NetOutTransfer; const netInSpeed = serverInfo.status.NetInSpeed; const netOutSpeed = serverInfo.status.NetOutSpeed; const memUsage = memTotal !== 0 ? memUsed / memTotal : 0; const swapUsage = swapTotal !== 0 ? swapUsed / swapTotal : 0; const diskUsage = diskTotal !== 0 ? diskUsed / diskTotal : 0; details = [ `${getCountryFlag(serverInfo.host.CountryCode)} ${serverInfo.name} ${status}`, "===========================", `id: ${serverId}`, `tag: ${serverInfo.tag === "" ? "🈳" : serverInfo.tag}`, `ipv4: ${maskIPv4(serverInfo.ipv4)}`, `ipv6: ${maskIPv6(serverInfo.ipv6)}`, `平台: ${serverInfo.host.Platform} ${serverInfo.host.PlatformVersion}`, `CPU信息: ${cpuInfo}`, `运行时间: ${convertTime(serverInfo.host.BootTime)}`, `负载: ${serverInfo.status.Load1.toFixed(2)} ${serverInfo.status.Load5.toFixed(2)} ${serverInfo.status.Load15.toFixed(2)}`, `CPU: ${serverInfo.status.CPU.toFixed(2)}% [${serverInfo.host.Arch}]`, `内存: ${percentage(memUsage)} [${naturalsize(memUsed)}/${naturalsize(memTotal)}]`, `交换: ${percentage(swapUsage)} [${naturalsize(swapUsed)}/${naturalsize(swapTotal)}]`, `磁盘: ${percentage(diskUsage)} [${naturalsize(diskUsed)}/${naturalsize(diskTotal)}]`, `流量: ↓${naturalsize(netInTransfer)} ↑${naturalsize(netOutTransfer)}`, `网速: ↓${naturalsize(netInSpeed)}/s ↑${naturalsize(netOutSpeed)}/s`, ` 更新于: ${getNow()}` ]; } if (data.type === "v1") { for (let i = 0; i < res.data.length; i++) { const serverInfo = res.data[i]; if (serverInfo.id !== serverId) { continue; } const alive = isServerAlive(new Date(serverInfo.last_active).getTime() / 1e3); const status = alive ? "❇️在线" : "☠️离线"; let cpuInfo = ""; if (serverInfo.host.cpu != null && serverInfo.host.cpu.length !== 0) { cpuInfo = serverInfo.host.cpu[0]; } const memTotal = serverInfo.host.mem_total ?? 0; const memUsed = serverInfo.state.mem_used ?? 0; const swapTotal = serverInfo.host.swap_total ?? 0; const swapUsed = serverInfo.state.swap_used ?? 0; const diskTotal = serverInfo.host.disk_total ?? 0; const diskUsed = serverInfo.state.disk_used ?? 0; const netInTransfer = serverInfo.state.net_in_transfer ?? 0; const netOutTransfer = serverInfo.state.net_out_transfer ?? 0; const netInSpeed = serverInfo.state.net_in_speed ?? 0; const netOutSpeed = serverInfo.state.net_out_speed ?? 0; const memUsage = memTotal !== 0 ? memUsed / memTotal : 0; const swapUsage = swapTotal !== 0 ? swapUsed / swapTotal : 0; const diskUsage = diskTotal !== 0 ? diskUsed / diskTotal : 0; const { load_1 = 0, load_5 = 0, load_15 = 0, cpu = 0 } = serverInfo.state ?? {}; const arch = serverInfo.host.arch ?? ""; details = [ `${getCountryFlag(serverInfo.geoip.country_code ?? "")} ${serverInfo.name} ${status}`, "===========================", `id: ${serverId}`, `ipv4: ${maskIPv4(serverInfo.geoip.ip.ipv4_addr)}`, `ipv6: ${maskIPv6(serverInfo.geoip.ip.ipv6_addr)}`, `平台: ${serverInfo.host.platform ?? ""} ${serverInfo.host.platform_version ?? ""}`, `CPU信息: ${cpuInfo}`, `运行时间: ${convertTime(serverInfo.host.boot_time ?? 0)}`, `负载: ${load_1.toFixed(2)} ${load_5.toFixed(2)} ${load_15.toFixed(2)}`, `CPU: ${cpu.toFixed(2)}% [${arch}]`, `内存: ${percentage(memUsage)} [${naturalsize(memUsed)}/${naturalsize(memTotal)}]`, `交换: ${percentage(swapUsage)} [${naturalsize(swapUsed)}/${naturalsize(swapTotal)}]`, `磁盘: ${percentage(diskUsage)} [${naturalsize(diskUsed)}/${naturalsize(diskTotal)}]`, `流量: ↓${naturalsize(netInTransfer)} ↑${naturalsize(netOutTransfer)}`, `网速: ↓${naturalsize(netInSpeed)}/s ↑${naturalsize(netOutSpeed)}/s`, ` 更新于: ${getNow()}` ]; } } return details; }, "generateDetailsById"); mainCmd.subcommand(".id <type:string> [serverId:integer]", "通过id查询服务器详细信息").action(async ({ session }, type, serverId) => { const procTypeRes = await processType(session, type); if (typeof procTypeRes === "string") { return procTypeRes; } if (procTypeRes.type === "komari") { return "komari类型站点仅支持通过UUID查询服务器详情,请使用 nezha uuid 指令。"; } if (serverId === void 0) { session.sendQueued("请输入服务器id"); serverId = Number(await session.prompt(config.responseTimeout)); if (!Number.isSafeInteger(serverId)) { return "参数 id 输入无效,请提供一个整数。"; } } const { id } = await ctx.database.getUser(session.platform, session.userId, ["id"]); const [data] = await ctx.database.get("nezha_site_v1", { userId: id, type: procTypeRes.type }); if (data !== void 0) { const authRes = await authenticate(data); if (!authRes.success) { return authRes.token; } const res = await ctx.http.get(getDetailsPath(data), { headers: buildHeader(authRes.token), params: { id: serverId } }).catch((err) => { return { error: err.message }; }); if (res !== void 0) { if ("error" in res) { return `访问站点失败,错误信息:${res.error}`; } const check = checkResponse(data.type, res); if (!check.success) { return check.message; } const details = generateDetailsById(data, res, serverId); if (details.length === 0) { return `未找到ID为 ${serverId} 的服务器。`; } await sendQueuedWithRecall(session, details.join("\n")); return; } return `访问站点失败,请联系管理员确认错误信息`; } else { return "没有站点数据可供使用,请先使用 nezha add 添加站点数据"; } }); const generateDetailsByUUID = /* @__PURE__ */ __name((data, res, statusData, uuid) => { let details = []; if (data.type === "komari") { for (let i = 0; i < res.data.length; i++) { const serverInfo = res.data[i]; if (serverInfo.uuid !== uuid) { continue; } const serverStatus = statusData[serverInfo.uuid]; const alive = isServerAlive(new Date(serverInfo.updated_at).getTime() / 1e3); const status = alive ? "❇️在线" : "☠️离线"; const memTotal = serverInfo.mem_total; const memUsed = serverStatus.ram; const swapTotal = serverInfo.swap_total; const swapUsed = serverStatus.swap; const diskTotal = serverInfo.disk_total; const diskUsed = serverStatus.disk; const netInTransfer = serverStatus.net_total_down; const netOutTransfer = serverStatus.net_total_up; const netInSpeed = serverStatus.net_in; const netOutSpeed = serverStatus.net_out; const memUsage = memTotal !== 0 ? memUsed / memTotal : 0; const swapUsage = swapTotal !== 0 ? swapUsed / swapTotal : 0; const diskUsage = diskTotal !== 0 ? diskUsed / diskTotal : 0; details = [ `${serverInfo.region} ${serverInfo.name} ${status}`, "===========================", `uuid: ${uuid}`, `平台: ${serverInfo.os}`, `CPU信息: ${serverInfo.cpu_name}`, `运行时间: ${convertPeriod(serverStatus.uptime)}`, `负载: ${serverStatus.load.toFixed(2)} ${serverStatus.load5.toFixed(2)} ${serverStatus.load15.toFixed(2)}`, `CPU: ${serverStatus.cpu.toFixed(2)}% [${serverInfo.arch}]`, `内存: ${percentage(memUsage)} [${naturalsize(memUsed)}/${naturalsize(memTotal)}]`, `交换: ${percentage(swapUsage)} [${naturalsize(swapUsed)}/${natu