@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; } } }
@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; } } } 谢谢老板
@solaireh3 #5
{{emoji}}
事件: {{event}}
服务: {{client}}
消息: {{message}}
时间: {{time}}
@Amity #15 太阳边上鼠标放上去找找
@Amity #19 网站后面加个/admin 就行了
@Amity #19 https://komari.140823.xyz/admin
未启用
@smagic #4 求个模版
默认就好
来早了,一会儿再来看评论
@smagic #7

谢谢老板