您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Sort items by total or single value
// ==UserScript== // @name Torn — Sort items by value // @namespace http://tampermonkey.net/ // @version 1.0 // @description Sort items by total or single value // @author Charkel [3429133] // @match https://www.torn.com/item.php* // @grant none // ==/UserScript== (function () { 'use strict'; const TITLE_BAR_SELECTOR = '.title-black.hospital-dark.top-round.scroll-dark'; const BUTTON_CONTAINER_CLASS = 'tm-sort-buttons'; let itemsFullyLoaded = false; let loadingOverlay; // Inject CSS for buttons and overlay const style = document.createElement('style'); style.textContent = ` .tm-sort-btn { margin-left: 6px; padding: 3px 7px; background: #acea00; border: 1px solid #222; border-radius: 3px; cursor: pointer; font-size: 12px; font-weight: 700; } .tm-loading-overlay { position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; background: rgba(0,0,0,0.70); display: flex; align-items: center; justify-content: center; z-index: 9999; font-size: 48px; font-weight: bold; color: #fff; text-shadow: -2px -2px 4px rgba(0,0,0,0.9), 2px -2px 4px rgba(0,0,0,0.9), -2px 2px 4px rgba(0,0,0,0.9), 2px 2px 4px rgba(0,0,0,0.9); pointer-events: none; } `; document.head.appendChild(style); function createButton(label, sortType) { const btn = document.createElement('button'); btn.className = 'tm-sort-btn'; btn.textContent = label + ' ↓'; btn.dataset.order = 'desc'; btn.dataset.sortType = sortType; return btn; } function showLoadingOverlay() { if (!loadingOverlay) { loadingOverlay = document.createElement('div'); loadingOverlay.className = 'tm-loading-overlay'; loadingOverlay.textContent = 'Loading items, please wait…'; document.body.appendChild(loadingOverlay); } loadingOverlay.style.display = 'flex'; } function hideLoadingOverlay() { if (loadingOverlay) loadingOverlay.style.display = 'none'; } async function loadAllItems() { if (itemsFullyLoaded) return; showLoadingOverlay(); return new Promise(resolve => { let lastHeight = 0; let sameHeightCount = 0; let attempts = 0; const maxAttempts = 100; function scrollStep() { attempts++; window.scrollTo(0, document.body.scrollHeight); const newHeight = document.body.scrollHeight; if (newHeight === lastHeight) { sameHeightCount++; if (sameHeightCount > 5 || attempts > maxAttempts) { itemsFullyLoaded = true; hideLoadingOverlay(); resolve(); return; } } else { sameHeightCount = 0; lastHeight = newHeight; } setTimeout(scrollStep, 300); } scrollStep(); }); } function parsePriceElem(priceElem) { if (!priceElem) return { single: 0, total: 0, qty: 0 }; const rawText = priceElem.textContent.replace(/\u00A0/g, ' ').trim(); if (/N\/A/i.test(rawText)) return { single: 0, total: 0, qty: 0 }; const numTokens = (rawText.match(/\d[\d,]*/g) || []).map(s => parseInt(s.replace(/,/g, ''), 10)); let qty = 0; const qtySpan = priceElem.querySelector('.tt-item-quantity'); const qtyMatch = qtySpan?.textContent.match(/(\d+)/) || rawText.match(/(\d+)\s*x/i); if (qtyMatch) qty = parseInt(qtyMatch[1], 10); if (qty) { const total = numTokens.length ? numTokens[numTokens.length - 1] : 0; let single = numTokens.find(n => n !== qty && n !== total) || 0; if (!single && total && qty) single = Math.round(total / qty); if (numTokens.length >= 1 && single && qty && numTokens[0] * qty === total) single = numTokens[0]; return { single: single || 0, total: total || 0, qty }; } if (numTokens.length === 0) return { single: 0, total: 0, qty: 0 }; if (numTokens.length === 1) return { single: numTokens[0], total: numTokens[0], qty: 0 }; const max = Math.max(...numTokens); const min = Math.min(...numTokens); if (max % min === 0 && (max / min) <= 1000) return { single: min, total: max, qty: Math.round(max / min) }; return { single: numTokens[0], total: max, qty: 0 }; } function isVisible(el) { return !!(el?.offsetWidth || el?.offsetHeight || el?.getClientRects().length); } function sortVisibleLists(sortType, order) { let containers = Array.from(document.querySelectorAll('.items-cont, .itemsList, ul.items-cont, ul.itemsList')) .filter(isVisible); if (!containers.length) { containers = Array.from(document.querySelectorAll('ul')) .filter(u => /-items$/.test(u.id) && isVisible(u)); } containers.forEach(container => { const items = Array.from(container.children).filter(el => el.tagName === 'LI'); if (!items.length) return; const pairs = items.map((li, i) => { const priceElem = li.querySelector('.tt-item-price'); const parsed = parsePriceElem(priceElem); const val = sortType === 'single' ? parsed.single : parsed.total; return { li, val, index: i }; }); pairs.sort((a, b) => { if (a.val === b.val) return a.index - b.index; return order === 'desc' ? b.val - a.val : a.val - b.val; }); pairs.forEach(p => container.appendChild(p.li)); }); } function addSortButtonsOnce() { const titleBar = document.querySelector(TITLE_BAR_SELECTOR); if (!titleBar || titleBar.querySelector('.' + BUTTON_CONTAINER_CLASS)) return; const container = document.createElement('div'); container.className = BUTTON_CONTAINER_CLASS; container.style.display = 'inline-block'; container.style.marginLeft = '8px'; const btnTotal = createButton('Total Value', 'total'); const btnSingle = createButton('Single Value', 'single'); function clickHandler(clickedBtn, otherBtn) { return async function () { const orderToUse = clickedBtn.dataset.order || 'desc'; otherBtn.dataset.order = 'desc'; otherBtn.textContent = otherBtn.textContent.replace(/↓|↑/, '↓'); await loadAllItems(); sortVisibleLists(clickedBtn.dataset.sortType, orderToUse); clickedBtn.dataset.order = orderToUse === 'desc' ? 'asc' : 'desc'; clickedBtn.textContent = clickedBtn.textContent.replace(/↓|↑/, clickedBtn.dataset.order === 'desc' ? '↓' : '↑'); window.scrollTo(0, 0); }; } btnTotal.addEventListener('click', clickHandler(btnTotal, btnSingle)); btnSingle.addEventListener('click', clickHandler(btnSingle, btnTotal)); container.appendChild(btnTotal); container.appendChild(btnSingle); titleBar.appendChild(container); } const observer = new MutationObserver(addSortButtonsOnce); const targetNode = document.querySelector('#mainContainer') || document.documentElement; observer.observe(targetNode, { childList: true, subtree: true }); addSortButtonsOnce(); })();