Fetches networth, formats decimals conditionally, applies high-visibility color grading, and fixes popup spam
// ==UserScript==
// @name Torn Networth Scouter
// @namespace http://tampermonkey.net/
// @version 1.5
// @description Fetches networth, formats decimals conditionally, applies high-visibility color grading, and fixes popup spam
// @author Domegid
// @license MIT
// @match https://www.torn.com/*
// @grant GM_getValue
// @grant GM_setValue
// ==/UserScript==
(function() {
'use strict';
// --- Prevent running in iframes (Fix for Torn PDA multi-prompt bug) ---
if (window.top !== window.self) return;
// --- Configuration & State ---
const CACHE_TTL = 24 * 60 * 60 * 1000; // 24 hours in milliseconds
const API_DELAY = 700; // 700ms delay to stay under 100 calls/min limit
const fetchQueue = [];
let isFetching = false;
// Retrieve or prompt for API Key
let apiKey = GM_getValue('torn_nw_apikey', '');
if (!apiKey) {
apiKey = prompt("Torn Networth Scouter:\nPlease enter your Torn API key (requires 'Public' access for profiles):");
if (apiKey) {
GM_setValue('torn_nw_apikey', apiKey.trim());
} else {
console.warn("Networth Scouter paused: No API key provided.");
}
}
// Load Cache (v2 uses raw numbers)
let nwCache = GM_getValue('torn_nw_cache_v2', {});
// --- Utility Functions ---
function getCachedNW(userId) {
const entry = nwCache[userId];
if (entry) {
const isExpired = (Date.now() - entry.timestamp > CACHE_TTL);
return { nw: entry.nw, expired: isExpired };
}
return null;
}
function setCachedNW(userId, rawNw) {
nwCache[userId] = { nw: rawNw, timestamp: Date.now() };
GM_setValue('torn_nw_cache_v2', nwCache);
}
function formatNW(num) {
if (num >= 1e9) {
// Billions or more: Show 1 decimal place
return (num / 1e9).toFixed(1) + 'B';
} else if (num >= 1e6) {
// Millions: No decimal place, rounded
return Math.round(num / 1e6) + 'M';
} else if (num >= 1e3) {
// Thousands: No decimal place, rounded
return Math.round(num / 1e3) + 'K';
}
return Math.round(num).toString();
}
function getColorForNW(num) {
// Red grading for under 500M
if (num < 100_000_000) return '#ff3333'; // < 100M: Vivid Red
if (num < 500_000_000) return '#ff6666'; // < 500M: Soft Red
// Yellow grading for 500M to 2B
if (num < 1_000_000_000) return '#ffea00'; // 500M - 1B: Bright Yellow
if (num < 2_000_000_000) return '#ccff00'; // 1B - 2B: Electric Lime/Yellow-Green
// Light/Neon Green grading for 2B and above
if (num < 4_000_000_000) return '#39ff14'; // 2B - 4B: Fluorescent Neon Green
return '#00ff00'; // 4B+: Pure Bright Lime Green
}
// --- API Queue Worker ---
async function processQueue() {
if (isFetching || fetchQueue.length === 0 || !apiKey) return;
isFetching = true;
while (fetchQueue.length > 0) {
const item = fetchQueue.shift();
let rawNw = null;
const cachedData = getCachedNW(item.userId);
if (cachedData && !cachedData.expired) {
rawNw = cachedData.nw;
}
if (rawNw === null) {
try {
const response = await fetch(`https://api.torn.com/user/${item.userId}?selections=personalstats&key=${apiKey}`);
const data = await response.json();
if (data.error) {
console.error("NW Scouter API Error:", data.error.error);
rawNw = -1; // API Error
} else if (data.personalstats && data.personalstats.networth !== undefined) {
rawNw = data.personalstats.networth;
setCachedNW(item.userId, rawNw);
} else {
rawNw = -2; // Hidden stats or N/A
setCachedNW(item.userId, rawNw);
}
} catch (error) {
console.error("NW Scouter Fetch Error:", error);
rawNw = -1;
}
await new Promise(resolve => setTimeout(resolve, API_DELAY));
}
if (rawNw !== null) {
// Reset opacity in case it was dimmed for background fetching
item.badge.style.opacity = '1';
if (rawNw === -1) {
item.badge.innerText = `[Err]`;
item.badge.style.color = '#ff3333';
} else if (rawNw === -2) {
item.badge.innerText = `[N/A]`;
item.badge.style.color = '#aaaaaa';
} else {
item.badge.innerText = `[$${formatNW(rawNw)}]`;
item.badge.style.color = getColorForNW(rawNw);
}
}
}
isFetching = false;
}
// --- UI Injection ---
function injectBadge(linkElement, initialText, color) {
const badge = document.createElement("span");
badge.className = "nw-badge";
badge.innerText = `[${initialText}]`;
const honorBar = linkElement.querySelector('.honor-text-wrap');
if (honorBar) {
badge.style.cssText = `
position: absolute;
right: 10%;
top: 50%;
transform: translateY(-50%);
color: ${color};
font-weight: bold;
font-size: 11px;
z-index: 10;
text-shadow: -1px -1px 0 #000, 1px -1px 0 #000, -1px 1px 0 #000, 1px 1px 0 #000;
`;
honorBar.style.position = 'relative';
honorBar.appendChild(badge);
} else {
badge.style.cssText = `color: ${color}; margin-left: 6px; font-weight: bold; font-size: 11px; white-space: nowrap; display: inline-flex; align-items: center;`;
linkElement.insertAdjacentElement('afterend', badge);
}
return badge;
}
// --- DOM Observer ---
const observer = new MutationObserver(() => {
const profileLinks = document.querySelectorAll('a[href*="profiles.php?XID="]:not(.nw-scouter-processed)');
profileLinks.forEach(link => {
link.classList.add('nw-scouter-processed');
// Skip top search dropdown
if (link.closest('#header-root')) return;
const linkText = link.innerText.trim();
// Skip the action links inside the mini-profile popup
if (linkText === "View profile" || linkText === "New tab") return;
if (link.querySelector('.honor-text') || linkText.length > 0) {
const idMatch = link.href.match(/XID=(\d+)/);
if (!idMatch) return;
const userId = idMatch[1];
const cachedData = getCachedNW(userId);
if (cachedData !== null) {
const rawNw = cachedData.nw;
let badge;
if (rawNw === -1) {
badge = injectBadge(link, "Err", "#ff3333");
} else if (rawNw === -2) {
badge = injectBadge(link, "N/A", "#aaaaaa");
} else {
// Add an asterisk if we are showing expired data
const displayText = cachedData.expired ? `$${formatNW(rawNw)}*` : `$${formatNW(rawNw)}`;
badge = injectBadge(link, displayText, getColorForNW(rawNw));
// Dim slightly while fetching
if (cachedData.expired) {
badge.style.opacity = '0.6';
}
}
// Queue for background refresh if expired
if (cachedData.expired) {
fetchQueue.push({ userId, badge });
processQueue();
}
} else {
const badge = injectBadge(link, "...", "#cccccc");
fetchQueue.push({ userId, badge });
processQueue();
}
}
});
});
function initObserver() {
if (document.body) {
observer.observe(document.body, { childList: true, subtree: true });
} else {
setTimeout(initObserver, 50);
}
}
initObserver();
})();