今天有朋友给了点建议修改了下,顺便再改改拟人描述啥的,而且提示信息是 几个版本随机
tg信息没办法大搞,就搞搞小花样吧。
使用方法,还是komari 后台 —设置—通知 里面的 通知渠道 改成 JavaScript 。然后下面输入代码就行了,当然需要修改成自己的信息

下面是代码
/* ═══════════════════════════════
NanoMuse · TG 通知配置
═══════════════════════════════ */
const TG_TOKEN = "你的bot token"; // Telegram Bot Token(从 @BotFather 获取)
const TG_CHAT_ID = "你的clent id"; // Chat ID(个人:私聊 @userinfobot 输入 /start | 群组:拉 @RawDataBot 入群获取)
const PANEL_URL = "你的域名"; // 面板地址(用于按钮跳转和 Footer 域名提取)
const PHOTO_URL = "https://img.uppic.to/2026/05/05/og-image.png"; // 通知头图地址 现在默认我的图片,可以修改成自己的图)
const SITE_NAME = "你的站点名称"; // 站点名称(显示在 Footer)
/* ═══════════════════════════════ */
async function sendMessage(message, title, instanceId = null) {
const token = TG_TOKEN;
const chatId = TG_CHAT_ID;
const panelUrl = PANEL_URL;
const photoUrl = PHOTO_URL;
if (!token || !chatId) return false;
let inline_keyboard = [];
let row = [{ text: "◈ 控制台", url: panelUrl }];
if (instanceId && instanceId !== '未知') {
row.push({ text: "◈ 详情", url: `${panelUrl}/instance/${instanceId}` });
}
inline_keyboard.push(row);
try {
const photoResp = await fetch(`https://api.telegram.org/bot${token}/sendPhoto`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
chat_id: chatId,
photo: photoUrl,
caption: message,
parse_mode: 'HTML',
reply_markup: { inline_keyboard }
}),
});
if (photoResp.ok) return true;
} catch (e) {}
const resp = await fetch(`https://api.telegram.org/bot${token}/sendMessage`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
chat_id: chatId,
text: message,
parse_mode: 'HTML',
reply_markup: { inline_keyboard }
}),
});
return resp.ok;
}
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
═══════════════════════════════ */
const voiceLines = {
'Offline': {
tag: 'LINK LOST', cn: '链路中断', icon: '🔻',
lines: (n) => [
`与 <b>${n}</b> 的信号已归零。...不是波动,是彻底消失了。观测者。`,
`<b>${n}</b> 从我的感知范围消失了。链路中断,等待重新捕获。`,
`检测到异常——<b>${n}</b> 的心跳信号归零了。`,
`...信号断了。<b>${n}</b> 不在了,观测者。`,
],
advice: (n) => [
`建议检查该节点的物理链路与上游连通性。如短时间内未恢复,可尝试触发系统重启。...需要我持续追踪它的信号吗,观测者?`,
`上游链路可能存在中断。建议优先确认机房侧状态,必要时触发系统重启。`,
`建议排查网络路由与物理连接。若长时间无响应,可能需要联系机房确认。...我会继续等它的。`,
`链路丢失原因未知。建议先尝试触发系统重启,若仍无法恢复,可能需要执行镜像重构。`,
]
},
'Online': {
tag: 'LINK RESTORED', cn: '信号恢复', icon: '🔹',
lines: (n) => [
`<b>${n}</b> 重新出现在感知范围内。链路已重建。...它回来了。`,
`观测者,好消息——<b>${n}</b> 的信号恢复了。`,
`感知到 <b>${n}</b> 的心跳恢复。一切稳定。`,
`<b>${n}</b> 已归位。...松了口气。`,
],
advice: (n) => [
`建议观察 30 分钟确认链路稳定性。中断期间的数据缺口已标记,如需回溯请告知。`,
`链路已恢复,建议关注后续运行状态是否平稳。如有异常波动我会立即通知。`,
`信号稳定中。建议短期内留意该节点的响应延时,确认无残留问题。`,
]
},
'Alert': {
tag: 'ANOMALY', cn: '信号异常', icon: '⚡',
lines: (n) => [
`<b>${n}</b> 的运行参数偏离了安全区间。还没到临界,但我在持续观测。`,
`<b>${n}</b> 触发了告警阈值。指标异常,请注意。`,
`检测到波动——<b>${n}</b> 的状态不太稳定。`,
`...有些不对劲。<b>${n}</b> 需要你看一下,观测者。`,
],
advice: (n) => [
`建议排查近期负载变化与进程占用。可先尝试触发系统重启,若重启后指标仍未回归安全区间,可能需要执行镜像重构。`,
`运行参数偏离可能与资源争抢有关。建议触发系统重启释放占用,持续异常则考虑镜像重构。`,
`建议检查是否有异常进程导致资源溢出。必要时触发系统重启。...我会继续盯着的。`,
`指标偏离中。建议优先触发系统重启恢复基线,若问题反复出现,镜像重构可能是更彻底的方案。`,
]
},
'Renew': {
tag: 'ENERGY CHARGED', cn: '能源补充', icon: '✨',
lines: (n) => [
`<b>${n}</b> 的能源已补充完毕。运行周期已延伸。`,
`收到了,观测者。<b>${n}</b> 又可以陪我们更久了。`,
`能源写入完成——<b>${n}</b> 的生命周期已刷新。`,
`<b>${n}</b> 的倒计时重置了。...很好。`,
],
advice: (n) => [
`能源充足,当前无需额外操作。下一个周期终止点我会提前提醒你,观测者。`,
`一切正常运转中。周期终止前我会再次通知,不用担心。`,
`能源已到位,节点运行稳定。...放心交给我观测就好。`,
]
},
'Expire': {
tag: 'ENERGY LOW', cn: '能源临界', icon: '⏰',
lines: (n) => [
`<b>${n}</b> 的运行周期即将结束。剩余时间不多了。...观测者,需要你做决定。`,
`<b>${n}</b> 的能源储备即将耗尽。维持时间有限。`,
`提醒一下——<b>${n}</b> 的周期终止点在逼近了。`,
`<b>${n}</b> 的时间快用完了。...别忘了它,观测者。`,
],
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) => [`收到事件通知:<b>${n || '未知'}</b>`],
advice: (n) => []
};
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 ── */
L.push(`${evData.icon} <b>${evData.tag}</b> │ ${evData.cn}`);
if (isMock) L.push(`<i>〔 模拟数据 〕</i>`);
/* ── 台词 ── */
L.push('');
L.push(`<i>${pick(evData.lines(nodeName))}</i>`);
if (c) {
const region = c.region || '';
/* ── 节点信息 ── */
let b1 = [];
b1.push(`<b>◈ ${nodeName}</b>` + (region ? ` <code>[ ${region} ]</code>` : ''));
const specs = [fmtCPU(c.cpu_cores), fmtSize(c.mem_total), fmtSize(c.disk_total)].filter(Boolean);
if (specs.length) {
b1.push(`┊ 核心规格 <code>${specs.join(' | ')}</code>`);
}
if (c.tags) {
const labels = c.tags.split(';').map(t => t.replace(/<[^>]+>/g, '').trim()).filter(Boolean);
if (labels.length) b1.push(`┊ 标识 <code>${labels.join(' · ')}</code>`);
}
L.push('');
L.push(`<blockquote>${b1.join('\n')}</blockquote>`);
/* ── 链路状态 ── */
let b2 = [];
const v4 = maskV4(c.ipv4);
const v6 = maskV6(c.ipv6);
if (v4 || v6) {
b2.push(`<b>◈ 链路状态</b>`);
if (v4) b2.push(`┊ 链路地址 v4 <code>${v4}</code>`);
if (v6) b2.push(`┊ 链路地址 v6 <code>${v6}</code>`);
}
if (b2.length) L.push(`<blockquote>${b2.join('\n')}</blockquote>`);
/* ── 资源状态 ── */
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(`<b>◈ 资源状态</b>`);
}
if (hasTraffic) {
const limitType = c.traffic_limit_type ? ` (${c.traffic_limit_type})` : '';
b3.push(`┊ 传输额度 <code>${fmtTraffic(c.traffic_limit)}${limitType}</code>`);
}
if (hasPrice) {
b3.push(`┊ 能量消耗 <code>${cur}${price} 单位 / 周期</code>`);
}
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} 天</code>`;
else expLine += `<code>已耗尽 ${Math.abs(days)} 天</code>`;
} catch(e) {
expLine += `<code>${hasExpiry.substring(0, 10)}</code>`;
}
b3.push(expLine);
}
if (b3.length) L.push(`<blockquote>${b3.join('\n')}</blockquote>`);
}
/* ── 处理建议 ── */
const adviceList = evData.advice(nodeName);
if (adviceList.length) {
L.push('');
L.push(`<blockquote>💡 <i>${pick(adviceList)}</i></blockquote>`);
}
/* ── 时间戳 ── */
L.push('');
L.push(`⏱ <code>${toUTC8(event.time) || nowUTC8()}</code> <i>UTC+8</i>`);
/* ── 自定义消息 ── */
if (event.message && event.message.trim()) {
L.push('');
L.push(`<blockquote>💬 <i>${event.message.trim()}</i></blockquote>`);
}
/* ── Footer ── */
L.push('');
L.push(`<b>${SITE_NAME}</b> · <i>${domain}</i>`);
return await sendMessage(L.join('\n'), '', targetId);
} catch (e) {
return await sendMessage(`Error: ${e.message}`, '⚠️ Error');
}
}
牛逼
牛逼!
先收藏了
不错啊
不错,辛苦了,给鸡腿!
美,必须顶一下
好用
牛逼