您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Allows you to sort your inventory in ascending/descending order.
// ==UserScript== // @name Inventory Sorter // @namespace https://gf.qytechs.cn/en/users/1362698-iambatman // @description Allows you to sort your inventory in ascending/descending order. // @version 2.0 // @author IAmBatman [2885239] // @grant GM_addStyle // @grant GM_setValue // @grant GM_getValue // @grant GM_deleteValue // @match https://www.torn.com/item.php // ==/UserScript== (async function () { "use strict"; GM_addStyle(` .is-container { height: 100%; margin-right: 2px; position: relative; display: flex; align-items: center; } .is-btn { height: 22px !important; padding: 0 5px !important; line-height: 0 !important; } .is-modal { height: 150px; width: 300px; margin-top: 8px; /* background-color: var(--default-bg-panel-color); */ background-color: #333; border-radius: 5px; top: 100%; left: 50%; transform: translateX(-50%); z-index: 10; box-shadow: rgba(0, 0, 0, 0.25) 0px 54px 55px, rgba(0, 0, 0, 0.12) 0px -12px 30px, rgba(0, 0, 0, 0.12) 0px 4px 6px, rgba(0, 0, 0, 0.17) 0px 12px 13px, rgba(0, 0, 0, 0.09) 0px -3px 5px; position: absolute; display: grid; justify-items: center; grid-auto-rows: min-content; } .is-modal-header{ font-size: 18px; font-weight: bold; line-height: 1; padding: 8px 0 12px 0; text-decoration: underline; } .is-modal-desc{ font-size: 12px; text-align: center; margin: 0 12px 0 12px; line-height: 1; } .is-form{ text-align: center; margin-top: 16px; position: relative; display: grid; align-items: center; justify-items: center; gap: 8px; grid-template-columns: repeat(2, 1fr); grid-template-rows: repeat(2, 1fr); } .is-form-text{ text-align: center; width: 160px; height: 28px; line-height: 0; border-radius: 5px; background-color: #000; color: #9f9f9f; border-color: transparent transparent #333 transparent; grid-column: span 2 / span 2; } .is-form-submit{ border-radius: 5px !important; width: 48px !important; height: 24px !important; line-height: 0 !important; grid-column-start: 1; grid-row-start: 2; } .is-form-status{ grid-column-start: 2; grid-row-start: 2; margin-left: -8px; } .is-form-delete-btn{ cursor: pointer; top: 0; right: 0; transform: translateX(125%); position: absolute; } .is-form-delete-icon{ height: 24px; width: 24px; stroke: #fff; } .is-item-value{ width: auto !important; padding: 0 10px 0 10px !important; } .is-item-value-color{ color: var(--default-green-color); } .is-item-qty{ font-weight: bold; } li:has(.group-arrow) .is-item-value{ width: auto !important; padding: 0 30px 0 10px !important; } `); let tabs = {}; let itemValues = {}; let sortState = "default"; let posBeforeScroll = null; let itemValueSource; if (await isKeySaved()) { itemValueSource = "is"; fetchApiValues(); } else { itemValueSource = "tt"; } let currentTab = getCurrentTabElement(); let currentTabIndex = getCurrentTabIndex(); recordTab(); const itemObserver = new MutationObserver((mutationList, observer) => { mutationList.forEach((mutation) => { if (mutation.type === "childList") { if (mutation.addedNodes.length) { recordTab(); } } }); }); const container = document.createElement("span"); container.classList.add("is-container"); container.classList.add("right"); const btn = document.createElement("button"); btn.classList.add("is-btn"); btn.classList.add("torn-btn"); btn.classList.add("dark-mode"); btn.textContent = "IS"; const modal = document.createElement("div"); modal.classList.add("is-modal"); modal.classList.add("hide"); const modalHeader = document.createElement("p"); modalHeader.classList.add("is-modal-header"); modalHeader.textContent = "INVENTORY SORTER"; const modalDescription = document.createElement("p"); modalDescription.classList.add("is-modal-desc"); modalDescription.textContent = `A public API key is sufficient. The key is stored on your browser, and it's not sent anywhere.`; modal.appendChild(modalHeader); modal.appendChild(modalDescription); const form = document.createElement("form"); form.classList.add("is-form"); const formTextInput = document.createElement("input"); formTextInput.classList.add("is-form-text"); const formSubmit = document.createElement("input"); formSubmit.classList.add("is-form-submit"); formSubmit.classList.add("torn-btn"); formSubmit.classList.add("dark-mode"); const formKeyStatusText = document.createElement("p"); formKeyStatusText.classList.add("is-form-status"); const formDeleteBtn = document.createElement("button"); formDeleteBtn.classList.add("is-form-delete-btn"); if (await isKeySaved()) { formKeyStatusText.textContent = "Key: Saved"; formDeleteBtn.classList.remove("hide"); } else { formKeyStatusText.textContent = "Key: Not Saved"; formDeleteBtn.classList.add("hide"); } const deleteBtnSvg = `<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="is-form-delete-icon"> <path stroke-linecap="round" stroke-linejoin="round" d="m14.74 9-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 0 1-2.244 2.077H8.084a2.25 2.25 0 0 1-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 0 0-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 0 1 3.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 0 0-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 0 0-7.5 0" /> </svg>`; formDeleteBtn.insertAdjacentHTML("afterbegin", deleteBtnSvg); formTextInput.setAttribute("type", "text"); formTextInput.setAttribute("placeholder", "API Key"); formSubmit.setAttribute("type", "button"); formSubmit.setAttribute("value", "SAVE"); formSubmit.disabled = true; form.appendChild(formTextInput); form.appendChild(formSubmit); form.appendChild(formKeyStatusText); form.appendChild(formDeleteBtn); modal.appendChild(form); const titleEl = document.querySelector(".title-black"); container.appendChild(btn); container.appendChild(modal); titleEl.appendChild(container); itemObserver.observe(getCurrentTabContent(), { attributes: true, childList: true, subtree: true, }); const categoryElements = document.querySelectorAll("#categoriesList")[0].children; titleEl.addEventListener("click", async (e) => { if (e.target !== titleEl) return; if ( (await GM_getValue("invsorter", null)) !== null && document.querySelector(".tt-item-price") ) { alert( "Please either delete your API key after clicking the 'IS' button or disable Torn Tools to be able to use Inventory Sorter!" ); return; } else if ( (await GM_getValue("invsorter", null)) === null && !document.querySelector(".tt-item-price") ) { alert( "Please submit your API key after clicking the 'IS' button to be able to use Inventory Sorter!" ); return; } if (!posBeforeScroll && !tabs[currentTabIndex].isFullyLoaded) { posBeforeScroll = window.scrollY; await loadTabItems(); } if (tabs[currentTabIndex].isFullyLoaded) { sortTab(); } }); Array.from(categoryElements).forEach((el) => { if (el.classList.contains("no-items")) return; el.addEventListener("click", (e) => { posBeforeScroll = null; currentTab = getCurrentTabElement(); currentTabIndex = getCurrentTabIndex(); sortState = "default"; itemObserver.disconnect(); itemObserver.observe(getCurrentTabContent(), { attributes: true, childList: true, subtree: true, }); recordTab(); }); }); btn.addEventListener("click", async (e) => { modal.classList.toggle("hide"); }); formSubmit.addEventListener("click", async (e) => { e.preventDefault(); const key = formTextInput.value; const url = `https://api.torn.com/key/?key=${key}&selections=info&comment=InvSorter`; try { const res = await fetch(url); if (!res.ok) { throw new Error(res.status); } const data = await res.json(); if (data.error && data.error.error) { formKeyStatusText.textContent = "API Error!"; return; } await GM_setValue("invsorter", key); formTextInput.value = ""; formKeyStatusText.textContent = "Key: Saved"; formDeleteBtn.classList.toggle("hide"); formSubmit.disabled = true; } catch (err) { console.error(`Inventory Sorter: ${err.message}`); } }); form.addEventListener("submit", (e) => { e.preventDefault(); }); formTextInput.addEventListener("input", async (e) => { if (formTextInput.value === "" && !(await isKeySaved())) { formSubmit.disabled = true; } if (formTextInput.value !== "" && !(await isKeySaved())) { formSubmit.disabled = false; } }); formDeleteBtn.addEventListener("click", async (e) => { await GM_deleteValue("invsorter"); formTextInput.value = ""; formKeyStatusText.textContent = "Key: Not Saved"; formDeleteBtn.classList.toggle("hide"); formSubmit.disabled = false; }); function recordTab() { if (tabs[currentTabIndex]?.isFullyLoaded) return; if (tabs[currentTabIndex]) { tabs[currentTabIndex].defaultOrder = [...getCurrentTabContent().children]; if (itemValueSource === "is" && Object.keys(itemValues).length) { appendItemValues(tabs[currentTabIndex].defaultOrder); } return; } const newTab = { [currentTabIndex]: { isFullyLoaded: false, defaultOrder: [...getCurrentTabContent().children], }, }; if (itemValueSource === "is" && Object.keys(itemValues).length) { appendItemValues(newTab[currentTabIndex].defaultOrder); } tabs = { ...tabs, ...newTab }; } function getCurrentTabElement() { return document.querySelector(".ui-tabs-active"); } function getCurrentTabContent() { return document.querySelector('[aria-hidden="false"]'); } function getCurrentTabIndex() { return Array.prototype.indexOf.call( currentTab.parentNode.children, currentTab ); } async function fetchApiValues() { try { const apiKey = await GM_getValue("invsorter", null); const url = `https://api.torn.com/torn/?key=${apiKey}&selections=items&comment=InvSorter`; const res = await fetch(url); if (!res.ok) { throw new Error(res); } const data = await res.json(); if (data.error && data.error.error) { return; } const { items } = data; for (const [id, item] of Object.entries(items)) { itemValues[id] = { name: item.name, value: item.market_value }; } appendItemValues(tabs[currentTabIndex].defaultOrder); } catch (err) { console.error(`Inventory Sorter: ${err.message}`); } } function appendItemValues(defaultElements) { Array.from(defaultElements).forEach((el) => { if (!el.getAttribute("data-item")) return; if (!el.querySelector(".is-item-value")) { const nameWrap = el.querySelector(".name-wrap"); const itemValue = getItemValue(el.getAttribute("data-item")); const valueEl = document.createElement("span"); const totalValueEl = document.createElement("span"); const qtyEl = el.querySelector(".item-amount"); const bonusesEl = el.querySelector(".bonuses-wrap"); valueEl.classList.add("is-item-value-color"); totalValueEl.classList.add("is-item-value-color"); let valueContainer; if (bonusesEl) { valueContainer = document.createElement("li"); valueContainer.classList.add("is-item-value"); bonusesEl.appendChild(valueContainer); } else { valueContainer = document.createElement("span"); valueContainer.classList.add("is-item-value"); valueContainer.classList.add("right"); nameWrap.appendChild(valueContainer); } if (qtyEl.textContent === "") { valueEl.textContent = `${getUsdFormat(itemValue)}`; valueContainer.appendChild(valueEl); } else { valueEl.textContent = `${getUsdFormat(itemValue)} `; const itemQty = qtyEl.textContent; const newQtyEl = document.createElement("span"); newQtyEl.classList.add("is-item-qty"); newQtyEl.textContent = `x ${itemQty} = `; totalValueEl.textContent = `${getUsdFormat(itemQty * itemValue)}`; valueContainer.appendChild(valueEl); valueContainer.appendChild(newQtyEl); valueContainer.appendChild(totalValueEl); } } }); } function getItemValue(itemId) { return itemValues[itemId]?.value; } function getUsdFormat(amount) { return new Intl.NumberFormat("en-US", { style: "currency", currency: "USD", maximumFractionDigits: 0, }).format(amount); } function sortTab() { const defaultOrderCopy = [...tabs[currentTabIndex].defaultOrder]; const tabItems = []; for (const item of defaultOrderCopy) { if (item.classList.contains("ajax-item-loader")) { continue; } const itemId = item.getAttribute("data-item"); let itemQty = item.getAttribute("data-qty"); if (itemValueSource === "is") { if (itemQty === "") { itemQty = 1; } tabItems.push({ el: item, value: getItemValue(itemId) * itemQty }); } else if (itemValueSource === "tt") { const value = item .querySelector(".tt-item-price") .lastChild.textContent.replace(/[^0-9.-]+/g, ""); tabItems.push({ el: item, value }); } } if (sortState === "default") { sortState = "descending"; tabItems.sort((a, b) => b.value - a.value); tabItems.forEach((item) => getCurrentTabContent().appendChild(item.el)); } else if (sortState === "descending") { sortState = "ascending"; tabItems.sort((a, b) => a.value - b.value); tabItems.forEach((item) => getCurrentTabContent().appendChild(item.el)); } else if (sortState === "ascending") { sortState = "default"; defaultOrderCopy.forEach((item) => { if (!item.classList.contains("ajax-item-loader")) { getCurrentTabContent().appendChild(item); } }); } } async function loadTabItems() { const text = document.querySelector("#load-more-items-desc").textContent; if (text.toLowerCase().includes("full")) { window.scroll(0, posBeforeScroll); tabs[currentTabIndex].isFullyLoaded = true; return; } if (text.toLowerCase().includes("load more")) { document.querySelector(".items-wrap").lastElementChild.scrollIntoView(); await new Promise((resolve) => setTimeout(resolve, 500)); return loadTabItems(); } } async function isKeySaved() { return (await GM_getValue("invsorter", null)) !== null; } })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址