惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

A
About on SuperTechFans
Y
Y Combinator Blog
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
Microsoft Security Blog
Microsoft Security Blog
aimingoo的专栏
aimingoo的专栏
I
InfoQ
C
Check Point Blog
IT之家
IT之家
MyScale Blog
MyScale Blog
Apple Machine Learning Research
Apple Machine Learning Research
Vercel News
Vercel News
Last Week in AI
Last Week in AI
GbyAI
GbyAI
P
Proofpoint News Feed
量子位
Stack Overflow Blog
Stack Overflow Blog
Microsoft Azure Blog
Microsoft Azure Blog
月光博客
月光博客
阮一峰的网络日志
阮一峰的网络日志
人人都是产品经理
人人都是产品经理
B
Blog
T
The Blog of Author Tim Ferriss
H
Help Net Security
云风的 BLOG
云风的 BLOG

WishMeLz

VOXI 使用记录 · WishMeLz MailHub · WishMeLz Docker 客户端中文 · WishMeLz SuJian 1.0:给自己的博客换一张素笺 · WishMeLz MailHub - 服务条款 · WishMeLz MailHub - 隐私权政策 · WishMeLz blog.itsse.cn blog.itsse.cn 柯尼卡美能达 bizhub C226 复印机 SMB 扫描到电脑配置教程 minio 最后的绝唱 搭建配置流程 - WishMeLz Namecrane/CraneMail 优化访问速度之 - Nginx stream 做 TCP 邮件代理 港卡,CSL 记录 - WishMeLz Electron 主进程起一个可用的 HTTPS 静态服务器 - WishMeLz 目标域名在线测试 - WishMeLz 某x面板每月自动重置流量功能 - WishMeLz Safari 解锁 120hz - WishMeLz CraneMail 优化访问速度之 - Stunnel/HAProxy - WishMeLz Linux 工具箱整理 - WishMeLz Docker 使用指南 - WishMeLz
Scriptable 小组件 - 搬瓦工 - WishMeLz
WishMeLz · 2026-03-25 · via WishMeLz

2026-03-25T07:18:22.png

/***********************
 * 固定配置(直接改这里)
 ***********************/
const baseUrl = "https://api.64clouds.com/v1";
const veid = "2012715";
const api_key = "private_X12fsdffeg345gdfg";
const vpsTitle = 'MEGABOX-PRO'

// ─────────────────────────────────────────────
// 工具函数
// ─────────────────────────────────────────────

function formatBytes(bytes, decimals = 1) {
  const n = Number(bytes || 0);
  if (n === 0) return "0 B";
  const k = 1024;
  const sizes = ["B", "KB", "MB", "GB", "TB"];
  const i = Math.floor(Math.log(n) / Math.log(k));
  return `${(n / Math.pow(k, i)).toFixed(decimals)} ${sizes[i]}`;
}

function clamp(v, min, max) {
  return Math.max(min, Math.min(max, v));
}

function timestampToDate(ts) {
  if (!ts) return null;
  const d = new Date(Number(ts) * 1000);
  return isNaN(d.getTime()) ? null : d;
}

function daysUntil(date) {
  if (!date) return null;
  return Math.ceil((date.getTime() - Date.now()) / 86400000);
}

// 根据使用率返回颜色
function ratioColor(ratio) {
  if (ratio >= 0.9) return new Color("#FF453A");
  if (ratio >= 0.75) return new Color("#FF9F0A");
  return new Color("#30D158");
}

// Light/Dark 动态颜色
function dc(light, dark) {
  return Color.dynamic(new Color(light), new Color(dark));
}

// ─────────────────────────────────────────────
// DrawContext 绘图工具
// ─────────────────────────────────────────────

function makeProgressBar(ratio, width, height, fillColor) {
  const ctx = new DrawContext();
  ctx.size = new Size(width, height);
  ctx.opaque = false;
  ctx.respectScreenScale = true;

  const r = height / 2;

  // 背景轨道
  const bgPath = new Path();
  bgPath.addRoundedRect(new Rect(0, 0, width, height), r, r);
  ctx.setFillColor(dc("#E5E5EA", "#3A3A3C"));
  ctx.addPath(bgPath);
  ctx.fillPath();

  // 填充
  const filled = Math.max(r * 2, width * clamp(ratio, 0, 1));
  const fgPath = new Path();
  fgPath.addRoundedRect(new Rect(0, 0, filled, height), r, r);
  ctx.setFillColor(fillColor || ratioColor(ratio));
  ctx.addPath(fgPath);
  ctx.fillPath();

  return ctx.getImage();
}

// ─────────────────────────────────────────────
// UI 组件辅助
// ─────────────────────────────────────────────

// 小徽章
function addBadge(stack, label, bgHex, fgHex = "#FFFFFF", alpha = 0.9) {
  const badge = stack.addStack();
  badge.backgroundColor = new Color(bgHex, alpha);
  badge.cornerRadius = 4;
  badge.setPadding(2, 5, 2, 5);
  const t = badge.addText(label);
  t.font = Font.boldSystemFont(8);
  t.textColor = new Color(fgHex);
}

// 分区标签
function addSectionTitle(parent, title) {
  const row = parent.addStack();
  row.centerAlignContent();
  const t = row.addText(title.toUpperCase());
  t.font = Font.boldSystemFont(9);
  t.textColor = dc("#9A9A9E", "#636366");
  row.addSpacer();
}

// 左色块 + 标签 + 右值
function addSpecRow(parent, label, value, dotColor) {
  const row = parent.addStack();
  row.centerAlignContent();

  const dot = row.addText("■");
  dot.font = Font.boldSystemFont(9);
  dot.textColor = dotColor;

  row.addSpacer(5);

  const lbl = row.addText(label);
  lbl.font = Font.systemFont(11);
  lbl.textColor = dc("#6C6C70", "#8E8E93");

  row.addSpacer();

  const val = row.addText(value);
  val.font = Font.boldSystemFont(11);
  val.textColor = dc("#1C1C1E", "#EBEBF5");
}

// ─────────────────────────────────────────────
// API 请求
// ─────────────────────────────────────────────

async function loadServiceInfo() {
  const url = `${baseUrl}/getServiceInfo?veid=${veid}&api_key=${api_key}`;
  const req = new Request(url);
  req.method = "GET";
  const json = await req.loadJSON();
  if (json.error && json.error !== 0) throw new Error(json.message || "API 错误");
  return json;
}

// ─────────────────────────────────────────────
// 渲染:小尺寸
// ─────────────────────────────────────────────

async function renderSmall(widget, data) {
  const multiplier = Number(data.monthly_data_multiplier || 1);
  const total = Number(data.plan_monthly_data || 0) * multiplier;
  const used = Number(data.data_counter || 0) * multiplier;
  const ratio = clamp(total > 0 ? used / total : 0, 0, 1);
  const resetDate = timestampToDate(data.data_next_reset);
  const daysLeft = daysUntil(resetDate);
  const ip = (data.ip_addresses || [])[0] || "N/A";
  const color = ratioColor(ratio);
  const isSuspended = !!data.suspended;

  const grad = new LinearGradient();
  grad.locations = [0, 1];
  grad.colors = [dc("#F2F2F7", "#1C1C1E"), dc("#FFFFFF", "#111113")];
  widget.backgroundGradient = grad;
  widget.setPadding(12, 14, 12, 14);

  // 顶部:状态点 + 主机名
  const topRow = widget.addStack();
  topRow.centerAlignContent();

  const statusDot = topRow.addText("●");
  statusDot.font = Font.boldSystemFont(10);
  statusDot.textColor = isSuspended ? new Color("#FF453A") : new Color("#30D158");

  topRow.addSpacer(5);

  const hostT = topRow.addText((vpsTitle || "VPS").split(".")[0]);
  hostT.font = Font.boldSystemFont(12);
  hostT.textColor = dc("#000000", "#FFFFFF");
  hostT.lineLimit = 1;
  hostT.minimumScaleFactor = 0.6;

  widget.addSpacer(6);

  // 大百分比
  const pctT = widget.addText(`${Math.round(ratio * 100)}%`);
  pctT.font = Font.boldSystemFont(38);
  pctT.textColor = color;

  widget.addSpacer(4);

  // 进度条
  widget.addImage(makeProgressBar(ratio, 130, 7, color));

  widget.addSpacer(5);

  // 流量数值
  const trafficT = widget.addText(`${formatBytes(used)} / ${formatBytes(total)}`);
  trafficT.font = Font.systemFont(9);
  trafficT.textColor = dc("#6C6C70", "#8E8E93");
  trafficT.minimumScaleFactor = 0.7;

  widget.addSpacer();

  // 底部:IP + 剩余天数
  const botRow = widget.addStack();
  botRow.centerAlignContent();

  const ipT = botRow.addText(ip);
  ipT.font = new Font("Menlo", 9);
  ipT.textColor = dc("#6C6C70", "#8E8E93");

  botRow.addSpacer();

  if (daysLeft !== null) {
    const dT = botRow.addText(`${daysLeft}d`);
    dT.font = Font.systemFont(9);
    dT.textColor = daysLeft <= 3 ? new Color("#FF9F0A") : dc("#6C6C70", "#8E8E93");
  }
}

// ─────────────────────────────────────────────
// 渲染:中号
// ─────────────────────────────────────────────

async function renderMedium(widget, data) {
  const multiplier = Number(data.monthly_data_multiplier || 1);
  const total = Number(data.plan_monthly_data || 0) * multiplier;
  const used = Number(data.data_counter || 0) * multiplier;
  const ratio = clamp(total > 0 ? used / total : 0, 0, 1);
  const resetDate = timestampToDate(data.data_next_reset);
  const daysLeft = daysUntil(resetDate);
  const ip = (data.ip_addresses || [])[0] || "N/A";
  const isSuspended = !!data.suspended;
  const color = ratioColor(ratio);
  const vmType = (data.vm_type || "KVM").toUpperCase();
  const osRaw = data.os || "";
  const osShort = osRaw.split("-").slice(0, 2).join(" ");

  const grad = new LinearGradient();
  grad.locations = [0, 1];
  grad.colors = [dc("#F7F7F7", "#1C1C1E"), dc("#FFFFFF", "#111113")];
  widget.backgroundGradient = grad;
  widget.setPadding(14, 16, 14, 16);

  // ── 标题行 ──
  const topRow = widget.addStack();
  topRow.centerAlignContent();

  const statusDot = topRow.addText("●");
  statusDot.font = Font.boldSystemFont(11);
  statusDot.textColor = isSuspended ? new Color("#FF453A") : new Color("#30D158");
  topRow.addSpacer(6);

  const hostT = topRow.addText(vpsTitle || "VPS");
  hostT.font = Font.boldSystemFont(15);
  hostT.textColor = dc("#000000", "#FFFFFF");
  hostT.lineLimit = 1;
  hostT.minimumScaleFactor = 0.65;
  topRow.addSpacer();

  addBadge(topRow, vmType, vmType === "KVM" ? "#0A84FF" : "#9B59B6");

  if (data.location_ipv6_ready) {
    topRow.addSpacer(4);
    addBadge(topRow, "IPv6", "#30D158");
  }
  if (isSuspended) {
    topRow.addSpacer(4);
    addBadge(topRow, "暂停", "#FF453A");
  }

  widget.addSpacer(3);

  // ── 副信息行 ──
  const subRow = widget.addStack();
  subRow.centerAlignContent();

  const locT = subRow.addText(`📍 ${data.node_location || "Unknown"}`);
  locT.font = Font.systemFont(10);
  locT.textColor = dc("#6C6C70", "#8E8E93");

  subRow.addSpacer(8);

  const planT = subRow.addText(data.plan || "");
  planT.font = Font.systemFont(10);
  planT.textColor = dc("#6C6C70", "#8E8E93");
  planT.lineLimit = 1;
  planT.minimumScaleFactor = 0.7;

  widget.addSpacer(10);

  // ── 流量主区域 ──
  const mainRow = widget.addStack();
  mainRow.centerAlignContent();

  // 左:大百分比
  const leftCol = mainRow.addStack();
  leftCol.layoutVertically();

  const pctT = leftCol.addText(`${Math.round(ratio * 100)}%`);
  pctT.font = Font.boldSystemFont(40);
  pctT.textColor = color;
  pctT.minimumScaleFactor = 0.7;

  const pctLabel = leftCol.addText("月流量使用");
  pctLabel.font = Font.systemFont(9);
  pctLabel.textColor = dc("#9A9A9E", "#636366");

  mainRow.addSpacer();

  // 右:详细数值
  const rightCol = mainRow.addStack();
  rightCol.layoutVertically();

  const usedT = rightCol.addText(formatBytes(used));
  usedT.font = Font.boldSystemFont(14);
  usedT.textColor = dc("#1C1C1E", "#EBEBF5");
  usedT.rightAlignText();

  const totalT = rightCol.addText(`共 ${formatBytes(total)}`);
  totalT.font = Font.systemFont(10);
  totalT.textColor = dc("#6C6C70", "#8E8E93");
  totalT.rightAlignText();

  rightCol.addSpacer(6);

  const resetT = rightCol.addText(
    daysLeft !== null ? `${daysLeft} 天后重置` : "—"
  );
  resetT.font = Font.systemFont(10);
  resetT.textColor =
    daysLeft !== null && daysLeft <= 3
      ? new Color("#FF9F0A")
      : dc("#6C6C70", "#8E8E93");
  resetT.rightAlignText();

  widget.addSpacer(8);

  // ── 进度条 ──
  widget.addImage(makeProgressBar(ratio, 290, 8, color));

  widget.addSpacer(10);

  // ── 底部行:IP + OS ──
  const botRow = widget.addStack();
  botRow.centerAlignContent();

  const ipT = botRow.addText(ip);
  ipT.font = new Font("Menlo", 10);
  ipT.textColor = dc("#3C3C43", "#EBEBF5");

  botRow.addSpacer();

  const osT = botRow.addText(osShort);
  osT.font = Font.systemFont(10);
  osT.textColor = dc("#6C6C70", "#8E8E93");
  osT.lineLimit = 1;
  osT.minimumScaleFactor = 0.7;
}

// ─────────────────────────────────────────────
// 渲染:大尺寸
// ─────────────────────────────────────────────

async function renderLarge(widget, data) {
  const multiplier = Number(data.monthly_data_multiplier || 1);
  const total = Number(data.plan_monthly_data || 0) * multiplier;
  const used = Number(data.data_counter || 0) * multiplier;
  const ratio = clamp(total > 0 ? used / total : 0, 0, 1);
  const resetDate = timestampToDate(data.data_next_reset);
  const daysLeft = daysUntil(resetDate);
  const ip = (data.ip_addresses || [])[0] || "N/A";
  const isSuspended = !!data.suspended;
  const color = ratioColor(ratio);
  const vmType = (data.vm_type || "KVM").toUpperCase();
  const osRaw = data.os || "";
  const osShort = osRaw.split("-").slice(0, 2).join(" ");

  const ram = Number(data.plan_ram || 0);
  const disk = Number(data.plan_disk || 0);
  const swap = Number(data.plan_swap || 0);
  const abusePoints = Number(data.total_abuse_points || 0);
  const maxAbuse = Number(data.max_abuse_points || 100);
  const abuseRatio = clamp(maxAbuse > 0 ? abusePoints / maxAbuse : 0, 0, 1);
  const suspCount = Number(data.suspension_count || 0);
  const policyViolation = !!data.policy_violation;

  const grad = new LinearGradient();
  grad.locations = [0, 1];
  grad.colors = [dc("#F7F7F7", "#1C1C1E"), dc("#FFFFFF", "#111113")];
  widget.backgroundGradient = grad;
  widget.setPadding(16, 16, 16, 16);

  // ── 标题行 ──
  const topRow = widget.addStack();
  topRow.centerAlignContent();

  const statusDot = topRow.addText("●");
  statusDot.font = Font.boldSystemFont(12);
  statusDot.textColor = isSuspended ? new Color("#FF453A") : new Color("#30D158");
  topRow.addSpacer(6);

  const hostT = topRow.addText(vpsTitle || "VPS");
  hostT.font = Font.boldSystemFont(16);
  hostT.textColor = dc("#000000", "#FFFFFF");
  hostT.lineLimit = 1;
  hostT.minimumScaleFactor = 0.65;
  topRow.addSpacer();

  addBadge(topRow, vmType, vmType === "KVM" ? "#0A84FF" : "#9B59B6");
  if (data.location_ipv6_ready) {
    topRow.addSpacer(4);
    addBadge(topRow, "IPv6", "#30D158");
  }
  if (isSuspended) {
    topRow.addSpacer(4);
    addBadge(topRow, "已暂停", "#FF453A");
  }
  if (policyViolation) {
    topRow.addSpacer(4);
    addBadge(topRow, "违规", "#FF9F0A", "#1C1C1E");
  }

  widget.addSpacer(3);

  // ── 副信息 ──
  const infoRow = widget.addStack();
  const infoT = infoRow.addText(
    `📍 ${data.node_location || "Unknown"}  ·  ${data.plan || ""}  ·  ${osShort}`
  );
  infoT.font = Font.systemFont(10);
  infoT.textColor = dc("#6C6C70", "#8E8E93");
  infoT.lineLimit = 1;
  infoT.minimumScaleFactor = 0.7;

  widget.addSpacer(12);

  // ══ 月流量 ══
  addSectionTitle(widget, "月流量");
  widget.addSpacer(5);

  const trafficRow = widget.addStack();
  trafficRow.centerAlignContent();

  const pctT = trafficRow.addText(`${Math.round(ratio * 100)}%`);
  pctT.font = Font.boldSystemFont(34);
  pctT.textColor = color;
  pctT.minimumScaleFactor = 0.7;

  trafficRow.addSpacer(14);

  const trafficDetails = trafficRow.addStack();
  trafficDetails.layoutVertically();

  const usedT = trafficDetails.addText(`已用  ${formatBytes(used)}`);
  usedT.font = Font.boldSystemFont(12);
  usedT.textColor = dc("#1C1C1E", "#EBEBF5");

  const totalT = trafficDetails.addText(`共    ${formatBytes(total)}`);
  totalT.font = Font.systemFont(11);
  totalT.textColor = dc("#6C6C70", "#8E8E93");

  trafficDetails.addSpacer(3);

  const resetT = trafficDetails.addText(
    daysLeft !== null ? `⏱ ${daysLeft} 天后重置` : "⏱ —"
  );
  resetT.font = Font.systemFont(10);
  resetT.textColor =
    daysLeft !== null && daysLeft <= 3
      ? new Color("#FF9F0A")
      : dc("#6C6C70", "#8E8E93");

  widget.addSpacer(6);
  widget.addImage(makeProgressBar(ratio, 292, 8, color));

  widget.addSpacer(14);

  // ══ 套餐规格 ══
  addSectionTitle(widget, "套餐规格");
  widget.addSpacer(6);
  addSpecRow(widget, "RAM", formatBytes(ram, 0), new Color("#0A84FF"));
  widget.addSpacer(5);
  addSpecRow(widget, "磁盘", formatBytes(disk, 0), new Color("#30D158"));
  widget.addSpacer(5);
  addSpecRow(widget, "SWAP", formatBytes(swap, 0), new Color("#FF9F0A"));

  widget.addSpacer(14);

  // ══ 安全状态 ══
  addSectionTitle(widget, "安全状态");
  widget.addSpacer(5);

  const abuseRow = widget.addStack();
  abuseRow.centerAlignContent();

  const abuseLabel = abuseRow.addText("滥用积分");
  abuseLabel.font = Font.systemFont(11);
  abuseLabel.textColor = dc("#6C6C70", "#8E8E93");
  abuseRow.addSpacer();

  const abuseVal = abuseRow.addText(`${abusePoints} / ${maxAbuse}`);
  abuseVal.font = Font.boldSystemFont(11);
  abuseVal.textColor =
    abuseRatio >= 0.75 ? new Color("#FF453A") : dc("#1C1C1E", "#EBEBF5");

  widget.addSpacer(4);
  widget.addImage(makeProgressBar(abuseRatio, 292, 5, ratioColor(abuseRatio)));

  widget.addSpacer(12);

  // ══ 底部 ══
  const botRow = widget.addStack();
  botRow.centerAlignContent();

  const ipT = botRow.addText(ip);
  ipT.font = new Font("Menlo", 10);
  ipT.textColor = dc("#3C3C43", "#EBEBF5");

  botRow.addSpacer();

  if (suspCount > 0) {
    const suspT = botRow.addText(`暂停 ${suspCount} 次`);
    suspT.font = Font.systemFont(10);
    suspT.textColor = new Color("#FF9F0A");
    botRow.addSpacer(8);
  }

  const ipCountText = botRow.addText(
    `${(data.ip_addresses || []).length} IPs`
  );
  ipCountText.font = Font.systemFont(10);
  ipCountText.textColor = dc("#6C6C70", "#8E8E93");
}

// ─────────────────────────────────────────────
// 主入口
// ─────────────────────────────────────────────

async function main() {
  const widget = new ListWidget();

  try {
    const data = await loadServiceInfo();
    const family = config.widgetFamily;

    if (family === "small") {
      await renderSmall(widget, data);
    } else if (family === "large") {
      await renderLarge(widget, data);
    } else {
      await renderMedium(widget, data);
    }
  } catch (e) {
    widget.backgroundColor = new Color("#1C1C1E");
    widget.setPadding(16, 16, 16, 16);
    const title = widget.addText("⚠️ 加载失败");
    title.font = Font.boldSystemFont(14);
    title.textColor = new Color("#FF453A");
    widget.addSpacer(6);
    const msg = widget.addText(e.message || String(e));
    msg.font = Font.systemFont(10);
    msg.textColor = new Color("#8E8E93");
    msg.lineLimit = 3;
  }

  Script.setWidget(widget);
  Script.complete();
}

await main();