const MEOW_NICKNAME = "";
const PANEL_URL = "";
const VALID_COUNTRY_CODES = new Set([
"AC","AD","AE","AF","AG","AI","AL","AM","AO","AQ","AR","AS","AT","AU","AW","AX","AZ",
"BA","BB","BD","BE","BF","BG","BH","BI","BJ","BL","BM","BN","BO","BQ","BR","BS","BT",
"BV","BW","BY","BZ","CA","CC","CD","CF","CG","CH","CI","CK","CL","CM","CN","CO","CP",
"CR","CU","CV","CW","CX","CY","CZ","DE","DG","DJ","DK","DM","DO","DZ","EA","EC","EE",
"EG","EH","ER","ES","ET","EU","FI","FJ","FK","FM","FO","FR","GA","GB","GD","GE","GF",
"GG","GH","GI","GL","GM","GN","GP","GQ","GR","GS","GT","GU","GW","GY","HK","HM","HN",
"HR","HT","HU","IC","ID","IE","IL","IM","IN","IO","IQ","IR","IS","IT","JE","JM","JO",
"JP","KE","KG","KH","KI","KM","KN","KP","KR","KW","KY","KZ","LA","LB","LC","LI","LK",
"LR","LS","LT","LU","LV","LY","MA","MC","MD","ME","MF","MG","MH","MK","ML","MM","MN",
"MO","MP","MQ","MR","MS","MT","MU","MV","MW","MX","MY","MZ","NA","NC","NE","NF","NG",
"NI","NL","NO","NP","NR","NU","NZ","OM","PA","PE","PF","PG","PH","PK","PL","PM","PN",
"PR","PS","PT","PW","PY","QA","RE","RO","RS","RU","RW","SA","SB","SC","SD","SE","SG",
"SH","SI","SJ","SK","SL","SM","SN","SO","SR","SS","ST","SV","SX","SY","SZ","TA","TC",
"TD","TF","TG","TH","TJ","TK","TL","TM","TN","TO","TR","TT","TV","TW","TZ","UA","UG",
"UM","US","UY","UZ","VA","VC","VE","VG","VI","VN","VU","WF","WS","XK","YE","YT","ZA",
"ZM","ZW"
]);
const COUNTRY_ALIASES = {
"中国": "CN", "大陆": "CN", "北京": "CN", "上海": "CN", "广州": "CN", "深圳": "CN", "杭州": "CN",
"香港": "HK", "澳门": "MO", "台湾": "TW",
"美国": "US", "洛杉矶": "US", "纽约": "US", "芝加哥": "US", "西雅图": "US", "达拉斯": "US", "圣何塞": "US", "硅谷": "US",
"usa": "US", "united states": "US", "america": "US", "los angeles": "US", "new york": "US",
"chicago": "US", "seattle": "US", "dallas": "US", "san jose": "US",
"加拿大": "CA", "多伦多": "CA", "温哥华": "CA", "蒙特利尔": "CA",
"canada": "CA", "toronto": "CA", "vancouver": "CA", "montreal": "CA",
"日本": "JP", "东京": "JP", "大阪": "JP", "japan": "JP", "tokyo": "JP", "osaka": "JP",
"韩国": "KR", "首尔": "KR", "korea": "KR", "south korea": "KR", "seoul": "KR",
"新加坡": "SG", "singapore": "SG",
"德国": "DE", "法兰克福": "DE", "柏林": "DE", "germany": "DE", "deutschland": "DE", "frankfurt": "DE", "berlin": "DE",
"英国": "GB", "伦敦": "GB", "united kingdom": "GB", "britain": "GB", "london": "GB",
"法国": "FR", "巴黎": "FR", "france": "FR", "paris": "FR",
"荷兰": "NL", "阿姆斯特丹": "NL", "netherlands": "NL", "holland": "NL", "amsterdam": "NL",
"俄罗斯": "RU", "莫斯科": "RU", "russia": "RU", "moscow": "RU", "乌克兰": "UA", "ukraine": "UA",
"澳大利亚": "AU", "澳洲": "AU", "悉尼": "AU", "墨尔本": "AU", "australia": "AU", "sydney": "AU", "melbourne": "AU",
"新西兰": "NZ", "new zealand": "NZ",
"印度": "IN", "孟买": "IN", "德里": "IN", "india": "IN", "mumbai": "IN", "delhi": "IN",
"泰国": "TH", "曼谷": "TH", "thailand": "TH", "bangkok": "TH",
"越南": "VN", "河内": "VN", "胡志明": "VN", "vietnam": "VN", "hanoi": "VN", "ho chi minh": "VN",
"马来西亚": "MY", "吉隆坡": "MY", "malaysia": "MY", "kuala lumpur": "MY",
"菲律宾": "PH", "马尼拉": "PH", "philippines": "PH", "manila": "PH",
"印度尼西亚": "ID", "印尼": "ID", "雅加达": "ID", "indonesia": "ID", "jakarta": "ID",
"土耳其": "TR", "伊斯坦布尔": "TR", "turkey": "TR", "istanbul": "TR",
"阿联酋": "AE", "迪拜": "AE", "united arab emirates": "AE", "uae": "AE", "dubai": "AE",
"沙特": "SA", "沙特阿拉伯": "SA", "saudi arabia": "SA",
"巴西": "BR", "圣保罗": "BR", "brazil": "BR", "sao paulo": "BR",
"墨西哥": "MX", "mexico": "MX", "阿根廷": "AR", "argentina": "AR", "智利": "CL", "chile": "CL",
"南非": "ZA", "south africa": "ZA", "埃及": "EG", "egypt": "EG", "以色列": "IL", "israel": "IL",
"意大利": "IT", "米兰": "IT", "罗马": "IT", "italy": "IT", "milan": "IT", "rome": "IT",
"西班牙": "ES", "马德里": "ES", "spain": "ES", "madrid": "ES",
"葡萄牙": "PT", "portugal": "PT", "瑞士": "CH", "苏黎世": "CH", "switzerland": "CH", "zurich": "CH",
"瑞典": "SE", "sweden": "SE", "挪威": "NO", "norway": "NO", "芬兰": "FI", "finland": "FI",
"丹麦": "DK", "denmark": "DK", "波兰": "PL", "poland": "PL", "奥地利": "AT", "austria": "AT",
"捷克": "CZ", "czech": "CZ", "爱尔兰": "IE", "ireland": "IE", "冰岛": "IS", "iceland": "IS"
};
function getCSTTime(timeStr) {
let date;
if (!timeStr || typeof timeStr !== 'string' || timeStr.startsWith('0001')) {
date = new Date();
} else {
date = new Date(timeStr.replace(/\.\d+Z$/, 'Z'));
}
if (isNaN(date.getTime())) date = new Date();
const cst = new Date(date.getTime() + 8 * 60 * 60 * 1000);
const f = (n) => n.toString().padStart(2, '0');
return `${cst.getUTCFullYear()}-${f(cst.getUTCMonth() + 1)}-${f(cst.getUTCDate())} ${f(cst.getUTCHours())}:${f(cst.getUTCMinutes())}:${f(cst.getUTCSeconds())}`;
}
function formatTraffic(bytes) {
if (!bytes || bytes === 0) return '无限制';
const gb = bytes / (1024 ** 3);
if (gb >= 1024) return `${(gb / 1024).toFixed(2)} TB`;
return `${gb.toFixed(2)} GB`;
}
function hideIP(ip) {
if (!ip || typeof ip !== 'string') return '未知';
const parts = ip.split('.');
if (parts.length === 4) {
return `${parts[0]}.${parts[1]}.xxx.xxx`;
}
const v6Parts = ip.split(':');
if (v6Parts.length >= 3) {
return v6Parts.slice(0, 3).join(':') + ':xxxx:xxxx:xxxx';
}
return '未知';
}
function formatMemory(bytes) {
if (!bytes || bytes === 0) return '0';
const gb = bytes / (1024 ** 3);
return gb < 1 ? `${Math.round(gb * 1024)}MB` : `${Math.round(gb)}G`;
}
function hasFlagEmoji(text) {
return /[\uD83C][\uDDE6-\uDDFF][\uD83C][\uDDE6-\uDDFF]/.test(text || "");
}
function parseBillingDesc(price, currency, cycleStr) {
if (price === undefined || price === null || price === -1 || String(price).toLowerCase() === 'free') {
return '🎁 免费';
}
const symbol = currency || '$';
let cycle = String(cycleStr || '').toLowerCase().trim();
if (!cycle || cycle === '0') {
cycle = '';
} else if (!isNaN(cycle)) {
cycle = `/${cycle}天`;
} else {
const alias = { 'month': '/月', 'year': '/年', 'quarter': '/季', 'weekly': '/周' };
cycle = alias[cycle] || `/${cycleStr}`;
}
return `${symbol}${price}${cycle}`;
}
function countryCodeToFlag(code) {
code = String(code || "").toUpperCase().trim();
if (!code || code.length !== 2 || !VALID_COUNTRY_CODES.has(code)) return "";
try {
return code
.split("")
.map(char => String.fromCodePoint(127397 + char.charCodeAt(0)))
.join("");
} catch (e) {
return "";
}
}
function getCountryCodeFromClient(client) {
if (!client) return "";
const directFields = [
client.country_code, client.countryCode, client.region_code, client.regionCode,
client.iso2, client.cc, client.country_code2, client.countryCode2
];
for (const field of directFields) {
if (!field) continue;
const code = String(field).trim().toUpperCase();
if (VALID_COUNTRY_CODES.has(code)) return code;
}
const text = [
client.name, client.region, client.country, client.location,
client.remark, client.description, client.hostname, client.host
].filter(Boolean).join(" ").toLowerCase();
for (const key in COUNTRY_ALIASES) {
if (text.includes(key.toLowerCase())) {
return COUNTRY_ALIASES[key];
}
}
const tokens = text.toUpperCase().split(/[^A-Z]/).filter(token => token && token.length === 2);
for (const token of tokens) {
if (VALID_COUNTRY_CODES.has(token)) return token;
}
return "";
}
function getFlag(client) {
if (!client) return "";
if (hasFlagEmoji(client.region) || hasFlagEmoji(client.name)) {
return "";
}
const code = getCountryCodeFromClient(client);
return code ? countryCodeToFlag(code) : "";
}
function translateMessage(message) {
let msg = String(message || "").trim();
if (!msg) return "";
const lower = msg.toLowerCase();
const exactMap = {
"client is offline": "节点已离线", "client is online": "节点已恢复在线",
"client offline": "节点离线", "client online": "节点在线",
"server is offline": "服务器已离线", "server is online": "服务器已恢复在线",
"node is offline": "节点已离线", "node is online": "节点已恢复在线",
"host is offline": "主机已离线", "host is online": "主机已恢复在线",
"heartbeat timeout": "心跳超时", "connection timeout": "连接超时",
"request timeout": "请求超时", "response timeout": "响应超时",
"ping timeout": "Ping 超时", "no response": "节点无响应",
"test message": "测试消息", "test notification": "测试通知",
"cpu usage is too high": "CPU 使用率过高", "memory usage is too high": "内存使用率过高",
"ram usage is too high": "内存使用率过高", "disk usage is too high": "磁盘使用率过高",
"load is too high": "系统负载过高", "network error": "网络异常",
"network unreachable": "网络不可达", "connection refused": "连接被拒绝",
"connection reset": "连接被重置", "service expired": "服务已到期",
"service will expire": "服务即将到期", "renew success": "续费成功", "renew failed": "续费失败"
};
if (exactMap[lower]) return exactMap[lower];
const rules = [
[/\bclient is offline\b/gi, "节点已离线"], [/\bclient is online\b/gi, "节点已恢复在线"],
[/\bserver is offline\b/gi, "服务器已离线"], [/\bserver is online\b/gi, "服务器已恢复在线"],
[/\bnode is offline\b/gi, "节点已离线"], [/\bnode is online\b/gi, "节点已恢复在线"],
[/\bhost is offline\b/gi, "主机已离线"], [/\bhost is online\b/gi, "主机已恢复在线"],
[/\bheartbeat timeout\b/gi, "心跳超时"], [/\bconnection timeout\b/gi, "连接超时"],
[/\brequest timeout\b/gi, "请求超时"], [/\bresponse timeout\b/gi, "响应超时"], [/\bping timeout\b/gi, "Ping 超时"],
[/\bcpu usage is too high\b/gi, "CPU 使用率过高"], [/\bmemory usage is too high\b/gi, "内存使用率过高"],
[/\bram usage is too high\b/gi, "内存使用率过高"], [/\bdisk usage is too high\b/gi, "磁盘使用率过高"],
[/\bload is too high\b/gi, "系统负载过高"], [/\bnetwork unreachable\b/gi, "网络不可达"],
[/\bnetwork error\b/gi, "网络异常"], [/\bconnection refused\b/gi, "连接被拒绝"], [/\bconnection reset\b/gi, "连接被重置"],
[/\bno response\b/gi, "节点无响应"], [/\boffline\b/gi, "离线"], [/\bonline\b/gi, "在线"], [/\bdown\b/gi, "不可用"],
[/\bup\b/gi, "可用"], [/\balert\b/gi, "告警"], [/\bwarning\b/gi, "警告"], [/\bcritical\b/gi, "严重"],
[/\berror\b/gi, "错误"], [/\bfailed\b/gi, "失败"], [/\bfailure\b/gi, "故障"], [/\bsuccess\b/gi, "成功"],
[/\btimeout\b/gi, "超时"], [/\bheartbeat\b/gi, "心跳"], [/\bconnection\b/gi, "连接"], [/\bconnect\b/gi, "连接"],
[/\bdisconnect\b/gi, "断开连接"], [/\brequest\b/gi, "请求"], [/\bresponse\b/gi, "响应"], [/\bserver\b/gi, "服务器"],
[/\bclient\b/gi, "节点"], [/\bnode\b/gi, "节点"], [/\bhost\b/gi, "主机"], [/\bcpu\b/gi, "CPU"], [/\bmemory\b/gi, "内存"],
[/\bram\b/gi, "内存"], [/\bdisk\b/gi, "磁盘"], [/\bload\b/gi, "负载"], [/\btraffic\b/gi, "流量"], [/\bnetwork\b/gi, "网络"],
[/\bupload\b/gi, "上传"], [/\bdownload\b/gi, "下载"], [/\busage\b/gi, "使用率"], [/\bhigh\b/gi, "过高"],
[/\blow\b/gi, "过低"], [/\brenew\b/gi, "续费"], [/\bexpire\b/gi, "到期"], [/\bexpired\b/gi, "已到期"],
[/\btest\b/gi, "测试"], [/\bmessage\b/gi, "消息"], [/\bnotification\b/gi, "通知"]
];
for (const [pattern, replacement] of rules) {
msg = msg.replace(pattern, replacement);
}
return msg;
}
const EVENT_MAP = {
'online': { icon: "🟢", title: "服务器上线", level: "正常" },
'offline': { icon: "🔴", title: "服务器离线", level: "异常" },
'alert': { icon: "⚠️", title: "异常警报", level: "警告" },
'renew': { icon: "💰", title: "续费通知", level: "提醒" },
'expire': { icon: "🚨", title: "到期预警", level: "重要" },
'expired': { icon: "🚨", title: "服务到期", level: "重要" },
'test': { icon: "🧪", title: "测试通知", level: "测试" },
'recover': { icon: "🟢", title: "告警恢复", level: "正常" },
'recovered': { icon: "🟢", title: "告警恢复", level: "正常" },
'report': { icon: "📊", title: "流量定时报告", level: "报告" }
};
async function sendMessage(message, title, instanceId = null) {
if (!MEOW_NICKNAME) return false;
let jumpUrl = PANEL_URL;
if (instanceId && instanceId !== '未知') {
jumpUrl = `${PANEL_URL}/instance/${instanceId}`;
}
const apiUrl = `https://api.chuckfang.com/${encodeURIComponent(MEOW_NICKNAME)}`;
try {
const resp = await fetch(apiUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
title: title,
msg: message,
url: jumpUrl,
imgUrl: "https://avatars.githubusercontent.com/u/208285284?v=4&s=216"
}),
});
const result = await resp.json();
return resp.ok && result.status === 200;
} catch (error) {
console.error("Error:", error);
return false;
}
}
async function sendEvent(event) {
if (!event) event = {};
try {
let eventName = String(event.event || "").toLowerCase();
if (eventName.includes('report') || (event.message && event.message.includes('流量报告'))) {
eventName = 'report';
}
const info = EVENT_MAP[eventName] || { icon: "📌", title: "系统通知", level: "通知" };
const title = `${info.icon} ${info.title}`;
let clientInfo = '';
let targetInstanceId = null;
if (event.clients && Array.isArray(event.clients) && event.clients.length > 0) {
if (event.clients.length === 1) {
const c = event.clients[0] || {};
targetInstanceId = c.uuid || null;
const flag = getFlag(c);
const flagPrefix = flag ? `${flag} ` : '';
const region = c.region ? ` [${c.region}]` : '';
const hiddenIPv4 = hideIP(c.ipv4);
const hiddenIPv6 = hideIP(c.ipv6);
const mem = c.mem_total ? formatMemory(c.mem_total) : '0';
const swap = c.swap_total ? formatMemory(c.swap_total) : '0';
const disk = c.disk_total ? formatMemory(c.disk_total) : '0';
const trafficLimit = formatTraffic(c.traffic_limit);
let trafficCycle = '';
if (c.traffic_limit_type && c.traffic_limit !== 0) {
const typeLower = String(c.traffic_limit_type).toLowerCase().trim();
const typeMap = {
'sum': '(总和)', 'max': '(取最大)', 'min': '(取最小)', 'up': '(仅上传)', 'down': '(仅下载)'
};
trafficCycle = typeMap[typeLower] || `(${c.traffic_limit_type})`;
}
clientInfo += `🖥️ 服务器:${flagPrefix}${c.name || "未知节点"}${region}\n`;
clientInfo += `📝 配 置:${c.cpu_cores || '0'}C / ${mem}${swap !== '0' ? `+${swap}` : ''} / ${disk}\n`;
clientInfo += `🌐 IPv4:${hiddenIPv4}\n`;
clientInfo += `🌐 IPv6:${hiddenIPv6}\n`;
clientInfo += `📶 流量限额:${trafficLimit}${trafficCycle}\n`;
const billingDesc = parseBillingDesc(c.price, c.currency, c.billing_cycle);
clientInfo += `💰 账 单:${billingDesc}\n`;
if (c.uuid) {
clientInfo += `🔗 链接:${PANEL_URL}/instance/${c.uuid}\n`;
}
} else {
const totalClients = event.clients.length;
clientInfo += `📦 关联节点:${totalClients} 台受影响\n`;
const displayCount = Math.min(totalClients, 3);
for (let i = 0; i < totalClients; i++) {
const c = event.clients[i];
if (!c) continue;
if (i < displayCount) {
const flag = getFlag(c);
const flagPrefix = flag ? `${flag} ` : '';
const region = c.region ? ` [${c.region}]` : '';
const mem = c.mem_total ? formatMemory(c.mem_total) : '0';
const disk = c.disk_total ? formatMemory(c.disk_total) : '0';
const billingDesc = parseBillingDesc(c.price, c.currency, c.billing_cycle);
clientInfo += `\n┌── 🖥️ ${i + 1}. ${flagPrefix}${c.name || "未知节点"}${region}`;
clientInfo += `\n│ ⚙️ 配置: ${c.cpu_cores || '0'}C / ${mem} / ${disk}`;
clientInfo += `\n└── 💰 账单: ${billingDesc}`;
if (c.uuid) {
clientInfo += `\n🔗 链接: ${PANEL_URL}/instance/${c.uuid}\n`;
} else {
clientInfo += `\n`;
}
}
}
if (totalClients > 3) {
clientInfo += `\n📌 仅展示前 3 台核心卡片,其余 ${totalClients - 3} 台请前往面板查看。\n`;
}
}
} else {
if (eventName !== 'report') {
clientInfo += `🖥️ 服务器:全局系统级事件\n`;
}
}
let message = clientInfo;
message += `\n📊 事件级别:${info.level}\n`;
message += `🕒 北京时间:${getCSTTime(event.time)}`;
const translatedMessage = translateMessage(event.message);
if (translatedMessage) {
message += `\n\n📄 详细描述:\n${translatedMessage}`;
}
return await sendMessage(message, title, targetInstanceId);
} catch (error) {
console.error("Error:", error);
const fallbackTitle = '⚠️ 通知 - 系统异常';
const fallbackMessage = `🚨 解析脚本出错\n\n📢 原始事件: ${event.event || '未知'}\n📅 发生时间: ${getCSTTime(event.time)}\n📝 错误详情: ${error.message}`;
try {
return await sendMessage(fallbackMessage, fallbackTitle);
} catch (fallbackError) {
return false;
}
}
}

我用上了