您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Steam 物品堆叠工具
当前为
// ==UserScript== // @name:zh-CN Steam 库存物品堆叠工具 // @name Inventory_Stack_Helper // @namespace https://blog.chrxw.com // @supportURL https://blog.chrxw.com/scripts.html // @contributionURL https://afdian.net/@chr233 // @version 1.9 // @description Steam 物品堆叠工具 // @description:zh-CN Steam 物品堆叠工具 // @author Chr_ // @match https://steamcommunity.com/profiles/*/inventory* // @match https://steamcommunity.com/id/*/inventory* // @license AGPL-3.0 // @icon https://blog.chrxw.com/favicon.ico // @grant GM_addStyle // ==/UserScript== // 初始化 (() => { "use strict"; let token = document.querySelector("#application_config")?.getAttribute("data-loyalty_webapi_token"); if (token) { token = token.replace(/"/g, ""); } else { ShowPromptDialog("读取Token失败, 可能需要重新登录"); return; } const GObjs = addPanel(); loadSetting(); doFitInventory(); //================================================================================================== function genBtn(text, title, onclick) { let btn = document.createElement("button"); btn.textContent = text; btn.title = title; btn.className = "ish_button"; btn.addEventListener("click", onclick); return btn; } function genSpan(text) { let span = document.createElement("span"); span.textContent = text; return span; } function genNumber(value, placeholder, title) { const t = document.createElement("input"); t.className = "ish_inputbox"; t.placeholder = placeholder; t.title = title; t.type = "number"; t.value = value; return t; } function addPanel() { const btnArea = document.querySelector("div.inventory_links"); const container = document.createElement("div"); container.className = "ish_container" btnArea.insertBefore(container, btnArea.firstChild); const hiddenContainer = document.createElement("div"); hiddenContainer.style.display="none"; container.appendChild(hiddenContainer); const iptAppId = genNumber("", "2923300", "AppId"); const iptContextId = genNumber("", "2", "ContextId"); const iptDelay = genNumber("500", "延时", "网络请求发送间隔"); const iptAmount = genNumber("500", "数量", "加载库存物品数量上限"); const btnStack = genBtn("堆叠", "堆叠库存中的物品", doStack); const btnUnstack = genBtn("反堆叠", "取消堆叠库存中的物品", doUnstack); const btnHelp = genBtn("❓", "查看帮助", doHelp); const spStatus = genSpan(""); hiddenContainer.appendChild(genSpan("库存")); hiddenContainer.appendChild(iptAppId); hiddenContainer.appendChild(iptContextId); hiddenContainer.appendChild(genSpan("延时")); hiddenContainer.appendChild(iptDelay); hiddenContainer.appendChild(genSpan("上限")); hiddenContainer.appendChild(iptAmount); container.appendChild(btnStack); container.appendChild(btnUnstack); container.appendChild(btnHelp); container.appendChild(spStatus); document.querySelectorAll('div.games_list_tabs>a').forEach(tab => { tab.addEventListener("click", doFitInventory); }); document.querySelector("#responsive_inventory_select")?.addEventListener("change", doFitInventory); return { iptAppId, iptContextId, iptDelay, iptAmount, btnStack, btnUnstack, btnHelp, spStatus }; } function doHelp() { const { script: { version } } = GM_info; console.log(GM_info); ShowAlertDialog("帮助", [ "<p>【堆叠】: 将指定库存中的同类物品堆叠到一起</p>", "<p>【反堆叠】: 将指定库存中的已堆叠物品拆分成单个物品</p>", `<p>【<a href="https://keylol.com/t954659-1-1" target="_blank">发布帖</a>】 【<a href="https://blog.chrxw.com/scripts.html" target="_blank">脚本反馈</a>】</p>`, `<p>【Developed by <a href="https://steamcommunity.com/id/Chr_" target="_blank">Chr_</a>】 【当前版本 ${version}】</p>`, ].join("<br>") ) } function doFitInventory() { const { iptAppId, iptContextId } = GObjs; const { appid, contextid } = g_ActiveInventory; iptAppId.value = appid ?? "0"; if (appid == 753) { iptContextId.value = "6"; } else { iptContextId.value = contextid ?? "2"; } } function doStack() { const { iptAppId, iptContextId, iptDelay, iptAmount, btnStack, btnUnstack, btnHelp, spStatus } = GObjs; const appId = parseInt(iptAppId.value); const contextId = parseInt(iptContextId.value); if (appId !== appId || contextId !== contextId) { ShowAlertDialog("提示", "请检查 AppId 和 ContextId 是否填写正确"); return; } const delay = parseInt(iptDelay.value); if (delay !== delay) { ShowAlertDialog("提示", "请检查 延时 是否填写正确"); return; } const amount = parseInt(iptAmount.value); if (amount !== amount) { ShowAlertDialog("提示", "请检查 上限 是否填写正确"); return; } saveSetting(); spStatus.textContent = "堆叠中 [正在加载库存]"; btnStack.style.display = "none"; btnUnstack.style.display = "none"; btnHelp.style.display = "none"; loadInventory(appId, contextId, amount) .then(async (inv) => { if (!inv) { ShowAlertDialog("提示", "库存读取失败, 请检查 AppId 和 ContextId 是否填写正确"); return; } const { assets } = inv; if (assets) { const itemGroup = {}; for (let item of assets) { const { classid } = item; // 只处理宝珠和宝珠袋 if (appId === 753 && (classid != "667924416" && classid != "667933237")) { continue; } if (!itemGroup[classid]) { itemGroup[classid] = []; } itemGroup[classid].push(item); } let totalReq = 0; const todoList = []; for (let classId in itemGroup) { const items = itemGroup[classId]; if (items.length > 1) { todoList.push(items); totalReq += items.length - 1; } } if (totalReq > 0) { const totalType = todoList.length; spStatus.textContent = `堆叠中 [种类 0/${totalType} 请求 0/${totalReq} 0.00%]`; let type = 1; let req = 1; for (let items of todoList) { for (let i = 1; i < items.length; i++) { await stackItem(iptAppId.value, items[i].assetid, items[0].assetid, items[i].amount); await asyncDelay(delay); const percent = (100 * req / totalReq).toFixed(2); spStatus.textContent = `堆叠中 [种类 ${type}/${totalType} 请求 ${req++}/${totalReq} ${percent}%]`; } type++; } } ShowAlertDialog("提示", totalReq > 0 ? "堆叠操作完成" : "无可堆叠物品"); } else { ShowAlertDialog("提示", "库存读取失败, 请检查 AppId 和 ContextId 是否填写正确"); } }) .catch((err) => { ShowAlertDialog("提示", "库存读取出错, 错误信息\r\n" + err); console.error(err); }) .finally(() => { spStatus.textContent = ""; btnStack.style.display = null; btnUnstack.style.display = null; btnHelp.style.display = null; g_ActiveInventory.m_owner.ReloadInventory(appId, contextId); }); } function doUnstack() { const { iptAppId, iptContextId, iptDelay, iptAmount, btnStack, btnUnstack, btnHelp, spStatus } = GObjs; const appId = parseInt(iptAppId.value); const contextId = parseInt(iptContextId.value); if (appId !== appId || contextId !== contextId) { ShowAlertDialog("提示", "请检查 AppId 和 ContextId 是否填写正确"); return; } const delay = parseInt(iptDelay.value); if (delay !== delay) { ShowAlertDialog("提示", "请检查 延时 是否填写正确"); return; } const amount = parseInt(iptAmount.value); if (amount !== amount) { ShowAlertDialog("提示", "请检查 上限 是否填写正确"); return; } saveSetting(); spStatus.textContent = "反堆叠中 [正在加载库存]"; btnStack.style.display = "none"; btnUnstack.style.display = "none"; btnHelp.style.display = "none"; loadInventory(appId, contextId, amount) .then(async (inv) => { if (!inv) { ShowAlertDialog("提示", "库存读取失败, 请检查 AppId 和 ContextId 是否填写正确"); return; } const { assets } = inv; if (assets) { const itemGroup = []; let totalReq = 0; for (let item of assets) { const { classid, amount } = item; // 只处理宝珠和宝珠袋 if (appId === 753 && (classid != "667924416" && classid != "667933237")) { continue; } const num = parseInt(amount); if (num > 1) { item.amount = num; itemGroup.push(item); totalReq += num - 1; } } if (totalReq > 0) { const totalType = itemGroup.length; spStatus.textContent = `反堆叠中 [种类 0/${totalType} 请求 0/${totalReq} 0.00%]`; let type = 1; let req = 1; for (let item of itemGroup) { for (let i = 1; i < item.amount; i++) { await unStackItem(iptAppId.value, item.assetid, 1); await asyncDelay(delay); const percent = (100 * req / totalReq).toFixed(2); spStatus.textContent = `反堆叠中 [种类 ${type}/${totalType} 请求 ${req++}/${totalReq} ${percent}%]`; } type++; } } ShowAlertDialog("提示", totalReq > 0 ? "反堆叠操作完成" : "无可反堆叠物品"); } else { ShowAlertDialog("提示", "库存读取失败, 请检查 AppId 和 ContextId 是否填写正确"); } }) .catch((err) => { ShowAlertDialog("提示", "库存读取出错, 错误信息\r\n" + err); console.error(err); }) .finally(() => { spStatus.textContent = ""; btnStack.style.display = null; btnUnstack.style.display = null; btnHelp.style.display = null; g_ActiveInventory.m_owner.ReloadInventory(appId, contextId); }); } function loadSetting() { const { iptAppId, iptContextId, iptDelay, iptAmount } = GObjs; // iptAppId.value = localStorage.getItem("ish_appId") ?? ""; // iptContextId.value = localStorage.getItem("ish_contextId") ?? ""; iptDelay.value = localStorage.getItem("ish_delay") ?? "500"; iptAmount.value = localStorage.getItem("ish_amount") ?? "500"; } function saveSetting() { const { iptAppId, iptContextId, iptDelay, iptAmount } = GObjs; // localStorage.setItem("ish_appId", iptAppId.value); // localStorage.setItem("ish_contextId", iptContextId.value); localStorage.setItem("ish_delay", iptDelay.value); localStorage.setItem("ish_amount", iptAmount.value); } //================================================================================================== // 延时 function asyncDelay(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } // 读取库存 function loadInventory(appId, contextId, count) { return new Promise((resolve, reject) => { fetch(`https://steamcommunity.com/inventory/${g_steamID}/${appId}/${contextId}?l=${g_strLanguage}&count=${count}`) .then(async (response) => { response.json().then(json => { if (json) { resolve(json); } else { fetch(`https://steamcommunity.com/inventory/${g_steamID}/${appId}/${contextId}?l=${g_strLanguage}&count=${count}`) .then(async (response) => { response.json().then(json => { if (json) { resolve(json); } else { fetch(`https://steamcommunity.com/inventory/${g_steamID}/${appId}/${contextId}?l=${g_strLanguage}&count=${count}`) .then(async (response) => { response.json().then(json => { if (json) { resolve(json); } else { } }) }) .catch((err) => { console.error(err); reject(`读取库存失败 ${err}`); }); } }) }) .catch((err) => { console.error(err); reject(`读取库存失败 ${err}`); }); } }) }) .catch((err) => { console.error(err); reject(`读取库存失败 ${err}`); }); }) } // 堆叠物品 function stackItem(appId, fromAssetId, destAssetId, quantity) { return new Promise((resolve, reject) => { fetch( `https://api.steampowered.com/IInventoryService/CombineItemStacks/v1/`, { method: "POST", body: `access_token=${token}&appid=${appId}&fromitemid=${fromAssetId}&destitemid=${destAssetId}&quantity=${quantity}&steamid=${g_steamID}`, headers: { "content-type": "application/x-www-form-urlencoded; charset=UTF-8", }, } ) .then((response) => { response.json().then(json => { const { success } = json; resolve(success); }) }) .catch((err) => { console.error(err); reject(`堆叠物品失败 ${err}`); }); }); } // 取消堆叠物品 function unStackItem(appId, itemAssetId, quantity) { return new Promise((resolve, reject) => { fetch( `https://api.steampowered.com/IInventoryService/SplitItemStack/v1/`, { method: "POST", body: `access_token=${token}&appid=${appId}&itemid=${itemAssetId}&quantity=${quantity}&steamid=${g_steamID}`, headers: { "content-type": "application/x-www-form-urlencoded; charset=UTF-8", }, } ) .then((response) => { response.json().then(json => { const { success } = json; resolve(success); }) }) .catch((err) => { console.error(err); reject(`取消堆叠物品失败 ${err}`); }); }); } })(); GM_addStyle(` div.ish_container { display: inline; } div.ish_container > * { margin-right: 5px; } input.ish_inputbox { width: 70px; padding: 5px; } input.ish_inputbox:nth-of-type(3), input.ish_inputbox:nth-of-type(4){ width: 50px; } `);