Boosterpack_Enhance

补充包制作工具

Dovrai installare un'estensione come Tampermonkey, Greasemonkey o Violentmonkey per installare questo script.

Dovrai installare un'estensione come Tampermonkey o Violentmonkey per installare questo script.

Dovrai installare un'estensione come Tampermonkey o Violentmonkey per installare questo script.

Dovrai installare un'estensione come Tampermonkey o Userscripts per installare questo script.

Dovrai installare un'estensione come ad esempio Tampermonkey per installare questo script.

Dovrai installare un gestore di script utente per installare questo script.

(Ho già un gestore di script utente, lasciamelo installare!)

Dovrai installare un'estensione come ad esempio Stylus per installare questo stile.

Dovrai installare un'estensione come ad esempio Stylus per installare questo stile.

Dovrai installare un'estensione come ad esempio Stylus per installare questo stile.

Dovrai installare un'estensione per la gestione degli stili utente per installare questo stile.

Dovrai installare un'estensione per la gestione degli stili utente per installare questo stile.

Dovrai installare un'estensione per la gestione degli stili utente per installare questo stile.

(Ho già un gestore di stile utente, lasciamelo installare!)

// ==UserScript==
// @name:zh-CN   补充包合成器增强
// @name         Boosterpack_Enhance
// @namespace    https://blog.chrxw.com
// @version      1.5
// @description  补充包制作工具
// @description:zh-CN  补充包制作工具
// @author       Chr_
// @match        https://steamcommunity.com/tradingcards/boostercreator*
// @match        https://steamcommunity.com//tradingcards/boostercreator/*
// @license      AGPL-3.0
// @icon         https://blog.chrxw.com/favicon.ico
// @grant        GM_addStyle
// @grant        GM_getResourceText
// @require      https://cdnjs.cloudflare.com/ajax/libs/tabulator/6.3.0/js/tabulator.min.js
// @resource     css https://cdnjs.cloudflare.com/ajax/libs/tabulator/6.3.0/css/tabulator_midnight.min.css
// ==/UserScript==

(() => {
    'use strict';

    const g_boosterData = {};
    const g_faveriteBooster = new Set();
    let g_craftMode = "2";

    // 初始化
    setTimeout(async () => {
        loadFavorite();
        await initBoosterData();
        initPanel();
    }, 200);

    function genDiv(cls) {
        const d = document.createElement("div");
        d.className = cls;
        return d;
    }
    function genInput(cls) {
        const i = document.createElement("input");
        i.className = cls;
        return i;
    }
    function genSpan(name) {
        const s = document.createElement("span");
        s.textContent = name;
        return s;
    }
    function genCheckbox(name, cls, key = null, checked = false) {
        const l = document.createElement("label");
        const i = document.createElement("input");
        const s = genSpan(name);
        i.textContent = name;
        i.title = name;
        i.type = "checkbox";
        i.className = "fac_checkbox";
        i.checked = localStorage.getItem(key) === "true";
        l.title = name;
        l.appendChild(i);
        l.appendChild(s);
        return [l, i];
    }
    function genImage(url, cls = "bh-image") {
        const i = document.createElement("img");
        i.src = url;
        i.className = cls;
        return i;
    }
    function genButton(name, foo, cls = "bh-button") {
        const b = document.createElement("button");
        b.textContent = name;
        b.title = name;
        b.className = cls;
        b.addEventListener("click", foo);
        return b;
    }
    function genOption(name, value) {
        const o = document.createElement("option");
        o.textContent = name;
        o.value = value;
        return o;
    }
    function genSelector() {
        const s = document.createElement("select");
        s.appendChild(genOption("可交易", "2"));
        s.appendChild(genOption("不可交易", "3"));
        return s;
    }

    function initPanel() {
        const area = document.querySelector("div.booster_creator_area");

        const filterContainer = genDiv("bh-filter");
        area.appendChild(filterContainer);

        const iptSearch = genInput("bh-search");
        iptSearch.placeholder = "搜索名称 / AppId";
        let t = 0;
        iptSearch.addEventListener("keydown", () => {
            clearTimeout(t);
            t = setTimeout(updateFilter, 500);
        });
        filterContainer.appendChild(iptSearch);

        const [lblOnlyFavorite, chkOnlyFavorite] = genCheckbox("仅显示已收藏", "bh-checkbox", "bh-onlyfavorite", false);
        chkOnlyFavorite.addEventListener("change", updateFilter);
        filterContainer.appendChild(lblOnlyFavorite);
        const [lblOnlyCraftable, chkOnlyCraftable] = genCheckbox("仅显示可合成", "bh-checkbox", "bh-onlycraftable", false);
        chkOnlyCraftable.addEventListener("change", updateFilter);
        filterContainer.appendChild(lblOnlyCraftable);

        const btnSearch = genButton("清除过滤条件", () => {
            iptSearch.value = "";
            chkOnlyFavorite.checked = false;
            chkOnlyCraftable.checked = false;
            updateFilter();
        }, "bh-button");
        filterContainer.appendChild(btnSearch);
        filterContainer.appendChild(genSpan(""));

        const tabledata = Object.values(g_boosterData);

        const divRight = genDiv("bh-right");
        filterContainer.appendChild(divRight);

        const selPackPrefer = genSelector();
        selPackPrefer.addEventListener("change", (_) => {
            g_craftMode = selPackPrefer.value;
            console.log(g_craftMode);
        })
        divRight.appendChild(selPackPrefer);

        const btnBatchCraft = genButton("批量合成收藏的包", async () => {
            const favoriteItems = tabledata.filter(x => x.favorite && x.available);
            if (favoriteItems.length === 0) {
                alert("无可合成项目");
            } else {
                for (let fav of favoriteItems) {
                    await doCraftBooster2(fav.appid, fav.contailer);
                    await asleep(200);
                }
            }
        }, "bh-button-right");
        divRight.appendChild(btnBatchCraft);

        const tableContainer = genDiv("bh-table");
        area.appendChild(tableContainer);

        const rowMenu = [
            {
                label: "收藏 / 取消收藏",
                action: doEditFavorite,
            }, {
                label: "合成补充包",
                action: doCraftBooster,
            },
        ];

        const table = new Tabulator(tableContainer, {
            height: 600,
            data: tabledata,
            layout: "fitDataStretch",
            rowHeight: 40,
            rowContextMenu: rowMenu,
            initialSort: [
                { column: "favorite", dir: "desc" },
            ],
            columns: [
                { title: "AppId", field: "appid" },
                { title: "图片", field: "appid", formatter: appImageFormatter, headerSort: false, width: 100, resizable: false },
                { title: "名称", field: "fullName", width: 300 },
                { title: "张数", field: "cardSet" },
                { title: "宝珠", field: "gemPrice" },
                {
                    title: "收藏",
                    field: "favorite",
                    formatter: "tickCross",
                    sorter: "boolean",
                    cellClick: (e, cell) => doEditFavorite(e, cell.getRow()),
                },
                { title: "合成", field: "available", formatter: "tickCross", sorter: "boolean" },
                { title: "操作", field: "available", frozen: true, formatter: operatorFormatter, headerSort: false },
            ],
        });

        window.addEventListener("hashchange", () => {
            const appId = location.hash.replace("#", "");
            iptSearch.value = appId;
            updateFilter();
        });

        window.addEventListener("beforeunload", () => {
            localStorage.setItem("bh-onlyfavorite", chkOnlyFavorite.checked);
            localStorage.setItem("bh-onlycraftable", chkOnlyCraftable.checked);
        });

        updateFilter();

        function doEditFavorite(e, row) {
            const cell = row.getCell("favorite");
            const newValue = !cell.getValue();
            cell.setValue(newValue);
            const appId = row.getCell("appid").getValue();
            const strAppId = `${appId}`;
            if (newValue) {
                g_faveriteBooster.add(strAppId);
            } else {
                g_faveriteBooster.delete(strAppId);
            }
            saveFavorite();
        }

        function doCraftBooster(e, row) {
            const appid = row.getCell("appid").getValue();
            const available = row.getCell("available").getValue();
            const container = row.getCell("container").getValue();
            if (available) {
                doCraftBooster2(appid, container);
            }
        }

        function updateFilter() {
            const filters = [{ field: "keywords", type: "like", value: iptSearch.value.trim(), matchAll: true }];
            if (chkOnlyFavorite.checked) {
                filters.push({ field: "favorite", type: "=", value: true });
            }
            if (chkOnlyCraftable.checked) {
                filters.push({ field: "available", type: "=", value: true });
            }
            table.setFilter(filters);
        }

        function appImageFormatter(cell, formatterParams, onRendered) {
            const appid = cell.getValue();
            const src = `https://shared.cloudflare.steamstatic.com/store_item_assets/steam/apps/${appid}/header.jpg`;
            const image = genImage(src, "be-row-image");
            return image;
        };

        function operatorFormatter(cell, formatterParams, onRendered) {
            const data = cell.getRow().getData();
            return data.contailer;
        };
    };

    function doCraftBooster2(appid, contailer) {
        let btn = contailer.querySelector("button");
        if (btn) {
            btn.disabled = true;
        }
        craftBoosterpack(appid)
            .then((success) => {
                if (success) {
                    g_boosterData[appid].available = false;
                    contailer.innerHTML = "";
                    contailer.appendChild(genSpan("合成成功"));
                    btn = null;
                } else {
                    btn.textContent = "合成失败";
                }
            })
            .catch((err) => {
                console.error(err);
                btn.textContent = "合成失败";
            }).finally(() => {
                if (btn) {
                    btn.disabled = false;
                }
            });
    }

    // 读取补充包列表
    async function initBoosterData() {
        const gemPrice2SetCount = {
            1200: 5,
            1000: 6,
            857: 7,
            750: 8,
            667: 9,
            600: 10,
            545: 11,
            500: 12,
            462: 13,
            429: 14,
            400: 15
        };

        const currentData = parseBoosterData(document.body.innerHTML);

        const nameEnDict = {};
        if (g_strLanguage !== "english") {
            const html = await loadSecondLanguage("english");
            const secondData = parseBoosterData(html);

            for (const { appid, name } of secondData) {
                if (appid && name) {
                    nameEnDict[appid] = name;
                }
            }
        }

        for (const item of currentData) {
            const { appid, name, unavailable, price, series, available_at_time } = item;
            const intPrice = parseInt(price);
            if (appid && name && intPrice === intPrice) {
                const nameEn = nameEnDict[appid] ?? "";
                let fullName;
                let keywords;
                if (name === nameEn) {
                    fullName = name;
                    keywords = `${appid} ${name}`.toLowerCase();
                } else {
                    fullName = `${name} (${nameEn})`;
                    keywords = `${appid} ${name} ${nameEn}`.toLowerCase();
                }

                const cardSet = gemPrice2SetCount[intPrice] ?? 0;
                const favorite = g_faveriteBooster.has(`${appid}`);

                //生成按钮
                const contailer = genDiv();
                if (!available_at_time) {
                    const benCraft = genButton("合成补充包", (e) => doCraftBooster2(appid, contailer), "bh-button");
                    contailer.appendChild(benCraft);
                } else {
                    const time = genSpan(available_at_time);
                    time.className = "bh-tips";
                    contailer.appendChild(time);
                }

                g_boosterData[appid] = {
                    appid,
                    fullName,
                    gemPrice: intPrice,
                    keywords,
                    series,
                    cardSet,
                    available_at_time,
                    available: !unavailable,
                    favorite,
                    contailer,
                };
            }
        }
    }

    function parseBoosterData(html) {
        const matchJson = new RegExp(/CBoosterCreatorPage\.Init\(([\s\S]+}]),\s*parseFloat/);
        const result = html.match(matchJson);
        if (result) {
            const json = result[1];
            return JSON.parse(json);
        } else {
            return [];
        }
    }

    function loadFavorite() {
        const value = localStorage.getItem("be_faviorite") ?? "";
        const arr = value.split('|').filter(x => x);

        g_faveriteBooster.clear();
        for (const item of arr) {
            g_faveriteBooster.add(item);
        }
    }

    function saveFavorite() {
        const value = Array.from(g_faveriteBooster).join('|');
        console.log(g_faveriteBooster);
        localStorage.setItem("be_faviorite", value);
    }

    // 加载第二语言
    function loadSecondLanguage(lang) {
        return new Promise((resolve, reject) => {
            fetch(
                `https://steamcommunity.com/tradingcards/boostercreator/?l=${lang}`,
                {
                    method: "GET",
                    credentials: "include",
                })
                .then((response) => {
                    return response.text();
                })
                .then((text) => {
                    resolve(text);
                })
                .catch((err) => {
                    reject(err);
                });
        });
    }

    // 合成补充包
    function craftBoosterpack(appId) {
        return new Promise((resolve, reject) => {
            const formData = new FormData();
            formData.append("sessionid", g_sessionID);
            formData.append("appid", appId);
            formData.append("series", "1");
            formData.append("tradability_preference", g_craftMode);

            fetch(
                "https://steamcommunity.com/tradingcards/ajaxcreatebooster/",
                {
                    method: "POST",
                    body: formData,
                    credentials: "include",
                })
                .then(response => {
                    return response.json();
                })
                .then((json) => {
                    if (json.purchase_result) {
                        const { success } = json.purchase_result;
                        resolve(success === 1);
                    }
                    resolve(false);
                })
                .catch((err) => {
                    reject(err);
                });
        });
    }

    //异步延时
    function asleep(ms) {
        return new Promise(resolve => setTimeout(resolve, ms));
    }
})();

GM_addStyle(GM_getResourceText("css"));
GM_addStyle(`
img.be-row-image {
  width: 90px;
  height: auto;
}
div.bh-filter {
  padding: 10px 0;
}
div.bh-filter > * {
  margin-right: 10px;
}
div.bh-right {
  display: inline;
  position: absolute;
  right: 10px;
}
div.bh-right > * {
  margin-left: 10px;
}
span.bh-tips {
  font-size: 10px;
}
`);