想看大家的komari通知模版。

mubdao 2026-06-21 10:43 1

想看大家的komari通知模版,好看的主题贴不少。


可以看看你们的通知模版吗?

最新回复 (33)
  • LIN-2077 06-21 10:44
    1

    未启用

  • mubdao 楼主 06-21 10:44
    2

    @LIN-2077 #1 发布于2026/6/21 10:44:01

    未启用

    好奔放🤣


  • PayPal 06-21 10:46
    3

    {{emoji}}

    事件: {{event}}

    服务: {{client}}

    消息: {{message}}

    时间: {{time}}

  • smagic 06-21 10:47
    4

  • solaireh3 06-21 10:49
    5

    @smagic #4 求个模版 ^-^

  • lalala123 06-21 10:50
    6

    默认就好

  • smagic 06-21 10:51
    7

    @solaireh3 #5


    // 提取公共常量到顶层,避免作用域找不到
    const TG_TOKEN = "获取到的Telegram Bot Token";
    const TG_CHAT_ID = "你的Telegram Chat ID"; // 可以是个人或群组的ID
    const MONITOR_URL = '你的地址'; // 监控系统主页链接

    // 公共工具函数提取到顶层,所有async函数都能访问
    /**
    * 统一转换时间为 UTC+8 格式化字符串
    * @param {string|Date|null} timeStr 原始时间
    * @returns {string} 格式化后 UTC+8 时间文本
    */
    function ensureUTC8Time(timeStr) {
    if (!timeStr) {
    // 无时间时使用当前UTC+8时间
    const now = new Date();
    // 本地时间 + 时区偏移抹平 +8小时
    const utc8Time = new Date(now.getTime() + (8 * 60 * 60 * 1000) - (now.getTimezoneOffset() * 60 * 1000));
    return utc8Time.toISOString().replace('Z', '+08:00');
    }

    // 解析时间并转换为UTC+8
    const date = new Date(timeStr);
    // 检查是否为有效日期
    if (isNaN(date.getTime())) {
    console.warn(`无效的时间格式: ${timeStr},使用当前UTC+8时间`);
    const now = new Date();
    const utc8Time = new Date(now.getTime() + (8 * 60 * 60 * 1000) - (now.getTimezoneOffset() * 60 * 1000));
    return utc8Time.toISOString().replace('Z', '+08:00');
    }

    // 转换为UTC+8时间字符串
    const utcTimestamp = date.getTime() - (date.getTimezoneOffset() * 60 * 1000);
    const utc8Timestamp = utcTimestamp + (8 * 60 * 60 * 1000);
    const utc8Date = new Date(utc8Timestamp);

    // 格式化输出
    const year = utc8Date.getUTCFullYear();
    const month = String(utc8Date.getUTCMonth() + 1).padStart(2, '0');
    const day = String(utc8Date.getUTCDate()).padStart(2, '0');
    const hours = String(utc8Date.getUTCHours()).padStart(2, '0');
    const minutes = String(utc8Date.getUTCMinutes()).padStart(2, '0');
    const seconds = String(utc8Date.getUTCSeconds()).padStart(2, '0');

    return `${year}-${month}-${day} ${hours}:${minutes}:${seconds} (UTC+8)`;
    }

    /**
    * 格式化字节大小
    * @param {number} bytes
    * @returns {string}
    */
    function formatBytes(bytes) {
    if (bytes === 0) return '0 B';
    const k = 1024;
    const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
    const i = Math.floor(Math.log(bytes) / Math.log(k));
    return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
    }

    /**
    * 格式化到期时间并计算剩余天数
    * @param {string} expireTimeStr
    * @returns {string}
    */
    function formatExpireTime(expireTimeStr) {
    if (!expireTimeStr) return '永久有效';

    const expireDate = new Date(expireTimeStr);
    if (isNaN(expireDate.getTime())) return '时间格式错误';

    const now = new Date();
    const nowUTC8 = new Date(now.getTime() + (8 * 60 * 60 * 1000) - (now.getTimezoneOffset() * 60 * 1000));
    const expireUTC8 = new Date(expireDate.getTime() + (8 * 60 * 60 * 1000) - (expireDate.getTimezoneOffset() * 60 * 1000));

    const diffDays = Math.ceil((expireUTC8 - nowUTC8) / (1000 * 60 * 60 * 24));

    const y = expireUTC8.getUTCFullYear();
    const m = String(expireUTC8.getUTCMonth() + 1).padStart(2, '0');
    const d = String(expireUTC8.getUTCDate()).padStart(2, '0');
    const dateStr = `${y}-${m}-${d}`;

    if (diffDays < 0) return `已过期 (${dateStr})`;
    if (diffDays === 0) return `今日到期 (${dateStr})`;
    if (diffDays <= 7) return `即将到期(${diffDays}天后) ${dateStr}`;
    return `${dateStr} (剩余${diffDays}天)`;
    }

    /**
    * 获取事件类型图标描述
    * @param {string} eventType
    * @returns {string}
    */
    function getEventTypeDesc(eventType) {
    const eventMap = {
    'Offline': '❌ 服务器离线',
    'Online': '✅ 服务器上线',
    'Alert': '⚠️ 监控告警',
    'Renew': '⏰ 服务器已自动续费',
    'Expire': '🚨 服务到期提醒',
    'Test': '🧪 测试通知',
    'offline': '❌ 服务器离线'
    };
    return eventMap[eventType] || `📊 ${eventType}`;
    }

    /**
    * 生成单台服务器信息卡片文本
    * @param {object} client
    * @param {number} index
    * @returns {string}
    */
    function generateClientCard(client, index) {
    let card = `\n┌── 🖥️ ${index}. <b>${client.name || '未知服务器'}</b>`;
    card += `\n│ 📍 地域: ${client.region || '未设置'}`;
    card += `\n│ 🖼️ 虚拟化: ${client.virtualization || '未知'} | 架构: ${client.arch || '未知'}`;
    card += `\n│ 🧮 CPU: ${client.cpu_name || '未知'} (${client.cpu_cores || 0}核)`;
    card += `\n│ 📦 配置: 内存 ${formatBytes(client.mem_total || 0)} | 磁盘 ${formatBytes(client.disk_total || 0)}`;

    let ips = [];
    if (client.ipv4) ips.push(`IPv4: ${client.ipv4}`);
    if (client.ipv6) ips.push(`IPv6: ${client.ipv6}`);
    card += `\n│ 🌐 ${ips.length > 0 ? ips.join(' | ') : '无公网IP'}`;

    card += `\n│ 📋 系统: ${client.os || '未知'} (内核: ${client.kernel_version || '未知'})`;

    if (client.price) {
    const renewal = client.auto_renewal ? '✅ 自动续费' : '❌ 手动续费';
    if (client.price == -1) {
    card += `\n│ 💰 价格: 免费 | 周期: ${client.billing_cycle || '月'}`;
    } else {
    card += `\n│ 💰 价格: ${client.currency || '$'}${client.price} | 周期: ${client.billing_cycle || '月'}`;
    }
    card += `\n│ 📅 到期: ${formatExpireTime(client.expired_at)} | ${renewal}`;
    }

    if (client.tags) card += `\n│ 🏷️ 标签: ${client.tags.replace(';', ' | ')}`;
    if (client.remark) card += `\n│ 📝 备注: ${client.remark.substring(0, 50)}${client.remark.length > 50 ? '...' : ''}`;

    card += `\n└── 🆔 UUID: ${client.uuid || '未设置'}`;
    return card;
    }

    async function sendMessage(message, title, buttons = []) {
    const url = `https://api.telegram.org/bot${TG_TOKEN}/sendMessage`;

    const requestBody = {
    chat_id: TG_CHAT_ID,
    text: `<b>${title}</b>\n\n${message}`,
    parse_mode: 'HTML',
    disable_web_page_preview: true
    };

    if (buttons.length > 0) {
    requestBody.reply_markup = {
    inline_keyboard: buttons,
    resize_keyboard: true,
    one_time_keyboard: false
    };
    }

    const resp = await fetch(url, {
    method: 'POST',
    headers: {
    'Content-Type': 'application/json',
    },
    body: JSON.stringify(requestBody),
    });

    if (!resp.ok) {
    console.error('Failed to send message:', resp.status, resp.statusText);
    return false;
    }
    return true;
    }

    async function sendEvent(event) {
    try {
    const title = `${getEventTypeDesc(event.event) || 'ℹ️'} Komari 通知`;
    let message = '';

    message += `📅 <b>事件时间 (UTC+8)</b>: ${ensureUTC8Time(event.time)}\n`;
    message += `📢 <b>事件类型</b>: ${event.event || '未知'}\n`;
    if (event.emoji) message += `🔤 <b>标识</b>: ${event.emoji}\n`;
    if (event.message && event.message.trim()) {
    message += `📝 <b>事件说明</b>: ${event.message}\n`;
    }
    message += '\n' + '━'.repeat(20) + '\n';

    const buttons = [];
    buttons.push([{
    text: '🔍 监控主页',
    url: `${MONITOR_URL}/`
    }]);

    if (event.clients && event.clients.length > 0) {
    const totalClients = event.clients.length;
    message += `🖥️ <b>受影响服务器</b>: ${totalClients} 台\n\n`;

    const displayCount = Math.min(totalClients, 3);
    const clientButtons = [];

    for (let i = 0; i < displayCount; i++) {
    const client = event.clients[i];
    message += generateClientCard(client, i + 1);

    if (client.uuid) {
    clientButtons.push({
    text: `${i + 1}. ${client.name || '服务器详情'}`,
    url: `${MONITOR_URL}/instance/${client.uuid}`
    });
    }
    }

    if (clientButtons.length > 0) {
    buttons.push(clientButtons);
    }

    if (totalClients > 3) {
    message += `\n\n📌 仅展示前3台,剩余 ${totalClients - 3} 台请查看监控系统`;
    buttons.push([{
    text: `📑 查看全部${totalClients}台机器`,
    url: `${MONITOR_URL}/`
    }]);
    }
    } else {
    message += '🖥️ <b>受影响服务器</b>: 无关联服务器信息\n';
    }

    const success = await sendMessage(message, title, buttons);
    if (success) {
    console.log(`事件通知已发送: ${event.event}`);
    } else {
    console.error(`事件通知发送失败: ${event.event}`);
    }
    return success;

    } catch (error) {
    console.error('发送事件通知时出错:', error);

    const fallbackMessage = `${event.emoji || ''} <b>${event.event || '未知事件'}</b>
    📅 时间 (UTC+8): ${ensureUTC8Time(event.time || new Date())}
    📝 说明: ${event.message || '无详细信息'}
    🖥️ 受影响服务器: ${event.clients?.length || 0} 台`;

    const fallbackTitle = '⚠️ Komari 通知 - 异常';
    const fallbackButtons = [[{
    text: '🔍 监控主页',
    url: `${MONITOR_URL}/`
    }]];

    try {
    return await sendMessage(fallbackMessage, fallbackTitle, fallbackButtons);
    } catch (fallbackError) {
    console.error('备用通知也失败:', fallbackError);
    return false;
    }
    }
    }
  • Amity 06-21 10:52
    8

    来早了,一会儿再来看评论

  • solaireh3 06-21 10:52
    9

    @smagic #7 ^-^ ^-^

  • mubdao 楼主 06-21 10:53
    10

    @smagic #7 发布于2026/6/21 10:51:37

    @solaireh3 #5


    async function sendMessage(message, title, buttons = []) {
    const token = "你的bot token"
    const chatId = "你的chat_id"

    const url = `https://api.telegram.org/bot${token}/sendMessage`;

    // 构建请求体
    const requestBody = {
    chat_id: chatId,
    text: `<b>${title}</b>\n\n${message}`,
    parse_mode: 'HTML',
    disable_web_page_preview: true // 禁用链接预览,让消息更整洁
    };

    // 如果有按钮,添加键盘配置
    if (buttons.length > 0) {
    requestBody.reply_markup = {
    inline_keyboard: buttons,
    resize_keyboard: true,
    one_time_keyboard: false
    };
    }

    const resp = await fetch(url, {
    method: 'POST',
    headers: {
    'Content-Type': 'application/json',
    },
    body: JSON.stringify(requestBody),
    });

    if (!resp.ok) {
    console.error('Failed to send message:', resp.status, resp.statusText);
    return false;
    }
    return true;
    }

    async function sendEvent(event) {
    try {
    // 时区检查和处理函数 - 确保时间始终为UTC+8
    const ensureUTC8Time = (timeStr) => {
    if (!timeStr) {
    // 无时间时使用当前UTC+8时间
    const now = new Date();
    const utc8Time = new Date(now.getTime() + (8 * 60 * 60 * 1000) - (now.getTimezoneOffset() * 60 * 1000));
    return utc8Time.toISOString().replace('Z', '+08:00');
    }

    // 解析时间并转换为UTC+8
    const date = new Date(timeStr);
    // 检查是否为有效日期
    if (isNaN(date.getTime())) {
    console.warn(`无效的时间格式: ${timeStr},使用当前UTC+8时间`);
    const now = new Date();
    const utc8Time = new Date(now.getTime() + (8 * 60 * 60 * 1000) - (now.getTimezoneOffset() * 60 * 1000));
    return utc8Time.toISOString().replace('Z', '+08:00');
    }

    // 转换为UTC+8时间字符串(保留原始格式,仅确保时区正确)
    // 获取UTC时间戳,加上8小时偏移
    const utcTimestamp = date.getTime() - (date.getTimezoneOffset() * 60 * 1000);
    const utc8Timestamp = utcTimestamp + (8 * 60 * 60 * 1000);
    const utc8Date = new Date(utc8Timestamp);

    // 格式化输出为 ISO 格式,明确标注UTC+8
    const year = utc8Date.getUTCFullYear();
    const month = String(utc8Date.getUTCMonth() + 1).padStart(2, '0');
    const day = String(utc8Date.getUTCDate()).padStart(2, '0');
    const hours = String(utc8Date.getUTCHours()).padStart(2, '0');
    const minutes = String(utc8Date.getUTCMinutes()).padStart(2, '0');
    const seconds = String(utc8Date.getUTCSeconds()).padStart(2, '0');

    return `${year}-${month}-${day} ${hours}:${minutes}:${seconds} (UTC+8)`;
    };

    // 格式化文件大小
    const formatBytes = (bytes) => {
    if (bytes === 0) return '0 B';
    const k = 1024;
    const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
    const i = Math.floor(Math.log(bytes) / Math.log(k));
    return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
    };

    // 格式化到期时间(确保使用UTC+8)
    const formatExpireTime = (expireTimeStr) => {
    if (!expireTimeStr) return '永久有效';

    // 确保到期时间也使用UTC+8
    const expireDate = new Date(expireTimeStr);
    if (isNaN(expireDate.getTime())) return '时间格式错误';

    const now = new Date();
    const nowUTC8 = new Date(now.getTime() + (8 * 60 * 60 * 1000) - (now.getTimezoneOffset() * 60 * 1000));
    const expireUTC8 = new Date(expireDate.getTime() + (8 * 60 * 60 * 1000) - (expireDate.getTimezoneOffset() * 60 * 1000));

    const diffDays = Math.ceil((expireUTC8 - nowUTC8) / (1000 * 60 * 60 * 24));

    if (diffDays < 0) return `已过期 (${expireUTC8.getUTCFullYear()}-${String(expireUTC8.getUTCMonth()+1).padStart(2,'0')}-${String(expireUTC8.getUTCDate()).padStart(2,'0')})`;
    if (diffDays === 0) return `今日到期 (${expireUTC8.getUTCFullYear()}-${String(expireUTC8.getUTCMonth()+1).padStart(2,'0')}-${String(expireUTC8.getUTCDate()).padStart(2,'0')})`;
    if (diffDays <= 7) return `即将到期(${diffDays}天后) ${expireUTC8.getUTCFullYear()}-${String(expireUTC8.getUTCMonth()+1).padStart(2,'0')}-${String(expireUTC8.getUTCDate()).padStart(2,'0')}`;
    return `${expireUTC8.getUTCFullYear()}-${String(expireUTC8.getUTCMonth()+1).padStart(2,'0')}-${String(expireUTC8.getUTCDate()).padStart(2,'0')} (剩余${diffDays}天)`;
    };

    // 获取事件类型描述
    const getEventTypeDesc = (eventType) => {
    const eventMap = {
    'Offline': '❌ 服务器离线',
    'Online': '✅ 服务器上线',
    'Alert': '⚠️ 监控告警',
    'Renew': '⏰ 服务器已自动续费',
    'Expire': '🚨 服务到期提醒',
    'Test': '🧪 测试通知',
    'offline': '❌ 服务器离线' // 兼容小写的offline
    };
    return eventMap[eventType] || `📊 ${eventType}`;
    };

    // 生成详细的服务器信息卡片
    const generateClientCard = (client, index) => {
    let card = `\n┌── 🖥️ ${index}. <b>${client.name || '未知服务器'}</b>`;
    card += `\n│ 📍 地域: ${client.region || '未设置'}`;
    card += `\n│ 🖼️ 虚拟化: ${client.virtualization || '未知'} | 架构: ${client.arch || '未知'}`;
    card += `\n│ 🧮 CPU: ${client.cpu_name || '未知'} (${client.cpu_cores || 0}核)`;
    card += `\n│ 📦 配置: 内存 ${formatBytes(client.mem_total || 0)} | 磁盘 ${formatBytes(client.disk_total || 0)}`;

    // IP信息
    let ips = [];
    if (client.ipv4) ips.push(`IPv4: ${client.ipv4}`);
    if (client.ipv6) ips.push(`IPv6: ${client.ipv6}`);
    card += `\n│ 🌐 ${ips.length > 0 ? ips.join(' | ') : '无公网IP'}`;

    // 系统信息
    card += `\n│ 📋 系统: ${client.os || '未知'} (内核: ${client.kernel_version || '未知'})`;

    // 计费信息
    if (client.price) {
    const renewal = client.auto_renewal ? '✅ 自动续费' : '❌ 手动续费';
    if(client.price == -1){
    card += `\n│ 💰 价格: 免费 | 周期: ${client.billing_cycle || '月'}`;
    }else{
    card += `\n│ 💰 价格: ${client.currency || '$'}${client.price} | 周期: ${client.billing_cycle || '月'}`;
    }
    card += `\n│ 📅 到期: ${formatExpireTime(client.expired_at)} | ${renewal}`;
    }

    // 标签和备注
    if (client.tags) card += `\n│ 🏷️ 标签: ${client.tags.replace(';', ' | ')}`;
    if (client.remark) card += `\n│ 📝 备注: ${client.remark.substring(0, 50)}${client.remark.length > 50 ? '...' : ''}`;

    card += `\n└── 🆔 UUID: ${client.uuid || '未设置'}`;

    return card;
    };

    const title = `${getEventTypeDesc(event.event) || 'ℹ️'} Komari 通知`;
    let message = '';

    // 头部信息 - 使用UTC+8时间
    message += `📅 <b>事件时间 (UTC+8)</b>: ${ensureUTC8Time(event.time)}\n`;
    message += `📢 <b>事件类型</b>: ${event.event || '未知'}\n`;
    if (event.emoji) message += `🔤 <b>标识</b>: ${event.emoji}\n`;
    if (event.message && event.message.trim()) {
    message += `📝 <b>事件说明</b>: ${event.message}\n`;
    }
    message += '\n' + '━'.repeat(20) + '\n';

    // 构建按钮数组
    const buttons = [];
    // 始终添加监控主页按钮(第一行)
    buttons.push([{
    text: '🔍 监控主页',
    url: 'https://monitor.smagical.de/'
    }]);

    if (event.clients && event.clients.length > 0) {
    const totalClients = event.clients.length;
    message += `🖥️ <b>受影响服务器</b>: ${totalClients} 台\n\n`;

    // 只展示前三台机器
    const displayCount = Math.min(totalClients, 3);
    const clientButtons = [];

    for (let i = 0; i < displayCount; i++) {
    const client = event.clients[i];
    message += generateClientCard(client, i + 1);

    // 为每台机器添加详情按钮
    if (client.uuid) {
    clientButtons.push({
    text: `${i + 1}. ${client.name || '服务器详情'}`,
    url: `https://monitor.smagical.de/instance/${client.uuid}`
    });
    }
    }

    // 添加机器详情按钮行
    if (clientButtons.length > 0) {
    buttons.push(clientButtons);
    }

    // 超过3台时添加查看更多按钮
    if (totalClients > 3) {
    message += `\n\n📌 仅展示前3台,剩余 ${totalClients - 3} 台请查看监控系统`;
    buttons.push([{
    text: `📑 查看全部${totalClients}台机器`,
    url: 'https://monitor.smagical.de/'
    }]);
    }
    } else {
    message += '🖥️ <b>受影响服务器</b>: 无关联服务器信息\n';
    }

    // 发送通知(传入按钮配置)
    const success = await sendMessage(message, title, buttons);
    if (success) {
    console.log(`事件通知已发送: ${event.event}`);
    } else {
    console.error(`事件通知发送失败: ${event.event}`);
    }
    return success;

    } catch (error) {
    console.error('发送事件通知时出错:', error);

    // 发送简化的错误通知(也包含基础监控按钮)
    const fallbackMessage = `${event.emoji || ''} <b>${event.event || '未知事件'}</b>
    📅 时间 (UTC+8): ${ensureUTC8Time(event.time || new Date())}
    📝 说明: ${event.message || '无详细信息'}
    🖥️ 受影响服务器: ${event.clients?.length || 0} 台`;

    const fallbackTitle = '⚠️ Komari 通知 - 异常';
    const fallbackButtons = [[{
    text: '🔍 监控主页',
    url: 'https://monitor.smagical.de/'
    }]];

    try {
    return await sendMessage(fallbackMessage, fallbackTitle, fallbackButtons);
    } catch (fallbackError) {
    console.error('备用通知也失败:', fallbackError);
    return false;
    }
    }
    }

    谢谢老板

  • mjj终结者 06-21 10:55
    11

    模板通知需要清爽, 一目了然


    <a href="https://komari.example.com">{{emoji}}{{event}}</a>
    💻 服务器:{{client}}
    💬 消息:{{message}}
    🕘 {{time}}

  • mubdao 楼主 06-21 10:56
    12

    @lalala123 #6 发布于2026/6/21 10:50:04

    默认就好


    好看

  • rainbow 06-21 11:01
    13

    @smagic #7 发布于2026/6/21 10:51:37,编辑于2026/6/21 11:00:17

    @solaireh3 #5


    async function sendMessage(message, title, buttons = []) {
    const token = "你的bot token"
    const chatId = "你的chat_id"
    const monitorUrl = 'https://monitor.smagical.de'; // 监控系统主页链接

    const url = `https://api.telegram.org/bot${token}/sendMessage`;

    // 构建请求体
    const requestBody = {
    chat_id: chatId,
    text: `<b>${title}</b>\n\n${message}`,
    parse_mode: 'HTML',
    disable_web_page_preview: true // 禁用链接预览,让消息更整洁
    };

    // 如果有按钮,添加键盘配置
    if (buttons.length > 0) {
    requestBody.reply_markup = {
    inline_keyboard: buttons,
    resize_keyboard: true,
    one_time_keyboard: false
    };
    }

    const resp = await fetch(url, {
    method: 'POST',
    headers: {
    'Content-Type': 'application/json',
    },
    body: JSON.stringify(requestBody),
    });

    if (!resp.ok) {
    console.error('Failed to send message:', resp.status, resp.statusText);
    return false;
    }
    return true;
    }

    async function sendEvent(event) {
    try {
    // 时区检查和处理函数 - 确保时间始终为UTC+8
    const ensureUTC8Time = (timeStr) => {
    if (!timeStr) {
    // 无时间时使用当前UTC+8时间
    const now = new Date();
    const utc8Time = new Date(now.getTime() + (8 * 60 * 60 * 1000) - (now.getTimezoneOffset() * 60 * 1000));
    return utc8Time.toISOString().replace('Z', '+08:00');
    }

    // 解析时间并转换为UTC+8
    const date = new Date(timeStr);
    // 检查是否为有效日期
    if (isNaN(date.getTime())) {
    console.warn(`无效的时间格式: ${timeStr},使用当前UTC+8时间`);
    const now = new Date();
    const utc8Time = new Date(now.getTime() + (8 * 60 * 60 * 1000) - (now.getTimezoneOffset() * 60 * 1000));
    return utc8Time.toISOString().replace('Z', '+08:00');
    }

    // 转换为UTC+8时间字符串(保留原始格式,仅确保时区正确)
    // 获取UTC时间戳,加上8小时偏移
    const utcTimestamp = date.getTime() - (date.getTimezoneOffset() * 60 * 1000);
    const utc8Timestamp = utcTimestamp + (8 * 60 * 60 * 1000);
    const utc8Date = new Date(utc8Timestamp);

    // 格式化输出为 ISO 格式,明确标注UTC+8
    const year = utc8Date.getUTCFullYear();
    const month = String(utc8Date.getUTCMonth() + 1).padStart(2, '0');
    const day = String(utc8Date.getUTCDate()).padStart(2, '0');
    const hours = String(utc8Date.getUTCHours()).padStart(2, '0');
    const minutes = String(utc8Date.getUTCMinutes()).padStart(2, '0');
    const seconds = String(utc8Date.getUTCSeconds()).padStart(2, '0');

    return `${year}-${month}-${day} ${hours}:${minutes}:${seconds} (UTC+8)`;
    };

    // 格式化文件大小
    const formatBytes = (bytes) => {
    if (bytes === 0) return '0 B';
    const k = 1024;
    const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
    const i = Math.floor(Math.log(bytes) / Math.log(k));
    return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
    };

    // 格式化到期时间(确保使用UTC+8)
    const formatExpireTime = (expireTimeStr) => {
    if (!expireTimeStr) return '永久有效';

    // 确保到期时间也使用UTC+8
    const expireDate = new Date(expireTimeStr);
    if (isNaN(expireDate.getTime())) return '时间格式错误';

    const now = new Date();
    const nowUTC8 = new Date(now.getTime() + (8 * 60 * 60 * 1000) - (now.getTimezoneOffset() * 60 * 1000));
    const expireUTC8 = new Date(expireDate.getTime() + (8 * 60 * 60 * 1000) - (expireDate.getTimezoneOffset() * 60 * 1000));

    const diffDays = Math.ceil((expireUTC8 - nowUTC8) / (1000 * 60 * 60 * 24));

    if (diffDays < 0) return `已过期 (${expireUTC8.getUTCFullYear()}-${String(expireUTC8.getUTCMonth()+1).padStart(2,'0')}-${String(expireUTC8.getUTCDate()).padStart(2,'0')})`;
    if (diffDays === 0) return `今日到期 (${expireUTC8.getUTCFullYear()}-${String(expireUTC8.getUTCMonth()+1).padStart(2,'0')}-${String(expireUTC8.getUTCDate()).padStart(2,'0')})`;
    if (diffDays <= 7) return `即将到期(${diffDays}天后) ${expireUTC8.getUTCFullYear()}-${String(expireUTC8.getUTCMonth()+1).padStart(2,'0')}-${String(expireUTC8.getUTCDate()).padStart(2,'0')}`;
    return `${expireUTC8.getUTCFullYear()}-${String(expireUTC8.getUTCMonth()+1).padStart(2,'0')}-${String(expireUTC8.getUTCDate()).padStart(2,'0')} (剩余${diffDays}天)`;
    };

    // 获取事件类型描述
    const getEventTypeDesc = (eventType) => {
    const eventMap = {
    'Offline': '❌ 服务器离线',
    'Online': '✅ 服务器上线',
    'Alert': '⚠️ 监控告警',
    'Renew': '⏰ 服务器已自动续费',
    'Expire': '🚨 服务到期提醒',
    'Test': '🧪 测试通知',
    'offline': '❌ 服务器离线' // 兼容小写的offline
    };
    return eventMap[eventType] || `📊 ${eventType}`;
    };

    // 生成详细的服务器信息卡片
    const generateClientCard = (client, index) => {
    let card = `\n┌── 🖥️ ${index}. <b>${client.name || '未知服务器'}</b>`;
    card += `\n│ 📍 地域: ${client.region || '未设置'}`;
    card += `\n│ 🖼️ 虚拟化: ${client.virtualization || '未知'} | 架构: ${client.arch || '未知'}`;
    card += `\n│ 🧮 CPU: ${client.cpu_name || '未知'} (${client.cpu_cores || 0}核)`;
    card += `\n│ 📦 配置: 内存 ${formatBytes(client.mem_total || 0)} | 磁盘 ${formatBytes(client.disk_total || 0)}`;

    // IP信息
    let ips = [];
    if (client.ipv4) ips.push(`IPv4: ${client.ipv4}`);
    if (client.ipv6) ips.push(`IPv6: ${client.ipv6}`);
    card += `\n│ 🌐 ${ips.length > 0 ? ips.join(' | ') : '无公网IP'}`;

    // 系统信息
    card += `\n│ 📋 系统: ${client.os || '未知'} (内核: ${client.kernel_version || '未知'})`;

    // 计费信息
    if (client.price) {
    const renewal = client.auto_renewal ? '✅ 自动续费' : '❌ 手动续费';
    if(client.price == -1){
    card += `\n│ 💰 价格: 免费 | 周期: ${client.billing_cycle || '月'}`;
    }else{
    card += `\n│ 💰 价格: ${client.currency || '$'}${client.price} | 周期: ${client.billing_cycle || '月'}`;
    }
    card += `\n│ 📅 到期: ${formatExpireTime(client.expired_at)} | ${renewal}`;
    }

    // 标签和备注
    if (client.tags) card += `\n│ 🏷️ 标签: ${client.tags.replace(';', ' | ')}`;
    if (client.remark) card += `\n│ 📝 备注: ${client.remark.substring(0, 50)}${client.remark.length > 50 ? '...' : ''}`;

    card += `\n└── 🆔 UUID: ${client.uuid || '未设置'}`;

    return card;
    };

    const title = `${getEventTypeDesc(event.event) || 'ℹ️'} Komari 通知`;
    let message = '';

    // 头部信息 - 使用UTC+8时间
    message += `📅 <b>事件时间 (UTC+8)</b>: ${ensureUTC8Time(event.time)}\n`;
    message += `📢 <b>事件类型</b>: ${event.event || '未知'}\n`;
    if (event.emoji) message += `🔤 <b>标识</b>: ${event.emoji}\n`;
    if (event.message && event.message.trim()) {
    message += `📝 <b>事件说明</b>: ${event.message}\n`;
    }
    message += '\n' + '━'.repeat(20) + '\n';

    // 构建按钮数组
    const buttons = [];
    // 始终添加监控主页按钮(第一行)
    buttons.push([{
    text: '🔍 监控主页',
    url: `${monitorUrl}/`
    }]);

    if (event.clients && event.clients.length > 0) {
    const totalClients = event.clients.length;
    message += `🖥️ <b>受影响服务器</b>: ${totalClients} 台\n\n`;

    // 只展示前三台机器
    const displayCount = Math.min(totalClients, 3);
    const clientButtons = [];

    for (let i = 0; i < displayCount; i++) {
    const client = event.clients[i];
    message += generateClientCard(client, i + 1);

    // 为每台机器添加详情按钮
    if (client.uuid) {
    clientButtons.push({
    text: `${i + 1}. ${client.name || '服务器详情'}`,
    url: `${monitorUrl}/instance/${client.uuid}`
    });
    }
    }

    // 添加机器详情按钮行
    if (clientButtons.length > 0) {
    buttons.push(clientButtons);
    }

    // 超过3台时添加查看更多按钮
    if (totalClients > 3) {
    message += `\n\n📌 仅展示前3台,剩余 ${totalClients - 3} 台请查看监控系统`;
    buttons.push([{
    text: `📑 查看全部${totalClients}台机器`,
    url: `${monitorUrl}/`
    }]);
    }
    } else {
    message += '🖥️ <b>受影响服务器</b>: 无关联服务器信息\n';
    }

    // 发送通知(传入按钮配置)
    const success = await sendMessage(message, title, buttons);
    if (success) {
    console.log(`事件通知已发送: ${event.event}`);
    } else {
    console.error(`事件通知发送失败: ${event.event}`);
    }
    return success;

    } catch (error) {
    console.error('发送事件通知时出错:', error);

    // 发送简化的错误通知(也包含基础监控按钮)
    const fallbackMessage = `${event.emoji || ''} <b>${event.event || '未知事件'}</b>
    📅 时间 (UTC+8): ${ensureUTC8Time(event.time || new Date())}
    📝 说明: ${event.message || '无详细信息'}
    🖥️ 受影响服务器: ${event.clients?.length || 0} 台`;

    const fallbackTitle = '⚠️ Komari 通知 - 异常';
    const fallbackButtons = [[{
    text: '🔍 监控主页',
    url: `${monitorUrl}/`
    }]];

    try {
    return await sendMessage(fallbackMessage, fallbackTitle, fallbackButtons);
    } catch (fallbackError) {
    console.error('备用通知也失败:', fallbackError);
    return false;
    }
    }
    }


    这个需要每个机器单独设置么,佬,已经鸡腿+点赞了

  • lalala123 06-21 11:06
    14

    @rainbow #13 通知模板 模板

  • Amity 06-21 11:07
    15



    用的这个主题,后台按钮找不到了

  • PayPal 06-21 11:28
    16

    @Amity #15 太阳边上鼠标放上去找找

  • Amity 06-21 11:34
    17

    @PayPal #16 没有,切换成深色了也找不到那个小齿轮图标

  • PayPal 06-21 11:35
    18

    @Amity #17 最底部找找

  • Amity 06-21 11:37
    19

    @PayPal #18 https://komari.140823.xyz/ ,佬你帮我找找。我都找遍了。以前我记得就在小太阳那附近的。 ^-^

  • 厚德载物 06-21 11:37
    20

  • lalala123 06-21 11:38
    21

    @Amity #19 网站后面加个/admin 就行了

  • adktv 06-21 11:41
    22

    这个搞个ai,直接让它监控就好了。都不用komari

  • Amity 06-21 11:43
    23

    @lalala123 #21 哦,真是这样的。找了半天也找不到。给你鸡腿🍗

  • aidespiccolo 06-21 12:19
    24

    蹲到了 ^-^

  • PayPal 06-21 12:26
    25

    @Amity #19 https://komari.140823.xyz/admin

  • solaireh3 06-21 12:37
    26

    @smagic #7 是填到JavaScript那栏吗

  • smagic 06-21 12:38
    27

    @solaireh3 #26 是

  • 1314520 06-21 12:38
    28

    BD

  • solaireh3 06-21 12:39
    29

    @smagic #27 测试提示了这个Failed to send message: sendMessage is not a callable function

  • smagic 06-21 12:40
    30

    @solaireh3 #29 我更新了一下,重新复制下。有地方有问题, ^-^ ,一直自己用没发现

  • boyloft 06-21 15:41
    31

    @smagic #7 大佬 这个怎么修改啊 在后台直接使用?

  • smagic 06-21 15:42
    32

    @boyloft #31 把前几行改了, bot_token chat_id 地址,然后使用javascript通知就行

  • boyloft 06-21 17:10
    33

    @smagic #32 这样啊 感谢大佬

* 帖子来源NodeSeek
返回