logo NodeSeekbeta

闲着无聊弄了一个VMISS的iOS Scriptable的小组件

如果你需要 DMIT的小组件可以前往查看

特别提醒,文中用到的Cookie默认1年有效期,且可以执行重装系统之类的风险操作,千万不要泄漏。
特别提醒,文中用到的Cookie默认1年有效期,且可以执行重装系统之类的风险操作,千万不要泄漏。
特别提醒,文中用到的Cookie默认1年有效期,且可以执行重装系统之类的风险操作,千万不要泄漏。
特别提醒,文中用到的Cookie默认1年有效期,且可以执行重装系统之类的风险操作,千万不要泄漏。

样式一

样式二

样式三

样式四

说明一

说明二

iOS安装scriptable

添加脚本
在电脑浏览器上登陆 VMISS ,获取cookie WHMCSlogin_auth_tk 的值。
在Scriptable app中运行脚本,设置好Cookie。
在桌面添加小组件选择这个脚本。
如果有多个设备,在小组件的Widget Parameter中输入数字序号,如 1

脚本内容:

const ScriptName = "VMISS";
const COOKIE_NAME = `${ScriptName}_WHMCSlogin_auth_tk`;
const CANVAS_SIZE = 50;
const RADIUS = 20;
const LINE_WIDTH = 4;
const productsIndex = args.widgetParameter ? parseInt(args.widgetParameter) - 1 : 0;
function parseTrafficUsedToGb(trafficUsed) {
    const value = parseFloat(trafficUsed.replace(/[^0-9.]/g, ""));
    if (trafficUsed.includes("MB")) {
        return value / 1024;
    }
    if (trafficUsed.includes("TB")) {
        return value * 1024;
    }
    return value;
}
function applySecondaryTextStyle(text) {
    text.font = Font.semiboldSystemFont(10);
    text.textColor = Color.dynamic(Color.gray(), Color.lightGray());
}
function addCenteredLabel(container, label) {
    const labelStack = container.addStack();
    labelStack.addSpacer();
    const text = labelStack.addText(label);
    applySecondaryTextStyle(text);
    labelStack.addSpacer();
}
function addChartBlock(rowStack, chart, label, spacerAfter = true) {
    const block = rowStack.addStack();
    block.layoutVertically();
    block.centerAlignContent();
    block.addImage(chart);
    block.addSpacer(2);
    addCenteredLabel(block, label);
    if (spacerAfter) {
        rowStack.addSpacer();
    }
}
function pickProductWithFallback(products, preferredIndex) {
    if (products.length === 0) {
        return null;
    }
    const startIndex = Math.min(Math.max(preferredIndex, 0), products.length - 1);
    for (let i = startIndex; i >= 0; i--) {
        const product = products[i];
        if (product) {
            return product;
        }
    }
    for (let i = startIndex + 1; i < products.length; i++) {
        const product = products[i];
        if (product) {
            return product;
        }
    }
    return null;
}
function showWidgetErrorAndFinish(widget, message) {
    const errorText = widget.addText(message);
    errorText.textColor = Color.red();
    errorText.font = Font.semiboldSystemFont(14);
    Script.setWidget(widget);
    widget.presentSmall();
    Script.complete();
}
async function handleSettingsMenu() {
    const setting = new Alert();
    setting.title = "设置";
    setting.message = "配置vmiss小组件";
    setting.addAction("设置 Cookie");
    setting.addDestructiveAction("删除 Cookie");
    setting.addAction("取消");
    const response = await setting.present();
    if (response === 0) {
        const cookieInput = new Alert();
        cookieInput.title = "输入 Cookie";
        cookieInput.message = "请输入您的 cookie 值:";
        cookieInput.addTextField("Cookie");
        cookieInput.addAction("保存");
        cookieInput.addAction("取消");
        const cookieResponse = await cookieInput.present();
        if (cookieResponse === 0) {
            const cookieValue = (cookieInput.textFieldValue(0) ?? "").trim();
            if (!cookieValue) {
                const invalidAlert = new Alert();
                invalidAlert.title = "Cookie 输入有误";
                invalidAlert.message = "Cookie 不能为空,请重新输入。";
                invalidAlert.addAction("好的");
                await invalidAlert.present();
            }
            else {
                Keychain.set(COOKIE_NAME, cookieValue);
                const confirmAlert = new Alert();
                confirmAlert.title = "保存成功";
                confirmAlert.message = "您的 cookie 已成功保存。";
                confirmAlert.addAction("好的");
                await confirmAlert.present();
            }
        }
        return true;
    }
    if (response === 1) {
        const confirmDelete = new Alert();
        confirmDelete.title = "确认删除";
        confirmDelete.message = "您确定要删除已保存的 Cookie 吗?";
        confirmDelete.addDestructiveAction("删除");
        confirmDelete.addAction("取消");
        const deleteResponse = await confirmDelete.present();
        if (deleteResponse === 0) {
            Keychain.remove(COOKIE_NAME);
            const deletedAlert = new Alert();
            deletedAlert.title = "删除成功";
            deletedAlert.message = "已成功删除保存的 Cookie。";
            deletedAlert.addAction("好的");
            await deletedAlert.present();
        }
        return true;
    }
    return false;
}
function parseProductsFromHtml(htmlContent) {
    const products = [];
    const rowRegex = /<tr[^>]*data-url="[^"]*id=(\d+)"[^>]*>[\s\S]*?<\/tr>/g;
    let rowMatch;
    while ((rowMatch = rowRegex.exec(htmlContent)) !== null) {
        const rowHtml = rowMatch[0];
        const id = parseInt(rowMatch[1], 10);
        const codeMatch = rowHtml.match(/<b>[^<]*<\/b>\s*-\s*([A-Za-z0-9.#]+)/);
        if (!codeMatch)
            continue;
        const code = codeMatch[1].trim();
        const nameMatch = rowHtml.match(/<a[^>]*>([^-<]+?)\s*-\s*[\d.]+\s*<\/a>/);
        const name = nameMatch ? nameMatch[1].trim() : "";
        const ipMatch = rowHtml.match(/<a[^>]*>[^-<]+?\s*-\s*([\d.]+)\s*<\/a>/);
        const ip = ipMatch ? ipMatch[1].trim() : "";
        const priceMatch = rowHtml.match(/data-order="[\d.]+">(\$[\d.]+\s*[A-Z]+)/);
        const price = priceMatch ? priceMatch[1].trim() : "";
        const statusMatch = rowHtml.match(/<span class="status[^"]*">([^<]+)<\/span>/);
        const status = statusMatch ? statusMatch[1].trim() : "";
        let positionMatch = code.match(/^([A-Z]+)/);
        let position = positionMatch ? positionMatch[1] : "";
        if (position === "CN" && code.includes("HK")) {
            position = "HK";
        }
        const cycleMatch = rowHtml.match(/小>(.*?)<\/span>/);
        let cycle = "month";
        if (cycleMatch) {
            const cycleText = cycleMatch[1].toLowerCase();
            if (cycleText.includes("季度") || cycleText.includes("quarter")) {
                cycle = "quarter";
            }
            else if (cycleText.includes("半年") || cycleText.includes("半")) {
                cycle = "half-year";
            }
            else if (cycleText.includes("年") || cycleText.includes("year")) {
                cycle = "year";
            }
        }
        products.push({
            id,
            name,
            status,
            ip,
            position,
            code,
            price,
            cycle,
        });
    }
    return products.sort((a, b) => a.id - b.id);
}
async function loadGraphData(id, type) {
    const resp = new Request(`https://app.vmiss.com/clientarea.php?action=productdetails&id=${id}&graphs=graphs&day=-2hours&_=${Date.now()}`);
    resp.timeoutInterval = 25 * 1000;
    const cookie = Keychain.get(COOKIE_NAME);
    if (!cookie) {
        throw new Error("请先设置 Cookie");
    }
    resp.headers = {
        "Cookie": "WHMCSlogin_auth_tk=" + cookie
    };
    try {
        const json = await resp.loadJSON();
        switch (type) {
            case "cpu":
                if (!json || !json.cpu) {
                    return [];
                }
                return json.cpu.map(([_, value]) => value);
            case "ram":
                if (!json || !json.ram) {
                    return [];
                }
                return json.ram.map(([_, value]) => value);
            case "net":
                if (!json || !json.net) {
                    return [];
                }
                return [
                    json.net.in.map(([_, value]) => value),
                    json.net.out.map(([_, value]) => value)
                ];
            case "disk":
                if (!json || !json.disk) {
                    return [];
                }
                return [
                    json.disk.read.map(([_, value]) => value),
                    json.disk.write.map(([_, value]) => value)
                ];
        }
    }
    catch (error) {
        try {
            const text = await resp.loadString();
            if (text.includes("login")) {
                throw new Error("Cookie 可能已过期,请重新设置");
            }
            else {
                throw new Error("数据加载失败");
            }
        }
        catch (error) {
            throw new Error("数据加载失败");
        }
    }
}
async function loadProductDetails(id) {
    const resp = new Request(`https://app.vmiss.com/clientarea.php?action=productdetails&id=${id}&getJSON`);
    resp.timeoutInterval = 25 * 1000;
    const cookie = Keychain.get(COOKIE_NAME);
    if (!cookie) {
        throw new Error("请先设置 Cookie");
    }
    resp.headers = {
        "Cookie": "WHMCSlogin_auth_tk=" + cookie
    };
    try {
        const info = await resp.loadJSON();
        const usedGb = parseTrafficUsedToGb(info.trafficUsed);
        info.trafficUsedPercent = parseFloat(((usedGb / info.trafficTotal) * 100).toFixed(2));
        return info;
    }
    catch (error) {
        try {
            const text = await resp.loadString();
            if (text.includes("login")) {
                throw new Error("Cookie 可能已过期,请重新设置");
            }
            else {
                throw new Error("数据加载失败");
            }
        }
        catch (error) {
            throw new Error("数据加载失败");
        }
    }
}
async function loadProducts(widget) {
    const resp = new Request("https://app.vmiss.com/clientarea.php?action=products");
    resp.timeoutInterval = 25 * 1000;
    resp.headers = {
        "Cookie": "WHMCSlogin_auth_tk=" + (Keychain.get(COOKIE_NAME) ?? "")
    };
    const productsHtml = await resp.loadString();
    if (productsHtml.includes("login")) {
        throw new Error("Cookie 可能已过期,请重新设置");
    }
    if (productsHtml.includes("login")) {
        const errorText = widget.addText("Cookie 可能已过期,请重新设置");
        errorText.textColor = Color.red();
        errorText.font = Font.semiboldSystemFont(14);
        Script.setWidget(widget);
        widget.presentSmall();
        Script.complete();
        return [];
    }
    else {
        return parseProductsFromHtml(productsHtml);
    }
}
async function loadPositionImage(position) {
    const url = `https://images.bybit.codes/flags/${position.toLowerCase()}.png`;
    try {
        return await new Request(url).loadImage();
    }
    catch (error) {
        return SFSymbol.named("globe").image;
    }
}
function gradientColorByRatio(ratio) {
    const t = Math.max(0, Math.min(1, ratio));
    const start = { r: 46, g: 204, b: 113 };
    const end = { r: 231, g: 76, b: 60 };
    const r = Math.round(start.r + (end.r - start.r) * t);
    const g = Math.round(start.g + (end.g - start.g) * t);
    const b = Math.round(start.b + (end.b - start.b) * t);
    return new Color(`#${r.toString(16).padStart(2, "0")}${g.toString(16).padStart(2, "0")}${b.toString(16).padStart(2, "0")}`);
}
function drawCircleProgress(progress) {
    const clampedProgress = Math.max(0, Math.min(1, progress));
    const context = new DrawContext();
    context.size = new Size(CANVAS_SIZE, CANVAS_SIZE);
    context.opaque = false;
    context.respectScreenScale = true;
    const center = new Point(CANVAS_SIZE / 2, CANVAS_SIZE / 2);
    context.setStrokeColor(new Color("#222"));
    context.setLineWidth(LINE_WIDTH);
    context.strokeEllipse(new Rect(center.x - RADIUS, center.y - RADIUS, RADIUS * 2, RADIUS * 2));
    const totalDegrees = 360 * clampedProgress;
    const stepDegrees = 3.6;
    for (let i = 0; i < totalDegrees; i += stepDegrees) {
        const ratio = totalDegrees === 0 ? 0 : i / totalDegrees;
        context.setFillColor(gradientColorByRatio(ratio));
        const angle = (i - 90) * (Math.PI / 180);
        const x = center.x + RADIUS * Math.cos(angle);
        const y = center.y + RADIUS * Math.sin(angle);
        context.fillEllipse(new Rect(x - LINE_WIDTH / 2, y - LINE_WIDTH / 2, LINE_WIDTH, LINE_WIDTH));
    }
    const percentText = `${Math.round(clampedProgress * 100)}%`;
    context.setTextColor(new Color("#FFFFFF"));
    context.setFont(Font.semiboldSystemFont(10));
    context.setTextAlignedCenter();
    context.drawTextInRect(percentText, new Rect(0, 18, CANVAS_SIZE, 14));
    return context.getImage();
}
function createProgressBarImage(total, current, width, height, fillColor, backgroundColor) {
    const context = new DrawContext();
    context.size = new Size(width, height);
    context.opaque = false;
    context.respectScreenScale = true;
    const bgPath = new Path();
    bgPath.addRoundedRect(new Rect(0, 0, width, height), height / 2, height / 2);
    context.setFillColor(backgroundColor);
    context.addPath(bgPath);
    context.fillPath();
    const progressWidth = Math.floor(width * Math.min(current / total, 1));
    const fillPath = new Path();
    fillPath.addRoundedRect(new Rect(0, 0, progressWidth, height), height / 2, height / 2);
    context.setFillColor(fillColor);
    context.addPath(fillPath);
    context.fillPath();
    return context.getImage();
}
function createLineChartImage(data, width, height, lineColor = new Color("#1F6FD4"), secondaryLineColor = new Color("#16A085")) {
    const context = new DrawContext();
    context.size = new Size(width, height);
    context.opaque = false;
    context.respectScreenScale = true;
    const seriesList = Array.isArray(data[0])
        ? data.filter((series) => series.length > 0)
        : [data];
    if (seriesList.length === 0) {
        return context.getImage();
    }
    const padding = 4;
    const chartWidth = Math.max(1, width - padding * 2);
    const chartHeight = Math.max(1, height - padding * 2);
    const mergedData = seriesList.flat();
    const minValue = Math.min(...mergedData);
    const maxValue = Math.max(...mergedData);
    const range = Math.max(1e-6, maxValue - minValue);
    const getPoints = (series) => {
        return series.map((value, index) => {
            const x = series.length === 1
                ? padding + chartWidth / 2
                : padding + (index / (series.length - 1)) * chartWidth;
            const normalized = (value - minValue) / range;
            const y = padding + chartHeight * (1 - normalized);
            return new Point(x, y);
        });
    };
    const drawSeries = (points, color, fillAlpha) => {
        if (points.length === 0) {
            return;
        }
        const areaPath = new Path();
        areaPath.move(new Point(points[0].x, height - padding));
        for (const point of points) {
            areaPath.addLine(point);
        }
        areaPath.addLine(new Point(points[points.length - 1].x, height - padding));
        areaPath.closeSubpath();
        context.setFillColor(new Color(color.hex, fillAlpha));
        context.addPath(areaPath);
        context.fillPath();
        if (points.length === 1) {
            context.setFillColor(color);
            context.fillEllipse(new Rect(points[0].x - 1.5, points[0].y - 1.5, 3, 3));
            return;
        }
        const linePath = new Path();
        linePath.move(points[0]);
        for (let i = 1; i < points.length; i++) {
            const mid = new Point((points[i - 1].x + points[i].x) / 2, (points[i - 1].y + points[i].y) / 2);
            linePath.addQuadCurve(mid, points[i - 1]);
        }
        linePath.addLine(points[points.length - 1]);
        context.setStrokeColor(color);
        context.setLineWidth(1.6);
        context.addPath(linePath);
        context.strokePath();
    };
    drawSeries(getPoints(seriesList[0]), lineColor, 0.12);
    if (seriesList.length > 1) {
        drawSeries(getPoints(seriesList[1]), secondaryLineColor, 0.08);
    }
    return context.getImage();
}
async function renderAccessoryCircular(widget, product) {
    const productDetails = await loadProductDetails(product.id);
    const progressImage = drawCircleProgress(productDetails.trafficUsedPercent / 100);
    widget.addImage(progressImage);
    Script.setWidget(widget);
    widget.presentAccessoryCircular();
}
async function renderAccessoryInline(widget, product) {
    const productDetails = await loadProductDetails(product.id);
    const usageText = widget.addText(`${product.ip}|${productDetails.trafficUsedPercent.toFixed(2)}%`);
    usageText.font = Font.semiboldSystemFont(10);
    Script.setWidget(widget);
    widget.presentAccessoryInline();
}
async function renderAccessoryRectangular(widget, product) {
    const productDetails = await loadProductDetails(product.id);
    const headerStack = widget.addStack();
    const positionImage = await loadPositionImage(product.position);
    const infoStack = headerStack.addStack();
    headerStack.centerAlignContent();
    const imgView = infoStack.addImage(positionImage);
    imgView.imageSize = new Size(16, 16);
    imgView.cornerRadius = 8;
    infoStack.addSpacer(2);
    infoStack.centerAlignContent();
    const ipText = infoStack.addText(product.ip);
    ipText.font = Font.semiboldSystemFont(10);
    ipText.textColor = Color.gray();
    headerStack.addSpacer();
    const timeText = headerStack.addText(new Date().toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" }));
    timeText.font = Font.semiboldMonospacedSystemFont(12);
    timeText.textColor = Color.gray();
    headerStack.centerAlignContent();
    widget.addSpacer(6);
    const usedTrafficValue = parseFloat(productDetails.trafficUsed.replace(/[^0-9.]/g, ""));
    const progressImage = createProgressBarImage(productDetails.trafficTotal, usedTrafficValue, 120, 12, new Color("#1F6FD4"), new Color("#E0E0E0"));
    widget.addImage(progressImage);
    widget.addSpacer(6);
    const line3Stack = widget.addStack();
    line3Stack.centerAlignContent();
    line3Stack.addSpacer();
    const detailText = line3Stack.addText(`${productDetails.trafficUsed} / ${productDetails.trafficTotal} GB`);
    detailText.font = Font.semiboldSystemFont(10);
    detailText.textColor = Color.gray();
    line3Stack.addSpacer();
    Script.setWidget(widget);
    widget.presentAccessoryRectangular();
}
async function renderSmall(widget, product) {
    const productDetails = await loadProductDetails(product.id);
    const usedGbSmall = parseTrafficUsedToGb(productDetails.trafficUsed);
    const line1Stack = widget.addStack();
    const leftStack = line1Stack.addStack();
    const statusCircle = SFSymbol.named(productDetails.status === "on" ? "circle.fill" : "circle").image;
    const statusView = leftStack.addImage(statusCircle);
    statusView.tintColor = productDetails.status === "on" ? Color.green() : Color.red();
    statusView.imageSize = new Size(8, 8);
    line1Stack.addSpacer(4);
    const nameText = leftStack.addText(product.name.length > 6 ? product.name.slice(0, 6) + "…" : product.name);
    nameText.font = Font.semiboldSystemFont(12);
    nameText.textColor = Color.dynamic(Color.black(), Color.white());
    line1Stack.addSpacer();
    const timeText2 = line1Stack.addText(new Date().toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" }));
    timeText2.font = Font.semiboldMonospacedSystemFont(12);
    timeText2.textColor = Color.dynamic(Color.gray(), Color.lightGray());
    line1Stack.centerAlignContent();
    widget.addSpacer();
    const line2Stack = widget.addStack();
    const posImage = await loadPositionImage(product.position);
    const posImgView = line2Stack.addImage(posImage);
    posImgView.imageSize = new Size(10, 10);
    line2Stack.addSpacer(4);
    const ipText2 = line2Stack.addText(product.ip);
    applySecondaryTextStyle(ipText2);
    widget.addSpacer();
    const progressBarImage = createProgressBarImage(productDetails.trafficTotal, usedGbSmall, 120, 12, new Color("#1F6FD4"), new Color("#E0E0E0"));
    const line4Stack = widget.addStack();
    line4Stack.addImage(progressBarImage);
    const processText = line4Stack.addText(`${productDetails.trafficUsedPercent.toFixed(2)}%`);
    applySecondaryTextStyle(processText);
    line4Stack.centerAlignContent();
    widget.addSpacer();
    const line3Stack = widget.addStack();
    const reamingText = line3Stack.addText(`剩余${(productDetails.trafficTotal - usedGbSmall).toFixed(2)} GB`);
    applySecondaryTextStyle(reamingText);
    line3Stack.addSpacer();
    widget.addSpacer();
    const lineChartData = await loadGraphData(product.id, "net");
    const lineChartImage = createLineChartImage(lineChartData, 120, 40);
    widget.addImage(lineChartImage);
    Script.setWidget(widget);
    widget.presentSmall();
}
async function renderMedium(widget, product) {
    const productDetails = await loadProductDetails(product.id);
    const usedGbMedium = parseTrafficUsedToGb(productDetails.trafficUsed);
    const line1Stack = widget.addStack();
    const leftStack2 = line1Stack.addStack();
    const statusCircle2 = SFSymbol.named(productDetails.status === "on" ? "circle.fill" : "circle").image;
    const statusView2 = leftStack2.addImage(statusCircle2);
    statusView2.tintColor = productDetails.status === "on" ? Color.green() : Color.red();
    statusView2.imageSize = new Size(8, 8);
    line1Stack.addSpacer(4);
    const nameOSStack = leftStack2.addStack();
    const nameText2 = nameOSStack.addText(product.name.length > 10 ? product.name.slice(0, 10) + "…" : product.name);
    nameText2.font = Font.semiboldSystemFont(12);
    nameText2.textColor = Color.dynamic(Color.black(), Color.white());
    nameOSStack.addSpacer(8);
    const osText = nameOSStack.addText(productDetails.operate_system);
    applySecondaryTextStyle(osText);
    line1Stack.addSpacer();
    const timeText3 = line1Stack.addText(new Date().toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" }));
    timeText3.font = Font.semiboldMonospacedSystemFont(12);
    timeText3.textColor = Color.dynamic(Color.gray(), Color.lightGray());
    line1Stack.centerAlignContent();
    widget.addSpacer();
    const line2Stack2 = widget.addStack();
    const posImage2 = await loadPositionImage(product.position);
    const posImgView2 = line2Stack2.addImage(posImage2);
    posImgView2.imageSize = new Size(10, 10);
    const codeText = line2Stack2.addText(product.code);
    applySecondaryTextStyle(codeText);
    line2Stack2.addSpacer();
    const ipText3 = line2Stack2.addText(product.ip);
    applySecondaryTextStyle(ipText3);
    widget.addSpacer();
    const line3Stack2 = widget.addStack();
    const trafficText = line3Stack2.addText("流量使用");
    applySecondaryTextStyle(trafficText);
    line3Stack2.addSpacer();
    const remainingTraffic = (productDetails.trafficTotal - usedGbMedium).toFixed(2);
    const remainingText = line3Stack2.addText(`剩余${remainingTraffic} GB`);
    applySecondaryTextStyle(remainingText);
    line3Stack2.centerAlignContent();
    widget.addSpacer(4);
    const progressBarImage2 = createProgressBarImage(productDetails.trafficTotal, usedGbMedium, 200, 14, new Color("#1F6FD4"), new Color("#E0E0E0"));
    const line3Stack3 = widget.addStack();
    line3Stack3.addImage(progressBarImage2);
    line3Stack3.centerAlignContent();
    widget.addSpacer(4);
    const line4Stack2 = widget.addStack();
    line4Stack2.centerAlignContent();
    const cpuData = await loadGraphData(product.id, "cpu");
    const cpuChart = createLineChartImage(cpuData, 150, 75);
    addChartBlock(line4Stack2, cpuChart, "Cpu");
    const ramData = await loadGraphData(product.id, "ram");
    const ramChart = createLineChartImage(ramData, 150, 75, new Color("#8E44AD"));
    addChartBlock(line4Stack2, ramChart, "Ram");
    const netData = await loadGraphData(product.id, "net");
    const netChart = createLineChartImage(netData, 150, 75, new Color("#2980B9"), new Color("#16A085"));
    addChartBlock(line4Stack2, netChart, "Net", false);
    line2Stack2.layoutHorizontally();
    widget.presentMedium();
    Script.setWidget(widget);
}
async function main() {
    if (config.runsInApp) {
        const shouldContinue = await handleSettingsMenu();
        if (!shouldContinue) {
            Script.complete();
            return;
        }
    }
    else {
        const widget = new ListWidget();
        const cookie = Keychain.get(COOKIE_NAME);
        if (!cookie) {
            showWidgetErrorAndFinish(widget, "请先设置 Cookie");
            return;
        }
        const products = await loadProducts(widget);
        if (products.length === 0) {
            showWidgetErrorAndFinish(widget, "未找到产品数据");
            return;
        }
        const product = pickProductWithFallback(products, productsIndex);
        if (!product) {
            showWidgetErrorAndFinish(widget, "未找到可用产品");
            return;
        }
        switch (config.widgetFamily) {
            case "accessoryCircular":
                await renderAccessoryCircular(widget, product);
                break;
            case "accessoryInline":
                await renderAccessoryInline(widget, product);
                break;
            case "accessoryRectangular":
                await renderAccessoryRectangular(widget, product);
                break;
            case "small":
                await renderSmall(widget, product);
                break;
            case "medium":
                await renderMedium(widget, product);
                break;
            case "large":
            case "extraLarge":
            default:
                Script.setWidget(widget);
                break;
        }
    }
    Script.complete();
}
void main();
12
  • 感谢佬

  • 这个可以啊,我看能不能抓下别的

  • 来个dmit的,这个用的人多

  • @苏叶 #3
    我晚点儿看看,我自己没dmit的机。
    之前买过一台到期了就没续了。

  • 牛逼了~

  • 这个真牛逼

  • 这个是真厉害了。。。

  • @VMISS #8

    尽快出API吧,这样能更规范的方便用户获取数据,不然目前这种用cookie的方式很不安全,且数据获取的稳定性也有问题。

12

你好啊,陌生人!

我的朋友,看起来你是新来的,如果想参与到讨论中,点击下面的按钮!

📈用户数目📈

目前论坛共有72255位seeker

🎉欢迎新用户🎉