// ==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();
})();