Torn Faction War Status Banner

Display faction war status on Torn pages with flashing chain counts and bonus warnings

// ==UserScript==
// @name         Torn Faction War Status Banner
// @namespace    http://tampermonkey.net/
// @version      2.13
// @description  Display faction war status on Torn pages with flashing chain counts and bonus warnings
// @match        https://*.torn.com/*
// @grant        GM_xmlhttpRequest
// @grant        GM_addStyle
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_deleteValue
// @license      GNU GPLv3
// ==/UserScript==

(function() {
    'use strict';

    let FRIENDLY_FACTION_ID = null;
    const UPDATE_INTERVAL = 2000; // 2 seconds

    // Debug configuration
    const DEBUG_CONFIG = {
        enabled: false,
        friendlyFactionName: "Our Faction"
    };

    // Configuration for button and prompt visibility
    const CONFIG = {
        clearButtonPages: [
            'https://www.torn.com/preferences.php'
        ],
        promptPages: [
            'https://www.torn.com/factions.php',
        ]
    };

    GM_addStyle(`
        #faction-war-status {
            background-color: #333;
            color: #fff;
            padding: 5px;
            font-size: 12px;
            text-align: center;
            box-sizing: border-box;
            white-space: nowrap;
            overflow: hidden;
            text-overflow: ellipsis;
            z-index: 1000;
            line-height: 1.2;
            width: 100%;
            margin-top: 5px;
            transition: opacity 3.0s ease-in-out;
        }
        .flash-chain {
            animation: flash 1s infinite;
            font-weight: bold;
        }
        @keyframes flash {
            0% { color: #00ff00; }
            50% { color: #ff0000; }
            100% { color: #00ff00; }
        }
        #clear-api-key-button {
            position: fixed;
            bottom: 75px;
            right: 10px;
            z-index: 10000;
            background-color: #f44336;
            color: white;
            border: none;
            padding: 10px 20px;
            text-align: center;
            text-decoration: none;
            display: inline-block;
            font-size: 16px;
            margin: 4px 2px;
            cursor: pointer;
        }
    `);

    function debug(message, data) {
        if (DEBUG_CONFIG.enabled) {
            console.log(`[Faction War Status] ${message}`, data || '');
        }
    }

    function createStatusBar() {
        debug('Creating status bar');
        const statusBar = document.createElement('div');
        statusBar.id = 'faction-war-status';
        statusBar.textContent = 'Loading faction war status...';
        return statusBar;
    }

    function insertStatusBar() {
        debug('Inserting status bar');
        let statusBar = document.getElementById('faction-war-status');
        if (!statusBar) {
            statusBar = createStatusBar();
            const mainContainer = document.querySelector('.content-wrapper');
            if (mainContainer) {
                mainContainer.insertAdjacentElement('afterbegin', statusBar);
                debug('Status bar inserted at the top of main content');
            } else {
                debug('Main content container not found, inserting at body');
                document.body.insertAdjacentElement('afterbegin', statusBar);
            }
        }
        statusBar.style.display = 'block';
        statusBar.classList.remove('fade-out');
    }

    function formatDate(timestamp) {
        const date = new Date(timestamp * 1000);
        const day = date.getDate();
        const month = date.toLocaleString('default', { month: 'short' });
        let hours = date.getHours();
        const minutes = date.getMinutes().toString().padStart(2, '0');
        const ampm = hours >= 12 ? 'PM' : 'AM';
        hours = hours % 12;
        hours = hours ? hours : 12;
        return `${day} ${month} ${hours}:${minutes}${ampm}`;
    }

    function shouldFlashChain(chain) {
        const ranges = [[45, 49], [90, 99], [240, 249], [480, 499], [980, 999], [2480, 2499], [4980, 4999], [9980, 9999], [24980, 24999], [49980, 49999], [99980, 99999]];
        return ranges.some(([min, max]) => chain >= min && chain <= max);
    }

    function fetchUserFactionId(apiKey) {
        debug('Fetching user faction ID');
        return new Promise((resolve, reject) => {
            GM_xmlhttpRequest({
                method: 'GET',
                url: `https://api.torn.com/user/?selections=profile&key=${apiKey}`,
                onload: function(response) {
                    if (response.status === 200) {
                        try {
                            const data = JSON.parse(response.responseText);
                            if (data.faction && data.faction.faction_id) {
                                debug('User faction ID fetched successfully', data.faction.faction_id);
                                resolve(data.faction.faction_id.toString());
                            } else {
                                debug('Faction ID not found in API response', data);
                                reject('Faction ID not found in API response');
                            }
                        } catch (error) {
                            debug('Error parsing API response', error);
                            reject('Error parsing API response: ' + error);
                        }
                    } else {
                        debug('Failed to fetch user data', response);
                        reject('Failed to fetch user data, status: ' + response.status);
                    }
                },
                onerror: function(error) {
                    debug('Error making API request', error);
                    reject('Error making API request: ' + error);
                }
            });
        });
    }

    function isOnPage(pageList) {
        return pageList.some(url => window.location.href.startsWith(url));
    }

    function promptForApiKey() {
        if (!isOnPage(CONFIG.promptPages)) {
            debug('Not on a prompt page, skipping API key prompt');
            return;
        }

        debug('Prompting for API key');
        const apiKey = prompt('Torn War Banner: Please enter your PUBLIC API key:');
        if (apiKey) {
            GM_setValue('apiKey', apiKey);
            fetchUserFactionId(apiKey)
                .then(factionId => {
                    FRIENDLY_FACTION_ID = factionId;
                    debug(`Friendly faction ID set to: ${FRIENDLY_FACTION_ID}`);
                    insertStatusBar();
                    updateStatus();
                    setInterval(updateStatus, UPDATE_INTERVAL);
                })
                .catch(error => {
                    debug('Error fetching user faction ID:', error);
                    const statusBar = document.getElementById('faction-war-status') || createStatusBar();
                    statusBar.textContent = 'Error: Unable to fetch user faction data';
                });
        } else {
            debug('No API key provided');
        }
    }

    function updateStatusDisplay(data) {
        debug('Updating status display', data);
        const statusBar = document.getElementById('faction-war-status');
        if (!statusBar) {
            debug('Status bar element not found');
            return;
        }

        if (data.peace) {
            debug('Faction is in peace mode', data.peace);
            if (DEBUG_CONFIG.enabled) {
                statusBar.textContent = `Faction is not at war (Debug)`;
                statusBar.style.opacity = '1';
                debug('Debug mode is enabled, showing banner');
            } else {
                statusBar.textContent = 'Faction is not at war';
                statusBar.style.opacity = '0';
                debug('Debug mode is disabled, fading out text');
            }
            return;
        }

        // If not in peace mode, show the banner and update with war status
        statusBar.style.opacity = '1';
        debug('Faction is not in peace mode, updating war status');

        if (!data.ranked_wars || Object.keys(data.ranked_wars).length === 0) {
            statusBar.textContent = 'No upcoming faction war';
            statusBar.style.opacity = '0';
            return;
        }

        let statusText = '';
        const now = Math.floor(Date.now() / 1000);
        const war = data.ranked_wars[Object.keys(data.ranked_wars)[0]];
        const warStarted = Object.values(war.factions).some(faction => faction.score > 0);

        debug('War data:', war);
        debug('Current time:', now);
        debug('War start time:', war.war.start);
        debug('War started:', warStarted);

        if (warStarted) {
            statusText = `Target: ${war.war.target} | `;
            for (const factionId in war.factions) {
                const faction = war.factions[factionId];
                const isFriendly = faction.name === DEBUG_CONFIG.friendlyFactionName || factionId === FRIENDLY_FACTION_ID;
                const factionColor = isFriendly ? '#00ff00' : '#ff0000';
                const factionName = faction.name;
                let chainText = `Chain ${faction.chain}`;
                if (isFriendly && shouldFlashChain(faction.chain)) {
                    chainText = `<span class="flash-chain">${chainText}</span>`;
                }
                statusText += `<span style="color: ${factionColor};">${factionName}: Score ${faction.score}, ${chainText}</span> | `;
            }
            statusText = statusText.trim().slice(0, -2);
        } else {
            statusText = `Start: ${formatDate(war.war.start)} LT | `;
            const factions = Object.values(war.factions);
            const friendlyFaction = factions.find(f => f.id === FRIENDLY_FACTION_ID);
            const enemyFaction = factions.find(f => f !== friendlyFaction);

            // Use faction tag for friendly faction if available
            const friendlyName = data.tag || friendlyFaction.name;

            // Ensure enemy faction is first (red), then friendly faction (green)
            statusText += `<span style="color: #ff0000;">${enemyFaction.name}</span> vs <span style="color: #00ff00;">${friendlyName}</span>`;
        }
        statusBar.innerHTML = statusText;
        debug('Status display updated', statusText);
    }

    function updateStatus() {
        debug('Updating status');
        if (!FRIENDLY_FACTION_ID) {
            debug('Friendly faction ID not set');
            return;
        }

        insertStatusBar();
        const apiKey = GM_getValue('apiKey', null);
        if (!apiKey) {
            debug('API key is not available.');
            promptForApiKey();
            return;
        }

        const API_URL = `https://api.torn.com/faction/${FRIENDLY_FACTION_ID}?selections=basic&key=${apiKey}`;

        GM_xmlhttpRequest({
            method: 'GET',
            url: API_URL,
            onload: function(response) {
                debug('API response received', response);
                if (response.status === 200) {
                    try {
                        const data = JSON.parse(response.responseText);
                        debug('Parsed API data:', data);

                        // Determine peace mode
                        let isPeaceMode = true;
                        if (data.ranked_wars && Object.keys(data.ranked_wars).length > 0) {
                            const war = data.ranked_wars[Object.keys(data.ranked_wars)[0]];
                            isPeaceMode = war.war.end !== 0;

                            if (!isPeaceMode) {
                                // Check if any faction has a score > 0
                                for (const factionId in war.factions) {
                                    if (war.factions[factionId].score > 0) {
                                        isPeaceMode = false;
                                        break;
                                    }
                                }
                            }
                        }

                        data.peace = isPeaceMode;
                        debug('Calculated peace status:', data.peace);

                        updateStatusDisplay(data);
                    } catch (error) {
                        debug('Error parsing API response:', error);
                        const statusBar = document.getElementById('faction-war-status');
                        if (statusBar) {
                            statusBar.textContent = 'Error parsing faction war data';
                        }
                    }
                } else {
                    debug('Failed to fetch faction data, status:', response.status);
                    const statusBar = document.getElementById('faction-war-status');
                    if (statusBar) {
                        statusBar.textContent = 'Failed to fetch faction war data';
                    }
                }
            },
            onerror: function(error) {
                debug('Error making API request:', error);
                const statusBar = document.getElementById('faction-war-status');
                if (statusBar) {
                    statusBar.textContent = 'Error fetching faction war data';
                }
            }
        });
    }

    function clearApiKey() {
        GM_deleteValue('apiKey');
        alert('API key cleared successfully. You can set a new API key by visiting a faction page.');
        location.reload();
    }

    function addClearApiKeyButton() {
        if (isOnPage(CONFIG.clearButtonPages)) {
            const button = document.createElement('button');
            button.id = 'clear-api-key-button';
            button.textContent = 'Clear War Banner API Key';
            button.style.position = 'fixed';
            button.style.bottom = '75px';
            button.style.right = '10px';
            button.style.zIndex = '10000';
            button.style.backgroundColor = '#f44336';
            button.style.color = 'white';
            button.style.border = 'none';
            button.style.padding = '10px 20px';
            button.style.cursor = 'pointer';
            button.addEventListener('click', clearApiKey);
            document.body.appendChild(button);
            debug('Clear API Key button added to page');
        }
    }


    // Run the initialization when the document is ready
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', init);
    } else {
        init();
    }

    // Immediate debug output
    debug('Script loaded and executed');

    function init() {
        debug('Initializing script');
        const apiKey = GM_getValue('apiKey', null);

        if (apiKey) {
            debug('API key found, fetching user faction ID');
            fetchUserFactionId(apiKey)
                .then(factionId => {
                FRIENDLY_FACTION_ID = factionId;
                debug(`Friendly faction ID set to: ${FRIENDLY_FACTION_ID}`);
                insertStatusBar();
                updateStatus();
                setInterval(updateStatus, UPDATE_INTERVAL);
            })
                .catch(error => {
                debug('Error fetching user faction ID:', error);
                if (isOnPage(CONFIG.promptPages)) {
                    const statusBar = document.getElementById('faction-war-status') || createStatusBar();
                    statusBar.textContent = 'Error: Unable to fetch user faction data';
                    promptForApiKey();
                }
            });
        } else if (isOnPage(CONFIG.promptPages)) {
            debug('No API key found and on a prompt page, prompting user');
            promptForApiKey();
        }

        // Add the Clear API Key button
        addClearApiKeyButton();
    }

})();