补充包制作工具
// ==UserScript== // @name:zh-CN 补充包合成器增强 // @name Boosterpack_Enhance // @namespace https://blog.chrxw.com // @version 1.4 // @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}/capsule_231x87.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 r###lt = html.match(matchJson); if (r###lt) { const json = r###lt[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_r###lt) { const { success } = json.purchase_r###lt; 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; } `);