// ==UserScript==
// @name TORN: Easy Player Net Worth Display
// @namespace http://tampermonkey.net/
// @version 3.1
// @description Displays a player's net worth on their Torn profile page using API v2, with a user-configurable API key.
// @author JohnBattlefield
// @match https://www.torn.com/profiles.php*
// @grant GM_xmlhttpRequest
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_deleteValue
// @license MIT License
// ==/UserScript==
(function() {
'use strict';
// Tampermonkey storage key for the API key
const API_KEY_STORAGE_KEY = 'tornNetWorthApiKey';
// Function to get a URL parameter by name
function getUrlParameter(name) {
name = name.replace(/[\[]/, '\\[').replace(/[\]]/, '\\]');
const regex = new RegExp('[\\?&]' + name + '=([^&#]*)');
const results = regex.exec(location.search);
return results === null ? '' : decodeURIComponent(results[1].replace(/\+/g, ' '));
}
// Function to simplify a number and add a suffix (k, M, B, T)
function formatNumberToSimplified(num) {
if (num >= 1000000000000) {
return `$${(num / 1000000000000).toFixed(2)}T`;
} else if (num >= 1000000000) {
return `$${(num / 1000000000).toFixed(2)}B`;
} else if (num >= 1000000) {
return `$${(num / 1000000).toFixed(0)}M`;
} else if (num >= 1000) {
return `$${(num / 1000).toFixed(0)}k`;
}
// For numbers less than a thousand, return the formatted number
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD',
minimumFractionDigits: 0,
maximumFractionDigits: 0
}).format(num);
}
// Function to get a color based on the net worth value
function getNetWorthBadgeColor(num) {
if (num >= 1000000000000) {
return '#6a1b9a'; // Trillions (Darker Purple)
} else if (num >= 100000000000) {
return '#993333'; // 100B - 1T (Maroon)
} else if (num >= 10000000000) {
return '#c06015'; // 10B - 100B (Darker Orange)
} else if (num >= 1000000000) {
return '#008080'; // 1B - 10B (Teal)
} else if (num >= 1000000) {
return '#388e3c'; // Millions (Darker Green)
} else {
return '#3a556b'; // Thousands or less
}
}
// Function to create and insert the net worth element
function displayNetWorth(netWorth) {
const playerNameElement = document.querySelector('h4#skip-to-content');
if (!playerNameElement) {
console.warn("Torn Net Worth Script: Could not find player name element to insert net worth.");
return;
}
if (document.getElementById('torn-net-worth')) {
console.log("Torn Net Worth Script: Net worth element already exists, skipping insertion.");
return;
}
const netWorthBadgeElement = document.createElement('span');
netWorthBadgeElement.id = 'torn-net-worth';
netWorthBadgeElement.style.marginLeft = '20px'; // Increased space
netWorthBadgeElement.style.fontSize = '14px';
netWorthBadgeElement.style.cursor = 'pointer';
netWorthBadgeElement.style.fontWeight = 'bold';
// Add a click event listener to show the API key input form
netWorthBadgeElement.addEventListener('click', () => {
showApiKeyInput();
});
// Create the combined text for the badge
const simplifiedNetWorthText = formatNumberToSimplified(netWorth);
netWorthBadgeElement.textContent = `Net Worth: ${simplifiedNetWorthText}`;
// Apply badge styling
netWorthBadgeElement.style.backgroundColor = getNetWorthBadgeColor(netWorth);
netWorthBadgeElement.style.color = 'white';
netWorthBadgeElement.style.padding = '4px 8px';
netWorthBadgeElement.style.borderRadius = '4px'; // Less round edges
netWorthBadgeElement.style.fontWeight = 'bold';
netWorthBadgeElement.style.display = 'inline-block';
playerNameElement.appendChild(netWorthBadgeElement);
console.log("Torn Net Worth Script: Net worth element successfully inserted.");
}
// Helper function to display messages in the UI
function showMessage(message, isError = false) {
let messageBox = document.getElementById('api-key-message');
if (!messageBox) {
const formContainer = document.getElementById('api-key-form');
if (formContainer) {
messageBox = document.createElement('p');
messageBox.id = 'api-key-message';
messageBox.style.marginTop = '10px';
formContainer.appendChild(messageBox);
} else {
return;
}
}
messageBox.textContent = message;
messageBox.style.color = isError ? 'red' : '#38a169';
}
// Function to show the API key input form
function showApiKeyInput() {
const playerNameElement = document.querySelector('h4#skip-to-content');
if (!playerNameElement) {
console.warn("Torn Net Worth Script: Could not find player name element to insert API key form.");
return;
}
if (document.getElementById('api-key-form')) {
return;
}
const existingNetWorthElement = document.getElementById('torn-net-worth');
if (existingNetWorthElement) {
existingNetWorthElement.remove();
}
const formContainer = document.createElement('div');
formContainer.id = 'api-key-form';
formContainer.style.marginTop = '10px';
formContainer.style.fontSize = '12px';
formContainer.style.backgroundColor = 'var(--c-background-secondary, #333)';
formContainer.style.color = 'var(--c-text-primary, #fff)';
formContainer.style.border = '1px solid var(--c-border-primary, #555)';
formContainer.style.padding = '10px';
formContainer.style.borderRadius = '5px';
formContainer.style.display = 'flex';
formContainer.style.flexDirection = 'column';
formContainer.style.gap = '15px'; // Increased gap for better mobile spacing
formContainer.innerHTML = `
<div>
<p style="font-weight: bold; margin-bottom: 10px;">API Key Required</p>
<p style="margin: 0 0 10px 0;">
Please enter your Torn API key.
</p>
<p style="margin: 0;">
<a href="https://www.torn.com/preferences.php#tab=api" target="_blank" style="color: #38a169; text-decoration: underline;">Get your API key here.</a>
</p>
</div>
<div style="display: flex; gap: 5px; flex-wrap: wrap;">
<input type="text" id="api-key-input" placeholder="Enter API Key" style="flex-grow: 1; padding: 5px; background-color: var(--c-background-primary, #444); color: var(--c-text-primary, #fff); border: 1px solid var(--c-border-primary, #666); border-radius: 3px; min-width: 150px;">
<button id="save-key-btn" style="padding: 5px 10px; background-color: #38a169; color: white; border: none; border-radius: 3px; cursor: pointer;">Save</button>
<button id="clear-key-btn" style="padding: 5px 10px; background-color: #d9534f; color: white; border: none; border-radius: 3px; cursor: pointer;">Clear</button>
</div>
`;
playerNameElement.parentNode.insertBefore(formContainer, playerNameElement.nextSibling);
document.getElementById('save-key-btn').addEventListener('click', () => {
const key = document.getElementById('api-key-input').value;
if (key) {
GM_setValue(API_KEY_STORAGE_KEY, key);
showMessage('API key saved! Reloading page to apply changes.');
setTimeout(() => location.reload(), 1000);
} else {
showMessage('Please enter a valid API key.', true);
}
});
document.getElementById('clear-key-btn').addEventListener('click', () => {
GM_deleteValue(API_KEY_STORAGE_KEY);
showMessage('API key cleared! Reloading page.');
setTimeout(() => location.reload(), 1000);
});
}
// Main function to fetch the data and display it
async function fetchAndDisplayNetWorth() {
const oldNetWorthElement = document.getElementById('torn-net-worth');
if (oldNetWorthElement) {
oldNetWorthElement.remove();
}
const playerId = getUrlParameter('XID');
const apiKey = await GM_getValue(API_KEY_STORAGE_KEY);
if (!apiKey) {
console.log("Torn Net Worth Script: No API key found. Showing input form.");
showApiKeyInput();
return;
}
if (playerId && apiKey) {
console.log(`Torn Net Worth Script: Initiating API request for Player ID: ${playerId} using V2 API`);
const apiUrl = `https://api.torn.com/v2/user/${playerId}/personalstats?cat=networth&key=${apiKey}`;
GM_xmlhttpRequest({
method: "GET",
url: apiUrl,
onload: function(response) {
console.log("Torn Net Worth Script: API response received. Status:", response.status);
try {
const data = JSON.parse(response.responseText);
// Check for API errors first
if (data && data.error) {
console.error("Torn Net Worth Script: API returned an error:", data.error);
// If the API key is invalid, delete it and prompt for a new one.
if (data.error.code === 2 || data.error.code === 3 || data.error.code === 10) {
GM_deleteValue(API_KEY_STORAGE_KEY);
showApiKeyInput();
} else {
// For other errors, just display 0 net worth to avoid the loop
displayNetWorth(0);
}
return;
}
if (data && data.personalstats && data.personalstats.networth) {
// Check if 'total' property exists. It might be 0 or negative, which is a valid value.
const totalNetWorth = data.personalstats.networth.total !== undefined ? data.personalstats.networth.total : 0;
console.log(`Torn Net Worth Script: Net worth data found. Total: ${totalNetWorth}`);
displayNetWorth(totalNetWorth);
} else {
// If data structure is unexpected, display 0 net worth to avoid the API key loop.
console.error("Torn Net Worth Script: Could not retrieve net worth data. Full response:", data);
displayNetWorth(0);
}
} catch (e) {
console.error("Torn Net Worth Script: Failed to parse JSON response. Response text:", response.responseText, "Error:", e);
GM_deleteValue(API_KEY_STORAGE_KEY);
showApiKeyInput();
}
},
onerror: function(response) {
console.error("Torn Net Worth Script: GM_xmlhttpRequest failed. Status:", response.status, "Status Text:", response.statusText, "Response Text:", response.responseText);
GM_deleteValue(API_KEY_STORAGE_KEY);
showApiKeyInput();
}
});
}
}
const observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
if (mutation.type === 'childList' && mutation.addedNodes.length > 0) {
for (const node of mutation.addedNodes) {
if (node.querySelector && node.querySelector('h4#skip-to-content')) {
console.log("Torn Net Worth Script: Profile header element detected, running script.");
fetchAndDisplayNetWorth();
return;
}
}
}
});
});
observer.observe(document.body, { childList: true, subtree: true });
console.log("Torn Net Worth Script: Initial script run initiated.");
fetchAndDisplayNetWorth();
})();