TORN: Easy Player Net Worth Display

Displays a player's net worth on their Torn profile page using API v2, with a user-configurable API key.

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

ستحتاج إلى تثبيت إضافة مثل Stylus لتثبيت هذا النمط.

ستحتاج إلى تثبيت إضافة لإدارة أنماط المستخدم لتتمكن من تثبيت هذا النمط.

ستحتاج إلى تثبيت إضافة لإدارة أنماط المستخدم لتثبيت هذا النمط.

ستحتاج إلى تثبيت إضافة لإدارة أنماط المستخدم لتثبيت هذا النمط.

(لدي بالفعل مثبت أنماط للمستخدم، دعني أقم بتثبيته!)

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

})();