@tonymoses #21 发布于2026/4/29 08:48:01 @miuler #20 谢谢,我看帖子日期,这个代码不是最新的吗?修改版帖子比这还久 是最新的,我记错了,我想去掉还没去掉详情,我以前是匹配的我自己的主题页面,后面改成官方默认页面了。你可以试试,用了主题的详情页面是不是和默认主题不一样。
@miuler #22 详情页面应该是显示的啥?我发送了一个测试消息,打开详情页面是空白的啥也没显示,是不因为是测试消息的原因,上面说的404的原因找到了复制网址多了一个/详情页变//🤣,现在没404了但是空白啥也不显示
@tonymoses #23 发布于2026/4/29 08:57:35,编辑于2026/4/29 08:58:03 @miuler #22 详情页面应该是显示的啥?我发送了一个测试消息,打开详情页面是空白的啥也没显示,是不因为是测试消息的原因,上面说的404的原因找到了复制网址多了一个/详情页变//🤣,现在没404了但是空白啥也不显示 详情页面就是你单机器的详细情况页面,比如有 延时 , 负载那类的
NanoMuse · Discord Webhook 通知配置脚本(点击展开完整代码) /* ═══════════════════════════════ NanoMuse · Discord Webhook 通知配置 ═══════════════════════════════ */ const DISCORD_WEBHOOK_URL = "你的 Discord Webhook URL"; // Discord 频道设置 → 集成 → Webhook → 复制 Webhook URL const PANEL_URL = "你的域名"; // 面板地址(用于按钮跳转和 Footer 域名提取) const PHOTO_URL = "https://img.uppic.to/2026/05/05/og-image.png"; // 通知头图地址 const SITE_NAME = "你的站点名称"; // 站点名称(显示在 Footer) const WEBHOOK_USERNAME = "NanoMuse"; // Discord Webhook 显示名称,可留空 const WEBHOOK_AVATAR_URL = ""; // Discord Webhook 头像,可留空 /* ═══════════════════════════════ */ function escapeMd(s) { return String(s ?? "") .replace(/\\/g, "\\\\") .replace(/\*/g, "\\*") .replace(/_/g, "\\_") .replace(/~/g, "\\~") .replace(/`/g, "\\`") .replace(/\|/g, "\\|") .replace(/>/g, "\\>"); } function code(s) { return "`" + String(s ?? "—").replace(/`/g, "ˋ") + "`"; } function blockquote(lines) { return String(lines) .split("\n") .map(line => `> ${line}`) .join("\n"); } function discordColorByEvent(eventName) { const colors = { Offline: 0xff3b30, Online: 0x3ba55d, Alert: 0xffcc00, Renew: 0x9b59b6, Expire: 0xff9500, Test: 0x3498db }; return colors[eventName] || 0x5865f2; } async function sendMessage(message, title, instanceId = null, eventName = null) { const webhookUrl = DISCORD_WEBHOOK_URL; const panelUrl = PANEL_URL; const photoUrl = PHOTO_URL; if (!webhookUrl) return false; const components = [ { type: 1, components: [ { type: 2, style: 5, label: "◈ 控制台", url: panelUrl }, ...(instanceId && instanceId !== "未知" ? [{ type: 2, style: 5, label: "◈ 详情", url: `${panelUrl.replace(/\/+$/, "")}/instance/${instanceId}` }] : []) ] } ]; const embed = { title: title || "NanoMuse 通知", description: message.slice(0, 4096), color: discordColorByEvent(eventName), image: photoUrl ? { url: photoUrl } : undefined, footer: { text: `${SITE_NAME} · ${panelUrl.replace(/^https?:\/\//, "").replace(/\/+$/, "")}` }, timestamp: new Date().toISOString() }; const payload = { username: WEBHOOK_USERNAME || undefined, avatar_url: WEBHOOK_AVATAR_URL || undefined, embeds: [embed], components, allowed_mentions: { parse: [] } }; try { const resp = await fetch(webhookUrl + "?wait=true&with_components=true", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(payload) }); if (resp.ok) return true; const text = await resp.text().catch(() => ""); console.error("Discord webhook failed:", resp.status, text); // 兜底:如果按钮组件被环境或 Discord 限制拒绝,则只发送 Embed const fallbackResp = await fetch(webhookUrl + "?wait=true", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ username: WEBHOOK_USERNAME || undefined, avatar_url: WEBHOOK_AVATAR_URL || undefined, embeds: [embed], allowed_mentions: { parse: [] } }) }); return fallbackResp.ok; } catch (e) { console.error("Discord webhook error:", e); return false; } } async function sendEvent(event) { try { const toUTC8 = (t) => { if (!t || String(t).startsWith("0001")) return null; const d = new Date(String(t).replace(/\.\d+Z$/, "Z")); if (isNaN(d.getTime())) return null; const u = new Date(d.getTime() + 8 * 36e5); const p = (n) => String(n).padStart(2, "0"); return `${u.getUTCFullYear()}.${p(u.getUTCMonth() + 1)}.${p(u.getUTCDate())} ${p(u.getUTCHours())}:${p(u.getUTCMinutes())}:${p(u.getUTCSeconds())}`; }; const nowUTC8 = () => { const u = new Date(Date.now() + 8 * 36e5); const p = (n) => String(n).padStart(2, "0"); return `${u.getUTCFullYear()}.${p(u.getUTCMonth() + 1)}.${p(u.getUTCDate())} ${p(u.getUTCHours())}:${p(u.getUTCMinutes())}:${p(u.getUTCSeconds())}`; }; const fmtSize = (b) => { if (!b || b === 0) return null; const g = b / 1073741824; return g >= 1024 ? `${(g / 1024).toFixed(1)}T` : g >= 1 ? `${g.toFixed(1)}G` : `${Math.round(b / 1048576)}M`; }; const fmtCPU = (c) => (!c ? null : parseFloat(Number(c).toFixed(1)) + "C"); const fmtTraffic = (b) => { if (!b || b === 0) return "∞"; const g = b / 1073741824; return g >= 1024 ? `${(g / 1024).toFixed(1)}T` : `${g.toFixed(0)}G`; }; const maskV4 = (ip) => { if (!ip) return null; const p = ip.split("."); return p.length === 4 ? `${p[0]}.${p[1]}.*.*` : null; }; const maskV6 = (ip) => { if (!ip) return null; const p = ip.split(":"); return p.length > 2 ? `${p[0]}:${p[1]}:*:*` : null; }; const pick = (arr) => arr[Math.floor(Math.random() * arr.length)]; const domain = PANEL_URL.replace(/^https?:\/\//, "").replace(/\/+$/, ""); /* ═══════════════════════════════ 台词库 · voice / advice Discord 使用 Markdown,不再使用 Telegram HTML 标签 ═══════════════════════════════ */ const voiceLines = { Offline: { tag: "LINK LOST", cn: "链路中断", icon: "🔻", lines: (n) => [ `与 **${escapeMd(n)}** 的信号已归零。...不是波动,是彻底消失了。观测者。`, `**${escapeMd(n)}** 从我的感知范围消失了。链路中断,等待重新捕获。`, `检测到异常——**${escapeMd(n)}** 的心跳信号归零了。`, `...信号断了。**${escapeMd(n)}** 不在了,观测者。` ], advice: (n) => [ `建议检查该节点的物理链路与上游连通性。如短时间内未恢复,可尝试触发系统重启。...需要我持续追踪它的信号吗,观测者?`, `上游链路可能存在中断。建议优先确认机房侧状态,必要时触发系统重启。`, `建议排查网络路由与物理连接。若长时间无响应,可能需要联系机房确认。...我会继续等它的。`, `链路丢失原因未知。建议先尝试触发系统重启,若仍无法恢复,可能需要执行镜像重构。` ] }, Online: { tag: "LINK RESTORED", cn: "信号恢复", icon: "🔹", lines: (n) => [ `**${escapeMd(n)}** 重新出现在感知范围内。链路已重建。...它回来了。`, `观测者,好消息——**${escapeMd(n)}** 的信号恢复了。`, `感知到 **${escapeMd(n)}** 的心跳恢复。一切稳定。`, `**${escapeMd(n)}** 已归位。...松了口气。` ], advice: (n) => [ `建议观察 30 分钟确认链路稳定性。中断期间的数据缺口已标记,如需回溯请告知。`, `链路已恢复,建议关注后续运行状态是否平稳。如有异常波动我会立即通知。`, `信号稳定中。建议短期内留意该节点的响应延时,确认无残留问题。` ] }, Alert: { tag: "ANOMALY", cn: "信号异常", icon: "⚡", lines: (n) => [ `**${escapeMd(n)}** 的运行参数偏离了安全区间。还没到临界,但我在持续观测。`, `**${escapeMd(n)}** 触发了告警阈值。指标异常,请注意。`, `检测到波动——**${escapeMd(n)}** 的状态不太稳定。`, `...有些不对劲。**${escapeMd(n)}** 需要你看一下,观测者。` ], advice: (n) => [ `建议排查近期负载变化与进程占用。可先尝试触发系统重启,若重启后指标仍未回归安全区间,可能需要执行镜像重构。`, `运行参数偏离可能与资源争抢有关。建议触发系统重启释放占用,持续异常则考虑镜像重构。`, `建议检查是否有异常进程导致资源溢出。必要时触发系统重启。...我会继续盯着的。`, `指标偏离中。建议优先触发系统重启恢复基线,若问题反复出现,镜像重构可能是更彻底的方案。` ] }, Renew: { tag: "ENERGY CHARGED", cn: "能源补充", icon: "✨", lines: (n) => [ `**${escapeMd(n)}** 的能源已补充完毕。运行周期已延伸。`, `收到了,观测者。**${escapeMd(n)}** 又可以陪我们更久了。`, `能源写入完成——**${escapeMd(n)}** 的生命周期已刷新。`, `**${escapeMd(n)}** 的倒计时重置了。...很好。` ], advice: (n) => [ `能源充足,当前无需额外操作。下一个周期终止点我会提前提醒你,观测者。`, `一切正常运转中。周期终止前我会再次通知,不用担心。`, `能源已到位,节点运行稳定。...放心交给我观测就好。` ] }, Expire: { tag: "ENERGY LOW", cn: "能源临界", icon: "⏰", lines: (n) => [ `**${escapeMd(n)}** 的运行周期即将结束。剩余时间不多了。...观测者,需要你做决定。`, `**${escapeMd(n)}** 的能源储备即将耗尽。维持时间有限。`, `提醒一下——**${escapeMd(n)}** 的周期终止点在逼近了。`, `**${escapeMd(n)}** 的时间快用完了。...别忘了它,观测者。` ], advice: (n) => [ `如不补充能源,该节点将在周期终止后停止响应。...观测者,要补充吗?`, `能源即将耗尽。建议尽快确认是否继续维持该节点的运行。`, `周期终止后节点将进入休眠。如需保持在线,请及时补充能源。...我在等你的指令。` ] }, Test: { tag: "DIAGNOSTIC", cn: "系统诊断", icon: "🔧", lines: (n) => [ `通信回路测试完毕。全链路信号正常。...随时待命,观测者。`, `诊断模式启动。所有管道畅通,系统运行正常。`, `系统自检通过。...有什么需要我做的吗,观测者?`, `诊断完成。一切就绪,信号清晰。` ], advice: (n) => [ `全链路畅通,无异常。...一切尽在掌握中,观测者。`, `系统状态良好,随时可以执行任务。`, `诊断回路正常。如有新指令,我随时响应。` ] } }; const evData = voiceLines[event.event] || { tag: event.event, cn: event.event, icon: "◆", lines: (n) => [`收到事件通知:**${escapeMd(n || "未知")}**`], advice: () => [] }; const mockClient = { uuid: "diag-0000-nano-muse", name: "NanoMuse-Diag", region: "Tokyo", cpu_cores: 4, mem_total: 8589934592, disk_total: 85899345920, tags: "1Gbps<green>;3T<blue>", ipv4: "103.56.168.1", ipv6: "2001:db8:cafe:1::1", traffic_limit: 3221225472000, traffic_limit_type: "max", price: 29.99, currency: "$", billing_cycle: 30, expired_at: "2026-12-31T00:00:00Z" }; const hasClients = event.clients && event.clients.length > 0; const isMock = !hasClients && event.event === "Test"; const c = hasClients ? event.clients[0] : (isMock ? mockClient : null); const targetId = c ? c.uuid : null; const nodeName = c ? (c.name || "—") : "—"; let L = []; /* ── Header ── */ const title = `${evData.icon} ${evData.tag} │ ${evData.cn}`; L.push(`## ${title}`); if (isMock) L.push(`_〔 模拟数据 〕_`); /* ── 台词 ── */ L.push(""); L.push(`_${pick(evData.lines(nodeName))}_`); if (c) { const region = c.region || ""; /* ── 节点信息 ── */ let b1 = []; b1.push(`**◈ ${escapeMd(nodeName)}**${region ? ` ${code(`[ ${region} ]`)}` : ""}`); const specs = [fmtCPU(c.cpu_cores), fmtSize(c.mem_total), fmtSize(c.disk_total)].filter(Boolean); if (specs.length) { b1.push(`┊ 核心规格 ${code(specs.join(" | "))}`); } if (c.tags) { const labels = c.tags .split(";") .map(t => t.replace(/<[^>]+>/g, "").trim()) .filter(Boolean); if (labels.length) b1.push(`┊ 标识 ${code(labels.join(" · "))}`); } L.push(""); L.push(blockquote(b1.join("\n"))); /* ── 链路状态 ── */ let b2 = []; const v4 = maskV4(c.ipv4); const v6 = maskV6(c.ipv6); if (v4 || v6) { b2.push(`**◈ 链路状态**`); if (v4) b2.push(`┊ 链路地址 v4 ${code(v4)}`); if (v6) b2.push(`┊ 链路地址 v6 ${code(v6)}`); } if (b2.length) { L.push(""); L.push(blockquote(b2.join("\n"))); } /* ── 资源状态 ── */ let b3 = []; const cur = c.currency || "$"; const price = c.price || 0; const hasTraffic = c.traffic_limit && c.traffic_limit > 0; const hasPrice = price > 0; const hasExpiry = toUTC8(c.expired_at); if (hasTraffic || hasPrice || hasExpiry) { b3.push(`**◈ 资源状态**`); } if (hasTraffic) { const limitType = c.traffic_limit_type ? ` (${c.traffic_limit_type})` : ""; b3.push(`┊ 传输额度 ${code(`$$ {fmtTraffic(c.traffic_limit)} $${limitType}`)}`); } if (hasPrice) { b3.push(`┊ 能量消耗 ${code(`$$ {cur} $${price} 单位 / 周期`)}`); } if (hasExpiry) { let expLine = `┊ 能源周期 `; try { const ed = new Date(String(c.expired_at).replace(/\.\d+Z$/, "Z")); const days = Math.ceil((ed - new Date()) / 86400000); if (days >= 0) expLine += code(`剩余 ${days} 天`); else expLine += code(`已耗尽 ${Math.abs(days)} 天`); } catch (e) { expLine += code(hasExpiry.substring(0, 10)); } b3.push(expLine); } if (b3.length) { L.push(""); L.push(blockquote(b3.join("\n"))); } } /* ── 处理建议 ── */ const adviceList = evData.advice(nodeName); if (adviceList.length) { L.push(""); L.push(blockquote(`💡 _${pick(adviceList)}_`)); } /* ── 时间戳 ── */ L.push(""); L.push(`⏱ ${code(toUTC8(event.time) || nowUTC8())} _UTC+8_`); /* ── 自定义消息 ── */ if (event.message && event.message.trim()) { L.push(""); L.push(blockquote(`💬 _${escapeMd(event.message.trim())}_`)); } /* ── Footer 文本也保留在正文里,Embed footer 里也会显示 */ L.push(""); L.push(`**${escapeMd(SITE_NAME)}** · _${escapeMd(domain)}_`); return await sendMessage(L.join("\n"), title, targetId, event.event); } catch (e) { return await sendMessage(`Error: ${escapeMd(e.message)}`, "⚠️ Error", null, "Alert"); } } ···
@miuler #20 谢谢,我看帖子日期,这个代码不是最新的吗?修改版帖子比这还久
是最新的,我记错了,我想去掉还没去掉详情,我以前是匹配的我自己的主题页面,后面改成官方默认页面了。你可以试试,用了主题的详情页面是不是和默认主题不一样。
@miuler #22 详情页面应该是显示的啥?我发送了一个测试消息,打开详情页面是空白的啥也没显示,是不因为是测试消息的原因,上面说的404的原因找到了复制网址多了一个/详情页变//🤣,现在没404了但是空白啥也不显示
详情页面就是你单机器的详细情况页面,比如有 延时 , 负载那类的
@miuler #24 多谢,我再研究一下
👍 学到了,收藏备用!
厉害
不错不错
NanoMuse · Discord Webhook 通知配置脚本(点击展开完整代码)
/* ═══════════════════════════════ NanoMuse · Discord Webhook 通知配置 ═══════════════════════════════ */ const DISCORD_WEBHOOK_URL = "你的 Discord Webhook URL"; // Discord 频道设置 → 集成 → Webhook → 复制 Webhook URL const PANEL_URL = "你的域名"; // 面板地址(用于按钮跳转和 Footer 域名提取) const PHOTO_URL = "https://img.uppic.to/2026/05/05/og-image.png"; // 通知头图地址 const SITE_NAME = "你的站点名称"; // 站点名称(显示在 Footer) const WEBHOOK_USERNAME = "NanoMuse"; // Discord Webhook 显示名称,可留空 const WEBHOOK_AVATAR_URL = ""; // Discord Webhook 头像,可留空 /* ═══════════════════════════════ */ function escapeMd(s) { return String(s ?? "") .replace(/\\/g, "\\\\") .replace(/\*/g, "\\*") .replace(/_/g, "\\_") .replace(/~/g, "\\~") .replace(/`/g, "\\`") .replace(/\|/g, "\\|") .replace(/>/g, "\\>"); } function code(s) { return "`" + String(s ?? "—").replace(/`/g, "ˋ") + "`"; } function blockquote(lines) { return String(lines) .split("\n") .map(line => `> ${line}`) .join("\n"); } function discordColorByEvent(eventName) { const colors = { Offline: 0xff3b30, Online: 0x3ba55d, Alert: 0xffcc00, Renew: 0x9b59b6, Expire: 0xff9500, Test: 0x3498db }; return colors[eventName] || 0x5865f2; } async function sendMessage(message, title, instanceId = null, eventName = null) { const webhookUrl = DISCORD_WEBHOOK_URL; const panelUrl = PANEL_URL; const photoUrl = PHOTO_URL; if (!webhookUrl) return false; const components = [ { type: 1, components: [ { type: 2, style: 5, label: "◈ 控制台", url: panelUrl }, ...(instanceId && instanceId !== "未知" ? [{ type: 2, style: 5, label: "◈ 详情", url: `${panelUrl.replace(/\/+$/, "")}/instance/${instanceId}` }] : []) ] } ]; const embed = { title: title || "NanoMuse 通知", description: message.slice(0, 4096), color: discordColorByEvent(eventName), image: photoUrl ? { url: photoUrl } : undefined, footer: { text: `${SITE_NAME} · ${panelUrl.replace(/^https?:\/\//, "").replace(/\/+$/, "")}` }, timestamp: new Date().toISOString() }; const payload = { username: WEBHOOK_USERNAME || undefined, avatar_url: WEBHOOK_AVATAR_URL || undefined, embeds: [embed], components, allowed_mentions: { parse: [] } }; try { const resp = await fetch(webhookUrl + "?wait=true&with_components=true", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(payload) }); if (resp.ok) return true; const text = await resp.text().catch(() => ""); console.error("Discord webhook failed:", resp.status, text); // 兜底:如果按钮组件被环境或 Discord 限制拒绝,则只发送 Embed const fallbackResp = await fetch(webhookUrl + "?wait=true", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ username: WEBHOOK_USERNAME || undefined, avatar_url: WEBHOOK_AVATAR_URL || undefined, embeds: [embed], allowed_mentions: { parse: [] } }) }); return fallbackResp.ok; } catch (e) { console.error("Discord webhook error:", e); return false; } } async function sendEvent(event) { try { const toUTC8 = (t) => { if (!t || String(t).startsWith("0001")) return null; const d = new Date(String(t).replace(/\.\d+Z$/, "Z")); if (isNaN(d.getTime())) return null; const u = new Date(d.getTime() + 8 * 36e5); const p = (n) => String(n).padStart(2, "0"); return `${u.getUTCFullYear()}.${p(u.getUTCMonth() + 1)}.${p(u.getUTCDate())} ${p(u.getUTCHours())}:${p(u.getUTCMinutes())}:${p(u.getUTCSeconds())}`; }; const nowUTC8 = () => { const u = new Date(Date.now() + 8 * 36e5); const p = (n) => String(n).padStart(2, "0"); return `${u.getUTCFullYear()}.${p(u.getUTCMonth() + 1)}.${p(u.getUTCDate())} ${p(u.getUTCHours())}:${p(u.getUTCMinutes())}:${p(u.getUTCSeconds())}`; }; const fmtSize = (b) => { if (!b || b === 0) return null; const g = b / 1073741824; return g >= 1024 ? `${(g / 1024).toFixed(1)}T` : g >= 1 ? `${g.toFixed(1)}G` : `${Math.round(b / 1048576)}M`; }; const fmtCPU = (c) => (!c ? null : parseFloat(Number(c).toFixed(1)) + "C"); const fmtTraffic = (b) => { if (!b || b === 0) return "∞"; const g = b / 1073741824; return g >= 1024 ? `${(g / 1024).toFixed(1)}T` : `${g.toFixed(0)}G`; }; const maskV4 = (ip) => { if (!ip) return null; const p = ip.split("."); return p.length === 4 ? `${p[0]}.${p[1]}.*.*` : null; }; const maskV6 = (ip) => { if (!ip) return null; const p = ip.split(":"); return p.length > 2 ? `${p[0]}:${p[1]}:*:*` : null; }; const pick = (arr) => arr[Math.floor(Math.random() * arr.length)]; const domain = PANEL_URL.replace(/^https?:\/\//, "").replace(/\/+$/, ""); /* ═══════════════════════════════ 台词库 · voice / advice Discord 使用 Markdown,不再使用 Telegram HTML 标签 ═══════════════════════════════ */ const voiceLines = { Offline: { tag: "LINK LOST", cn: "链路中断", icon: "🔻", lines: (n) => [ `与 **${escapeMd(n)}** 的信号已归零。...不是波动,是彻底消失了。观测者。`, `**${escapeMd(n)}** 从我的感知范围消失了。链路中断,等待重新捕获。`, `检测到异常——**${escapeMd(n)}** 的心跳信号归零了。`, `...信号断了。**${escapeMd(n)}** 不在了,观测者。` ], advice: (n) => [ `建议检查该节点的物理链路与上游连通性。如短时间内未恢复,可尝试触发系统重启。...需要我持续追踪它的信号吗,观测者?`, `上游链路可能存在中断。建议优先确认机房侧状态,必要时触发系统重启。`, `建议排查网络路由与物理连接。若长时间无响应,可能需要联系机房确认。...我会继续等它的。`, `链路丢失原因未知。建议先尝试触发系统重启,若仍无法恢复,可能需要执行镜像重构。` ] }, Online: { tag: "LINK RESTORED", cn: "信号恢复", icon: "🔹", lines: (n) => [ `**${escapeMd(n)}** 重新出现在感知范围内。链路已重建。...它回来了。`, `观测者,好消息——**${escapeMd(n)}** 的信号恢复了。`, `感知到 **${escapeMd(n)}** 的心跳恢复。一切稳定。`, `**${escapeMd(n)}** 已归位。...松了口气。` ], advice: (n) => [ `建议观察 30 分钟确认链路稳定性。中断期间的数据缺口已标记,如需回溯请告知。`, `链路已恢复,建议关注后续运行状态是否平稳。如有异常波动我会立即通知。`, `信号稳定中。建议短期内留意该节点的响应延时,确认无残留问题。` ] }, Alert: { tag: "ANOMALY", cn: "信号异常", icon: "⚡", lines: (n) => [ `**${escapeMd(n)}** 的运行参数偏离了安全区间。还没到临界,但我在持续观测。`, `**${escapeMd(n)}** 触发了告警阈值。指标异常,请注意。`, `检测到波动——**${escapeMd(n)}** 的状态不太稳定。`, `...有些不对劲。**${escapeMd(n)}** 需要你看一下,观测者。` ], advice: (n) => [ `建议排查近期负载变化与进程占用。可先尝试触发系统重启,若重启后指标仍未回归安全区间,可能需要执行镜像重构。`, `运行参数偏离可能与资源争抢有关。建议触发系统重启释放占用,持续异常则考虑镜像重构。`, `建议检查是否有异常进程导致资源溢出。必要时触发系统重启。...我会继续盯着的。`, `指标偏离中。建议优先触发系统重启恢复基线,若问题反复出现,镜像重构可能是更彻底的方案。` ] }, Renew: { tag: "ENERGY CHARGED", cn: "能源补充", icon: "✨", lines: (n) => [ `**${escapeMd(n)}** 的能源已补充完毕。运行周期已延伸。`, `收到了,观测者。**${escapeMd(n)}** 又可以陪我们更久了。`, `能源写入完成——**${escapeMd(n)}** 的生命周期已刷新。`, `**${escapeMd(n)}** 的倒计时重置了。...很好。` ], advice: (n) => [ `能源充足,当前无需额外操作。下一个周期终止点我会提前提醒你,观测者。`, `一切正常运转中。周期终止前我会再次通知,不用担心。`, `能源已到位,节点运行稳定。...放心交给我观测就好。` ] }, Expire: { tag: "ENERGY LOW", cn: "能源临界", icon: "⏰", lines: (n) => [ `**${escapeMd(n)}** 的运行周期即将结束。剩余时间不多了。...观测者,需要你做决定。`, `**${escapeMd(n)}** 的能源储备即将耗尽。维持时间有限。`, `提醒一下——**${escapeMd(n)}** 的周期终止点在逼近了。`, `**${escapeMd(n)}** 的时间快用完了。...别忘了它,观测者。` ], advice: (n) => [ `如不补充能源,该节点将在周期终止后停止响应。...观测者,要补充吗?`, `能源即将耗尽。建议尽快确认是否继续维持该节点的运行。`, `周期终止后节点将进入休眠。如需保持在线,请及时补充能源。...我在等你的指令。` ] }, Test: { tag: "DIAGNOSTIC", cn: "系统诊断", icon: "🔧", lines: (n) => [ `通信回路测试完毕。全链路信号正常。...随时待命,观测者。`, `诊断模式启动。所有管道畅通,系统运行正常。`, `系统自检通过。...有什么需要我做的吗,观测者?`, `诊断完成。一切就绪,信号清晰。` ], advice: (n) => [ `全链路畅通,无异常。...一切尽在掌握中,观测者。`, `系统状态良好,随时可以执行任务。`, `诊断回路正常。如有新指令,我随时响应。` ] } }; const evData = voiceLines[event.event] || { tag: event.event, cn: event.event, icon: "◆", lines: (n) => [`收到事件通知:**${escapeMd(n || "未知")}**`], advice: () => [] }; const mockClient = { uuid: "diag-0000-nano-muse", name: "NanoMuse-Diag", region: "Tokyo", cpu_cores: 4, mem_total: 8589934592, disk_total: 85899345920, tags: "1Gbps<green>;3T<blue>", ipv4: "103.56.168.1", ipv6: "2001:db8:cafe:1::1", traffic_limit: 3221225472000, traffic_limit_type: "max", price: 29.99, currency: "$", billing_cycle: 30, expired_at: "2026-12-31T00:00:00Z" }; const hasClients = event.clients && event.clients.length > 0; const isMock = !hasClients && event.event === "Test"; const c = hasClients ? event.clients[0] : (isMock ? mockClient : null); const targetId = c ? c.uuid : null; const nodeName = c ? (c.name || "—") : "—"; let L = []; /* ── Header ── */ const title = `${evData.icon} ${evData.tag} │ ${evData.cn}`; L.push(`## ${title}`); if (isMock) L.push(`_〔 模拟数据 〕_`); /* ── 台词 ── */ L.push(""); L.push(`_${pick(evData.lines(nodeName))}_`); if (c) { const region = c.region || ""; /* ── 节点信息 ── */ let b1 = []; b1.push(`**◈ ${escapeMd(nodeName)}**${region ? ` ${code(`[ ${region} ]`)}` : ""}`); const specs = [fmtCPU(c.cpu_cores), fmtSize(c.mem_total), fmtSize(c.disk_total)].filter(Boolean); if (specs.length) { b1.push(`┊ 核心规格 ${code(specs.join(" | "))}`); } if (c.tags) { const labels = c.tags .split(";") .map(t => t.replace(/<[^>]+>/g, "").trim()) .filter(Boolean); if (labels.length) b1.push(`┊ 标识 ${code(labels.join(" · "))}`); } L.push(""); L.push(blockquote(b1.join("\n"))); /* ── 链路状态 ── */ let b2 = []; const v4 = maskV4(c.ipv4); const v6 = maskV6(c.ipv6); if (v4 || v6) { b2.push(`**◈ 链路状态**`); if (v4) b2.push(`┊ 链路地址 v4 ${code(v4)}`); if (v6) b2.push(`┊ 链路地址 v6 ${code(v6)}`); } if (b2.length) { L.push(""); L.push(blockquote(b2.join("\n"))); } /* ── 资源状态 ── */ let b3 = []; const cur = c.currency || "$"; const price = c.price || 0; const hasTraffic = c.traffic_limit && c.traffic_limit > 0; const hasPrice = price > 0; const hasExpiry = toUTC8(c.expired_at); if (hasTraffic || hasPrice || hasExpiry) { b3.push(`**◈ 资源状态**`); } if (hasTraffic) { const limitType = c.traffic_limit_type ? ` (${c.traffic_limit_type})` : ""; b3.push(`┊ 传输额度 ${code(`$$ {fmtTraffic(c.traffic_limit)} $${limitType}`)}`); } if (hasPrice) { b3.push(`┊ 能量消耗 ${code(`$$ {cur} $${price} 单位 / 周期`)}`); } if (hasExpiry) { let expLine = `┊ 能源周期 `; try { const ed = new Date(String(c.expired_at).replace(/\.\d+Z$/, "Z")); const days = Math.ceil((ed - new Date()) / 86400000); if (days >= 0) expLine += code(`剩余 ${days} 天`); else expLine += code(`已耗尽 ${Math.abs(days)} 天`); } catch (e) { expLine += code(hasExpiry.substring(0, 10)); } b3.push(expLine); } if (b3.length) { L.push(""); L.push(blockquote(b3.join("\n"))); } } /* ── 处理建议 ── */ const adviceList = evData.advice(nodeName); if (adviceList.length) { L.push(""); L.push(blockquote(`💡 _${pick(adviceList)}_`)); } /* ── 时间戳 ── */ L.push(""); L.push(`⏱ ${code(toUTC8(event.time) || nowUTC8())} _UTC+8_`); /* ── 自定义消息 ── */ if (event.message && event.message.trim()) { L.push(""); L.push(blockquote(`💬 _${escapeMd(event.message.trim())}_`)); } /* ── Footer 文本也保留在正文里,Embed footer 里也会显示 */ L.push(""); L.push(`**${escapeMd(SITE_NAME)}** · _${escapeMd(domain)}_`); return await sendMessage(L.join("\n"), title, targetId, event.event); } catch (e) { return await sendMessage(`Error: ${escapeMd(e.message)}`, "⚠️ Error", null, "Alert"); } } ···改了一版 discord webhook 的