@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;
}
}
}