const SCRIPT_VERSION = "v20250617"; // == 样式注入模块 == // 注入自定义CSS隐藏特定元素 function injectCustomCSS() { const style = document.createElement("style"); style.textContent = ` /* 隐藏父级类名为 mt-4 w-full mx-auto 下的所有 div */ .mt-4.w-full.mx-auto > div { display: none; } `; document.head.appendChild(style); } injectCustomCSS(); // == 工具函数模块 == const utils = (() => { /** * 格式化文件大小,自动转换单位 * @param {number} bytes - 字节数 * @returns {{value: string, unit: string}} 格式化后的数值和单位 */ function formatFileSize(bytes) { if (bytes === 0) return { value: "0", unit: "B" }; const units = ["B", "KB", "MB", "GB", "TB", "PB"]; let size = bytes; let unitIndex = 0; while (size >= 1024 && unitIndex < units.length - 1) { size /= 1024; unitIndex++; } return { value: size.toFixed(unitIndex === 0 ? 0 : 2), unit: units[unitIndex], }; } /** * 计算百分比,输入可为大数,支持自动缩放 * @param {number} used - 已使用量 * @param {number} total - 总量 * @returns {string} 百分比字符串,保留2位小数 */ function calculatePercentage(used, total) { used = Number(used); total = Number(total); // 大数缩放,防止数值溢出 if (used > 1e15 || total > 1e15) { used /= 1e10; total /= 1e10; } return total === 0 ? "0.00" : ((used / total) * 100).toFixed(2); } /** * 格式化日期字符串,返回 yyyy-MM-dd 格式 * @param {string} dateString - 日期字符串 * @returns {string} 格式化日期 */ function formatDate(dateString) { const date = new Date(dateString); if (isNaN(date)) return ""; return date.toLocaleDateString("zh-CN", { year: "numeric", month: "2-digit", day: "2-digit", }); } /** * 安全设置子元素文本内容,避免空引用错误 * @param {HTMLElement} parent - 父元素 * @param {string} selector - 子元素选择器 * @param {string} text - 要设置的文本 */ function safeSetTextContent(parent, selector, text) { const el = parent.querySelector(selector); if (el) el.textContent = text; } /** * 根据百分比返回渐变HSL颜色(绿→橙→红) * @param {number} percentage - 0~100的百分比 * @returns {string} hsl颜色字符串 */ function getHslGradientColor(percentage) { const clamp = (val, min, max) => Math.min(Math.max(val, min), max); const lerp = (start, end, t) => start + (end - start) * t; const p = clamp(Number(percentage), 0, 100); let h, s, l; if (p <= 35) { const t = p / 35; h = lerp(142, 32, t); // 绿色到橙色 s = lerp(69, 85, t); l = lerp(45, 55, t); } else if (p <= 85) { const t = (p - 35) / 50; h = lerp(32, 0, t); // 橙色到红色 s = lerp(85, 75, t); l = lerp(55, 50, t); } else { const t = (p - 85) / 15; h = 0; // 红色加深 s = 75; l = lerp(50, 45, t); } return `hsl(${h.toFixed(0)}, ${s.toFixed(0)}%, ${l.toFixed(0)}%)`; } /** * 透明度渐隐渐现切换内容 * @param {HTMLElement} element - 目标元素 * @param {string} newContent - 新HTML内容 * @param {number} duration - 动画持续时间,毫秒 */ function fadeOutIn(element, newContent, duration = 500) { element.style.transition = `opacity ${duration / 2}ms`; element.style.opacity = "0"; setTimeout(() => { element.innerHTML = newContent; element.style.transition = `opacity ${duration / 2}ms`; element.style.opacity = "1"; }, duration / 2); } return { formatFileSize, calculatePercentage, formatDate, safeSetTextContent, getHslGradientColor, fadeOutIn, }; })(); // == 流量统计渲染模块 == const trafficRenderer = (() => { const toggleElements = []; // 存储需周期切换显示的元素及其内容 /** * 渲染流量统计条目 * @param {Object} trafficData - 后台返回的流量数据 * @param {Object} config - 配置项 */ function renderTrafficStats(trafficData, config) { const serverMap = new Map(); // 解析流量数据,按服务器名聚合 for (const cycleId in trafficData) { const cycle = trafficData[cycleId]; if (!cycle.server_name || !cycle.transfer) continue; for (const serverId in cycle.server_name) { const serverName = cycle.server_name[serverId]; const transfer = cycle.transfer[serverId]; const max = cycle.max; const from = cycle.from; const to = cycle.to; const next_update = cycle.next_update[serverId]; if (serverName && transfer !== undefined && max && from && to) { serverMap.set(serverName, { id: serverId, transfer, max, name: cycle.name, from, to, next_update, }); } } } serverMap.forEach((serverData, serverName) => { // 查找对应显示区域 const targetElement = Array.from( document.querySelectorAll("section.grid.items-center.gap-2"), ).find((section) => { const firstText = section.querySelector("p")?.textContent.trim(); return firstText === serverName.trim(); }); if (!targetElement) return; // 格式化数据 const usedFormatted = utils.formatFileSize(serverData.transfer); const totalFormatted = utils.formatFileSize(serverData.max); const percentage = utils.calculatePercentage( serverData.transfer, serverData.max, ); const fromFormatted = utils.formatDate(serverData.from); const toFormatted = utils.formatDate(serverData.to); const nextUpdateFormatted = new Date( serverData.next_update, ).toLocaleString("zh-CN", { timeZone: "Asia/Shanghai" }); const uniqueClassName = "traffic-stats-for-server-" + serverData.id; const progressColor = utils.getHslGradientColor(percentage); const containerDiv = targetElement.closest("div"); if (!containerDiv) return; // 日志输出函数 const log = (...args) => { if (config.enableLog) console.log("[renderTrafficStats]", ...args); }; // 查找是否已有对应流量条目元素 const existing = Array.from( containerDiv.querySelectorAll(".new-inserted-element"), ).find((el) => el.classList.contains(uniqueClassName)); if (!config.showTrafficStats) { // 不显示时移除对应元素 if (existing) { existing.remove(); log(`移除流量条目: ${serverName}`); } return; } if (existing) { // 更新已存在元素内容 utils.safeSetTextContent( existing, ".used-traffic", usedFormatted.value, ); utils.safeSetTextContent(existing, ".used-unit", usedFormatted.unit); utils.safeSetTextContent( existing, ".total-traffic", totalFormatted.value, ); utils.safeSetTextContent(existing, ".total-unit", totalFormatted.unit); utils.safeSetTextContent(existing, ".from-date", fromFormatted); utils.safeSetTextContent(existing, ".to-date", toFormatted); utils.safeSetTextContent( existing, ".percentage-value", percentage + "%", ); utils.safeSetTextContent( existing, ".next-update", `next update: ${nextUpdateFormatted}`, ); const progressBar = existing.querySelector(".progress-bar"); if (progressBar) { progressBar.style.width = percentage + "%"; progressBar.style.backgroundColor = progressColor; } log(`更新流量条目: ${serverName}`); } else { // 插入新的流量条目元素 let oldSection = null; if (config.insertAfter) { oldSection = containerDiv.querySelector( "section.flex.items-center.w-full.justify-between.gap-1", ) || containerDiv.querySelector("section.grid.items-center.gap-3"); } else { oldSection = containerDiv.querySelector( "section.grid.items-center.gap-3", ); } if (!oldSection) return; // 时间区间内容,用于切换显示 const defaultTimeInfoHTML = `${fromFormatted} - ${toFormatted}`; const contents = [ defaultTimeInfoHTML, `${percentage}%`, `${nextUpdateFormatted}`, ]; const newElement = document.createElement("div"); newElement.classList.add( "space-y-1.5", "new-inserted-element", uniqueClassName, ); newElement.style.width = "100%"; newElement.innerHTML = `