Guest User

Untitled

a guest
Jun 27th, 2024
247
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. // ==UserScript==
  2. // @name:zh-CN         Steam 库存物品堆叠工具
  3. // @name               Inventory_Stack_Helper_ru
  4. // @namespace          https://blog.chrxw.com
  5. // @supportURL         https://blog.chrxw.com/scripts.html
  6. // @contributionURL    https://afdian.net/@chr233
  7. // @version            1.7
  8. // @description        Steam Inventory Stack Helper
  9. // @description:zh-CN  Steam 物品堆叠工具
  10. // @author             Chr_
  11. // @match              https://steamcommunity.com/profiles/*/inventory*
  12. // @match              https://steamcommunity.com/id/*/inventory*
  13. // @license            AGPL-3.0
  14. // @icon               https://blog.chrxw.com/favicon.ico
  15. // @grant              GM_addStyle
  16. // @downloadURL https://update.greasyfork.org/scripts/498326/Inventory_Stack_Helper.user.js
  17. // @updateURL https://update.greasyfork.org/scripts/498326/Inventory_Stack_Helper.meta.js
  18. // ==/UserScript==
  19.  
  20. // Инициация
  21. (() => {
  22.     "use strict";
  23.  
  24.     let token = document.querySelector("#application_config")?.getAttribute("data-loyalty_webapi_token");
  25.     if (token) {
  26.         token = token.replace(/"/g, "");
  27.     }
  28.     else {
  29.         ShowPromptDialog("Не удалось прочитать токен, возможно, вам потребуется снова войти в систему");
  30.         return;
  31.     }
  32.  
  33.     const GObjs = addPanel();
  34.     loadSetting();
  35.     doFitInventory();
  36.  
  37.     //==================================================================================================
  38.  
  39.     function genBtn(text, title, onclick) {
  40.         let btn = document.createElement("button");
  41.         btn.textContent = text;
  42.         btn.title = title;
  43.         btn.className = "ish_button";
  44.         btn.addEventListener("click", onclick);
  45.         return btn;
  46.     }
  47.     function genSpan(text) {
  48.         let span = document.createElement("span");
  49.         span.textContent = text;
  50.         return span;
  51.     }
  52.     function genNumber(value, placeholder, title, readonly = false, disabled = false) {
  53.         const t = document.createElement("input");
  54.         t.className = "ish_inputbox";
  55.         t.placeholder = placeholder;
  56.         t.title = title;
  57.         t.type = "number";
  58.         t.value = value;
  59.         t.readOnly = readonly;
  60.         t.disabled = disabled;
  61.         return t;
  62.     }
  63.  
  64.     function addPanel() {
  65.         const btnArea = document.querySelector("div.inventory_links");
  66.  
  67.         const container = document.createElement("div");
  68.         container.className = "ish_container"
  69.         btnArea.insertAdjacentElement("afterend", container);
  70.  
  71.         const iptAppId = genNumber("", "2923300", "AppId",true,true);
  72.         const iptContextId = genNumber("", "2", "ContextId",true,true);
  73.         const iptDelay = genNumber("500", "Delay", "Интервал между запросами");
  74.         const iptAmount = genNumber("500", "How many?", "Количество загружемых предметов");
  75.  
  76.         const btnStack = genBtn("Stuck", "Объединить предметы", doStack);
  77.         const btnUnstack = genBtn("Unstack", "Разъединить предметы", doUnstack);
  78.         const btnHelp = genBtn("❓", "Помощь", doHelp);
  79.         const spStatus = genSpan("");
  80.  
  81.         container.appendChild(genSpan("AppId"));
  82.         container.appendChild(iptAppId);
  83.         container.appendChild(iptContextId);
  84.  
  85.         container.appendChild(genSpan("Delay"));
  86.         container.appendChild(iptDelay);
  87.         container.appendChild(genSpan("Max"));
  88.         container.appendChild(iptAmount);
  89.  
  90.         container.appendChild(btnStack);
  91.         container.appendChild(btnUnstack);
  92.         container.appendChild(btnHelp);
  93.         container.appendChild(spStatus);
  94.  
  95.         document.querySelectorAll('div.games_list_tabs>a').forEach(tab => {
  96.             tab.addEventListener("click", doFitInventory);
  97.         });
  98.  
  99.         document.querySelector("#responsive_inventory_select")?.addEventListener("change", doFitInventory);
  100.  
  101.         return { iptAppId, iptContextId, iptDelay, iptAmount, btnStack, btnUnstack, btnHelp, spStatus };
  102.     }
  103.  
  104.     function doHelp() {
  105.         const { script: { version } } = GM_info;
  106.  
  107.         console.log(GM_info);
  108.  
  109.         ShowAlertDialog("FAQ",
  110.             [
  111.                 "<p>【AppId】: Выберите вкладку инвентаря, AppID и ContextID будут загружены автоматически</p>",
  112.                 "<p>【Delay】: Интервал между отправкой каждого сетевого запроса рекомендуется устанавливать более чем 100</p>",
  113.                 "<p>【Max】: Максимальное количества объединяемых предметов (по умолчанию 500). Если предметов много, скорректируете по своему усмотрению</p>",
  114.                 "<p>【Stuck】: Объедините одинаковые предметы в один </p>",
  115.                 "<p>【Unstuck】: Разъединить предметы </p>",
  116.                 `<p>【<a href="https://keylol.com/t954659-1-1" target="_blank">Блог</a>】 【<a href="https://blog.chrxw.com/scripts.html" target="_blank">Обратная связь</a>】</p>`,
  117.                 `<p>【Разработчик <a href="https://steamcommunity.com/id/Chr_" target="_blank">Chr_</a>】 【Версия ${version}】</p>`,
  118.             ].join("<br>")
  119.  
  120.         )
  121.     }
  122.  
  123.     function doFitInventory() {
  124.         const { iptAppId, iptContextId } = GObjs;
  125.         const { appid, contextid } = g_ActiveInventory;
  126.  
  127.         iptAppId.value = appid ?? "0";
  128.  
  129.         if (appid == 753) {
  130.             iptContextId.value = "6";
  131.         } else {
  132.             iptContextId.value = contextid ?? "2";
  133.         }
  134.     }
  135.  
  136.     function doStack() {
  137.         const { iptAppId, iptContextId, iptDelay, iptAmount, btnStack, btnUnstack, btnHelp, spStatus } = GObjs;
  138.  
  139.         const appId = parseInt(iptAppId.value);
  140.         const contextId = parseInt(iptContextId.value);
  141.         if (appId !== appId || contextId !== contextId) {
  142.             ShowAlertDialog("Подсказка", "Пожалуйста, проверьте, правильно ли заполнены AppID и ContextID");
  143.             return;
  144.         }
  145.  
  146.         const delay = parseInt(iptDelay.value);
  147.         if (delay !== delay) {
  148.             ShowAlertDialog("Подсказка", "Пожалуйста, проверьте, правильно ли указана задержка");
  149.             return;
  150.         }
  151.  
  152.         const amount = parseInt(iptAmount.value);
  153.         if (amount !== amount) {
  154.             ShowAlertDialog("Подсказка", "Пожалуйста, проверьте, правильно ли указано максимальное количество");
  155.             return;
  156.         }
  157.  
  158.         saveSetting();
  159.  
  160.         spStatus.textContent = "Объединение [Загрузка инвентаря]";
  161.         btnStack.style.display = "none";
  162.         btnUnstack.style.display = "none";
  163.         btnHelp.style.display = "none";
  164.  
  165.         loadInventory(appId, contextId, amount)
  166.             .then(async (inv) => {
  167.                 if (!inv) {
  168.                     ShowAlertDialog("Подсказка", "Не удалось прочитать инвентарь, пожалуйста, проверьте, правильно ли заполнены AppID и ContextID");
  169.                     return;
  170.                 }
  171.  
  172.                 const { assets } = inv;
  173.                 if (assets) {
  174.                     const itemGroup = {};
  175.  
  176.                     for (let item of assets) {
  177.                         const { classid } = item;
  178.  
  179.                         // Работает только со самоцветами и мешками самоцветов.
  180.                         if (appId === 753 && (classid != "667924416" && classid != "667933237")) {
  181.                             continue;
  182.                         }
  183.  
  184.                         if (!itemGroup[classid]) {
  185.                             itemGroup[classid] = [];
  186.                         }
  187.                         itemGroup[classid].push(item);
  188.                     }
  189.  
  190.                     let totalReq = 0;
  191.                     const todoList = [];
  192.                     for (let classId in itemGroup) {
  193.                         const items = itemGroup[classId];
  194.                         if (items.length > 1) {
  195.                             todoList.push(items);
  196.                             totalReq += items.length - 1;
  197.                         }
  198.                     }
  199.  
  200.                     if (totalReq > 0) {
  201.                         const totalType = todoList.length;
  202.                         spStatus.textContent = `Объединение [Тип 0/${totalType} Запрос 0/${totalReq} 0.00%]`;
  203.  
  204.                         let type = 1;
  205.                         let req = 1;
  206.                         for (let items of todoList) {
  207.                             for (let i = 1; i < items.length; i++) {
  208.                                 await stackItem(iptAppId.value, items[i].assetid, items[0].assetid, items[i].amount);
  209.                                 await asyncDelay(delay);
  210.                                 const percent = (100 * req / totalReq).toFixed(2);
  211.                                 spStatus.textContent = `Объединение [Тип ${type}/${totalType} Запрос ${req++}/${totalReq} ${percent}%]`;
  212.                             }
  213.                             type++;
  214.                         }
  215.                     }
  216.  
  217.                     ShowAlertDialog("Подсказка", totalReq > 0 ? "Объединение завершено": "Больше нет предметов, которые можно объединить");
  218.                 } else {
  219.                     ShowAlertDialog("Подсказка", "Не удалось прочитать инвентарь, пожалуйста, проверьте, правильно ли заполнены AppID и ContextID");
  220.                 }
  221.             })
  222.             .catch((err) => {
  223.                 ShowAlertDialog("Подсказка", "Ошибка считывания инвентаря\r\n" + err);
  224.                 console.error(err);
  225.             })
  226.             .finally(() => {
  227.                 spStatus.textContent = "";
  228.                 btnStack.style.display = null;
  229.                 btnUnstack.style.display = null;
  230.                 btnHelp.style.display = null;
  231.                 g_ActiveInventory.m_owner.ReloadInventory(appId, contextId);
  232.             });
  233.     }
  234.  
  235.     function doUnstack() {
  236.         const { iptAppId, iptContextId, iptDelay, iptAmount, btnStack, btnUnstack, btnHelp, spStatus } = GObjs;
  237.  
  238.         const appId = parseInt(iptAppId.value);
  239.         const contextId = parseInt(iptContextId.value);
  240.         if (appId !== appId || contextId !== contextId) {
  241.             ShowAlertDialog("Подсказка", "Пожалуйста, проверьте, правильно ли заполнены AppID и ContextID");
  242.             return;
  243.         }
  244.  
  245.         const delay = parseInt(iptDelay.value);
  246.         if (delay !== delay) {
  247.             ShowAlertDialog("Подсказка", "Пожалуйста, проверьте, правильно ли указана задержка");
  248.             return;
  249.         }
  250.  
  251.         const amount = parseInt(iptAmount.value);
  252.         if (amount !== amount) {
  253.             ShowAlertDialog("Подсказка", "Пожалуйста, проверьте, правильно ли указано максимальное количество");
  254.             return;
  255.         }
  256.  
  257.         saveSetting();
  258.  
  259.         spStatus.textContent = "Разборка [Загрузка инвентаря]";
  260.         btnStack.style.display = "none";
  261.         btnUnstack.style.display = "none";
  262.         btnHelp.style.display = "none";
  263.  
  264.         loadInventory(appId, contextId, amount)
  265.             .then(async (inv) => {
  266.                 if (!inv) {
  267.                     ShowAlertDialog("Подсказка", "Не удалось прочитать инвентарь, пожалуйста, проверьте, правильно ли заполнены AppID и ContextID");
  268.                     return;
  269.                 }
  270.  
  271.                 const { assets } = inv;
  272.                 if (assets) {
  273.                     const itemGroup = [];
  274.                     let totalReq = 0;
  275.                     for (let item of assets) {
  276.                         const { classid, amount } = item;
  277.  
  278.                         // Работает только со самоцветами и мешками самоцветов.
  279.                         if (appId === 753 && (classid != "667924416" && classid != "667933237")) {
  280.                             continue;
  281.                         }
  282.  
  283.                         const num = parseInt(amount);
  284.                         if (num > 1) {
  285.                             item.amount = num;
  286.                             itemGroup.push(item);
  287.                             totalReq += num - 1;
  288.                         }
  289.                     }
  290.  
  291.                     if (totalReq > 0) {
  292.                         const totalType = itemGroup.length;
  293.  
  294.                         spStatus.textContent = `Разъединение [Тип 0/${totalType} Запрос 0/${totalReq} 0.00%]`;
  295.  
  296.                         let type = 1;
  297.                         let req = 1;
  298.  
  299.                         for (let item of itemGroup) {
  300.                             for (let i = 1; i < item.amount; i++) {
  301.                                 await unStackItem(iptAppId.value, item.assetid, 1);
  302.                                 await asyncDelay(delay);
  303.                                 const percent = (100 * req / totalReq).toFixed(2);
  304.                                 spStatus.textContent = `Разъединение [Тип ${type}/${totalType} Запрос ${req++}/${totalReq} ${percent}%]`;
  305.                             }
  306.                             type++;
  307.                         }
  308.                     }
  309.  
  310.                     ShowAlertDialog("Подсказка", totalReq > 0 ? "Разъединение завершено" : "Больше нет предметов, которые можно разъединить");
  311.                 } else {
  312.                     ShowAlertDialog("Подсказка", "Не удалось прочитать инвентарь, пожалуйста, проверьте, правильно ли заполнены AppID и ContextID");
  313.                 }
  314.             })
  315.             .catch((err) => {
  316.                 ShowAlertDialog("Подсказка", "Ошибка считывания инвентаря\r\n" + err);
  317.                 console.error(err);
  318.             })
  319.             .finally(() => {
  320.                 spStatus.textContent = "";
  321.                 btnStack.style.display = null;
  322.                 btnUnstack.style.display = null;
  323.                 btnHelp.style.display = null;
  324.                 g_ActiveInventory.m_owner.ReloadInventory(appId, contextId);
  325.             });
  326.     }
  327.  
  328.     function loadSetting() {
  329.         const { iptAppId, iptContextId, iptDelay, iptAmount } = GObjs;
  330.         // iptAppId.value = localStorage.getItem("ish_appId") ?? "";
  331.         // iptContextId.value = localStorage.getItem("ish_contextId") ?? "";
  332.         iptDelay.value = localStorage.getItem("ish_delay") ?? "500";
  333.         iptAmount.value = localStorage.getItem("ish_amount") ?? "500";
  334.     }
  335.  
  336.     function saveSetting() {
  337.         const { iptAppId, iptContextId, iptDelay, iptAmount } = GObjs;
  338.         // localStorage.setItem("ish_appId", iptAppId.value);
  339.         // localStorage.setItem("ish_contextId", iptContextId.value);
  340.         localStorage.setItem("ish_delay", iptDelay.value);
  341.         localStorage.setItem("ish_amount", iptAmount.value);
  342.     }
  343.  
  344.     //==================================================================================================
  345.  
  346.     // Delay
  347.     function asyncDelay(ms) {
  348.         return new Promise(resolve => setTimeout(resolve, ms));
  349.     }
  350.  
  351.     // Чтение инвентаря
  352.     function loadInventory(appId, contextId, count) {
  353.         return new Promise((resolve, reject) => {
  354.             fetch(`https://steamcommunity.com/inventory/${g_steamID}/${appId}/${contextId}?l=${g_strLanguage}&count=${count}`)
  355.                 .then(async (response) => {
  356.                     response.json().then(json => {
  357.                         resolve(json);
  358.                     })
  359.                 })
  360.                 .catch((err) => {
  361.                     fetch(`https://steamcommunity.com/inventory/${g_steamID}/${appId}/${contextId}?l=${g_strLanguage}&count=${count}`)
  362.                         .then(async (response) => {
  363.                             response.json().then(json => {
  364.                                 resolve(json);
  365.                             })
  366.                         })
  367.                         .catch((err) => {
  368.                             fetch(`https://steamcommunity.com/inventory/${g_steamID}/${appId}/${contextId}?l=${g_strLanguage}&count=${count}`)
  369.                                 .then(async (response) => {
  370.                                     response.json().then(json => {
  371.                                         resolve(json);
  372.                                     })
  373.                                 })
  374.                                 .catch((err) => {
  375.                                     console.error(err);
  376.                                     reject(`Не удалось прочитать инвентарь ${err}`);
  377.                                 });
  378.                         });
  379.                 });
  380.         })
  381.     }
  382.  
  383.     // Объединение предметов
  384.     function stackItem(appId, fromAssetId, destAssetId, quantity) {
  385.         return new Promise((resolve, reject) => {
  386.             fetch(
  387.                 `https://api.steampowered.com/IInventoryService/CombineItemStacks/v1/`,
  388.                 {
  389.                     method: "POST",
  390.                     body: `access_token=${token}&appid=${appId}&fromitemid=${fromAssetId}&destitemid=${destAssetId}&quantity=${quantity}&steamid=${g_steamID}`,
  391.                     headers: {
  392.                         "content-type":
  393.                             "application/x-www-form-urlencoded; charset=UTF-8",
  394.                     },
  395.                 }
  396.             )
  397.                 .then((response) => {
  398.                     response.json().then(json => {
  399.                         const { success } = json;
  400.                         resolve(success);
  401.                     })
  402.                 })
  403.                 .catch((err) => {
  404.                     console.error(err);
  405.                     reject(`Не удалось объединить предметы ${err}`);
  406.                 });
  407.         });
  408.     }
  409.  
  410.     // Разъединение
  411.     function unStackItem(appId, itemAssetId, quantity) {
  412.         return new Promise((resolve, reject) => {
  413.             fetch(
  414.                 `https://api.steampowered.com/IInventoryService/SplitItemStack/v1/`,
  415.                 {
  416.                     method: "POST",
  417.                     body: `access_token=${token}&appid=${appId}&itemid=${itemAssetId}&quantity=${quantity}&steamid=${g_steamID}`,
  418.                     headers: {
  419.                         "content-type":
  420.                             "application/x-www-form-urlencoded; charset=UTF-8",
  421.                     },
  422.                 }
  423.             )
  424.                 .then((response) => {
  425.                     response.json().then(json => {
  426.                         const { success } = json;
  427.                         resolve(success);
  428.                     })
  429.                 })
  430.                 .catch((err) => {
  431.                     console.error(err);
  432.                     reject(`Не удалось разъединить предметы ${err}`);
  433.                 });
  434.         });
  435.     }
  436. })();
  437.  
  438. GM_addStyle(`
  439. div.ish_container {
  440.   display: inline;
  441. }
  442.  
  443. div.ish_container > * {
  444.   margin: 5px 5px 10px 5px;
  445. }
  446.  
  447. input.ish_inputbox {
  448.   width: 70px;
  449.   padding: 5px;
  450. }
  451.  
  452. input::-webkit-outer-spin-button,
  453. input::-webkit-inner-spin-button {
  454.   -webkit-appearance: none;
  455.   margin: 0;
  456. }
  457.  
  458. input.ish_inputbox:nth-of-type(3),
  459. input.ish_inputbox:nth-of-type(4){
  460.   width: 50px;
  461. }
  462. `);
Advertisement
Add Comment
Please, Sign In to add comment