Torn War Sidebar Assistant with Fair-Fight (Auto-refresh)

Displays war stats and fair-fight targets in Torn's sidebar. Auto-refreshes every 15s and includes manual refresh button for new war detection.

// ==UserScript==
// @name         Torn War Sidebar Assistant with Fair-Fight (Auto-refresh)
// @namespace    http://tampermonkey.net/
// @version      1.8.1
// @description  Displays war stats and fair-fight targets in Torn's sidebar. Auto-refreshes every 15s and includes manual refresh button for new war detection.
// @author       Ambidextrous
// @match        https://www.torn.com/*
// @grant        none
// ==/UserScript==

(function () {
    'use strict';

    const API_KEY_STORAGE_KEY = "torn_war_api_key";
    const FACTION_CACHE_KEY = "cached_enemy_faction_id";
    const MY_FACTION_NAME = "Monarch Optimus";
    const REFRESH_INTERVAL = 15000; // 15 seconds

    let currentEnemyFactionId = null;
    let userLevel = null;
    let userStats = null;
    let sidebar = null;

    async function getAPIKey() {
        let key = localStorage.getItem(API_KEY_STORAGE_KEY);
        if (!key) {
            key = prompt("Enter your Torn API key:");
            if (key) localStorage.setItem(API_KEY_STORAGE_KEY, key);
            else alert("API key is required.");
        }
        return key;
    }

    async function fetchUserStats(apiKey) {
        const res = await fetch(`https://api.torn.com/user/?selections=personalstats,basic&key=${apiKey}`);
        const data = await res.json();
        if (data.error) throw new Error(data.error.error);
        return {
            stats: data.personalstats,
            level: data.level
        };
    }

    async function fetchLatestEnemyFactionID(apiKey, forceRefresh = false) {
        if (!forceRefresh && currentEnemyFactionId) return currentEnemyFactionId;

        try {
            const res = await fetch(`https://api.torn.com/faction/?selections=rankedwars&key=${apiKey}`);
            const data = await res.json();
            const wars = Object.values(data.rankedwars || {}).reverse();

            const latestEnemy = wars.find(war => !war.faction_name.includes(MY_FACTION_NAME));
            if (latestEnemy?.faction) {
                localStorage.setItem(FACTION_CACHE_KEY, latestEnemy.faction);
                currentEnemyFactionId = latestEnemy.faction;
                return latestEnemy.faction;
            } else {
                throw new Error("Enemy faction not found.");
            }
        } catch (err) {
            const cached = localStorage.getItem(FACTION_CACHE_KEY);
            if (cached) {
                currentEnemyFactionId = cached;
                return cached;
            }
            const manual = prompt("Enter enemy faction ID manually:");
            if (manual) {
                localStorage.setItem(FACTION_CACHE_KEY, manual);
                currentEnemyFactionId = manual;
                return manual;
            }
            throw new Error("No faction ID available.");
        }
    }

    async function fetchEnemyFactionMembers(factionId, apiKey) {
        const res = await fetch(`https://api.torn.com/faction/${factionId}?selections=basic&key=${apiKey}`);
        const data = await res.json();
        if (data.error) throw new Error(data.error.error);
        return Object.values(data.members || {});
    }

    function isFairFight(myLevel, enemyLevel) {
        return enemyLevel >= myLevel * 0.85 && enemyLevel <= myLevel * 1.15;
    }

    function createOrUpdateSidebarPanel(fairTargets) {
        if (!sidebar) sidebar = document.querySelector('#sidebarroot');
        let panel = document.getElementById('war-tracker');
        if (panel) panel.remove();

        panel = document.createElement('div');
        panel.id = 'war-tracker';
        panel.style.padding = '10px';
        panel.style.margin = '10px';
        panel.style.backgroundColor = '#111';
        panel.style.color = 'white';
        panel.style.border = '1px solid #444';
        panel.style.borderRadius = '6px';
        panel.style.fontSize = '12px';

        const targetsHTML = fairTargets.length > 0
            ? fairTargets.map(t => `<a>${t.name} Lvl ${t.level}</a><li><a href="https://www.torn.com/profiles.php?XID=${t.ID}" target="_blank">Attack!</li></a>`).join("")
            : "<li>No fair-fight targets found.</li>";

        panel.innerHTML = `
            <h3 style="margin-top: 0;">War Stats</h3>
            <p><b># of Hits:</b> ${userStats?.attackhits ?? 'N/A'}</p><br>
            <p style="color:DodgerBlue;"><b>Respect:</b> ${userStats?.respectforfaction ?? 'N/A'}</p><br>
            <p style="color:Tomato;"><b># of Losses:</b> ${userStats?.criticalhits ?? 'N/A'}</p><br>
            <hr>
            <br><br>
            <h3>Fair-Fight Targets</h3>
            <ul>${targetsHTML}</ul>
            <button id="manual-refresh" style="margin-top:10px;padding:5px;background:#444;color:white;border:none;border-radius:4px;cursor:pointer;">🔄 Refresh Targets</button>
        `;

        panel.querySelector('#manual-refresh').addEventListener('click', async () => {
            localStorage.removeItem(FACTION_CACHE_KEY);
            currentEnemyFactionId = null;
            await refresh();
        });

        sidebar.prepend(panel);
    }

    async function refresh() {
        try {
            const apiKey = await getAPIKey();
            if (!apiKey) return;

            if (!userStats || !userLevel) {
                const { stats, level } = await fetchUserStats(apiKey);
                userStats = stats;
                userLevel = level;
            }

            const factionId = await fetchLatestEnemyFactionID(apiKey);
            const enemies = await fetchEnemyFactionMembers(factionId, apiKey);
            const fairTargets = enemies.filter(m => isFairFight(userLevel, m.level));
            fairTargets.sort((a, b) => Math.abs(userLevel - a.level) - Math.abs(userLevel - b.level));

            createOrUpdateSidebarPanel(fairTargets);
        } catch (err) {
            console.error("Failed to refresh data:", err.message);
        }
    }

    function startAutoRefresh() {
        refresh();
        setInterval(refresh, REFRESH_INTERVAL);
    }

    setTimeout(startAutoRefresh, 1);
})();