logo NodeSeekbeta

Komari TG通知美化模版 纳米缪斯 修改版

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

邮件美化模版链接

image

下面是代码

/* ═══════════════════════════════
   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');
  }
}
1234
1234

你好啊,陌生人!

我的朋友,看起来你是新来的,如果想参与到讨论中,点击下面的按钮!

📈用户数目📈

目前论坛共有72287位seeker

🎉欢迎新用户🎉