War End

Adds a toggle-able side bar to estimate War End for a given faction.

// ==UserScript==
// @name         War End
// @version      Alpha1.4
// @namespace    https://greasyfork.org/
// @description  Adds a toggle-able side bar to estimate War End for a given faction.
// @author       Gravity0000
// @supportURL   https://www.torn.com/profiles.php?XID=2131364
// @license      MIT
// @match        https://www.torn.com/*
// @grant        GM_xmlhttpRequest
// @grant        GM_addStyle
// @connect      api.torn.com
// ==/UserScript==

(function () {
    'use strict';

    const styleTag = document.createElement('style');
    document.head.appendChild(styleTag);

    const isDarkMode = document.body.classList.contains('dark-mode');
    let placeholderColor = isDarkMode ? '#bbbbbb' : '#555555';

    function updatePlaceholderStyle() {
        styleTag.textContent = `
            .inputBox::placeholder,
            .apiBox::placeholder,
            .leadBox::placeholder {
                color: ${placeholderColor} !important;
            }
        `;
    }

    function applyThemeColors(isDark) {
        const textColor = isDark ? 'white' : 'black';
        const bgColor = isDark ? 'black' : 'white';
        const containerBg = isDark ? '#A9A9A9' : '#D3D3D3';
        const buttonBg = isDark ? '#009407' : '#98f59b';
        const borderColor = isDark ? 'white' : 'black';

        [apiBox, infoBox, textBox, leadBox, toggleButton, updateButton].forEach(el => {
            el.style.color = textColor;
        });
        [apiBox, infoBox, textBox, leadBox].forEach(el => {
            el.style.backgroundColor = bgColor;
        });
        [toggleButton, updateButton].forEach(el => {
            el.style.border = `1px solid ${borderColor}`;
            el.style.backgroundColor = buttonBg;
        });
        container.style.backgroundColor = containerBg;
        placeholderColor = isDark ? '#FFFFFF' : '#000000';
        updatePlaceholderStyle();
    }

    const container = document.createElement('div');
    container.id = 'myScriptContainer';
    container.style.display = 'none';
    document.body.appendChild(container);

    const apiBox = document.createElement('input');
    apiBox.type = 'text';
    apiBox.id = 'myAPIBox';
    apiBox.placeholder = 'Enter API Here';
    container.appendChild(apiBox);

    const infoBox = document.createElement('div');
    infoBox.id = 'myInfoBox';
    infoBox.innerHTML = 'Initial Info';
    container.appendChild(infoBox);

    const textBox = document.createElement('input');
    textBox.type = 'number';
    textBox.id = 'myTextBox';
    textBox.placeholder = 'Faction ID or Blank.';
    container.appendChild(textBox);

    const leadBox = document.createElement('input');
    leadBox.type = 'number';
    leadBox.id = 'myLeadBox';
    leadBox.placeholder = 'Est. Lead or Blank.';
    container.appendChild(leadBox);

    [textBox, leadBox].forEach(box => {
        box.addEventListener('keypress', event => {
            const key = event.keyCode || event.which;
            if (!((key >= 48 && key <= 57) || key === 8)) event.preventDefault();
        });
    });

    const toggleButton = document.createElement('button');
    toggleButton.innerText = 'WarEnd';
    toggleButton.id = 'myToggleButton';
    Object.assign(toggleButton.style, {
        position: 'fixed',
        top: '65%',
        right: '0%',
        zIndex: '9999',
        cursor: 'pointer'
    });
    document.body.appendChild(toggleButton);

    toggleButton.addEventListener('click', () => {
        container.style.display = container.style.display === 'none' ? '' : 'none';
    });

    const savedAPI = localStorage.getItem('savedAPIValue');
    if (savedAPI) apiBox.value = savedAPI;
    const savedFaction = localStorage.getItem('savedFactionValue');
    if (savedFaction) textBox.value = savedFaction;
    const savedLead = localStorage.getItem('savedLeadValue');
    if (savedLead) leadBox.value = savedLead;
    const updateButton = document.createElement('button');
    updateButton.id = 'myUpdateButton';
    updateButton.textContent = 'Update';
    container.appendChild(updateButton);

    [textBox, apiBox, leadBox].forEach((el, i) => el.classList.add(['inputBox', 'apiBox', 'leadBox'][i]));
    applyThemeColors(isDarkMode);

    const observer = new MutationObserver(() => {
        applyThemeColors(document.body.classList.contains('dark-mode'));
    });
    observer.observe(document.body, { attributes: true, attributeFilter: ['class'] });

    updateButton.addEventListener('click', () => {
        const faction_id = textBox.value;
        const apiKey = apiBox.value;
        localStorage.setItem('savedAPIValue', apiKey);
        localStorage.setItem('savedFactionValue', faction_id);
        localStorage.setItem('savedLeadValue', leadBox.value);

        GM_xmlhttpRequest({
            method: 'GET',
            url: `https://api.torn.com/v2/faction/${faction_id}/wars/?selections=basic&key=${apiKey}&comment=warend`,
            onload: response => {
                if (response.status !== 200) {
                    infoBox.innerHTML = 'ERROR';
                    return;
                }

                const data = JSON.parse(response.responseText);
                if (data.error?.code === 2) {
                    infoBox.innerHTML = 'Invalid API Key';
                    return;
                }
                if (!data.wars || !data.wars.ranked) {
                    infoBox.innerHTML = 'Invalid Waring Faction ID';
                    return;
                }

                const start = data.wars.ranked.start;
                const now = Date.now();
                const [f1, f2] = data.wars.ranked.factions;
                const diff = Math.abs(f1.score - f2.score);
                const target = data.wars.ranked.target;
                const passedMs = Math.abs(now - (start * 1000));
                const hoursPassed = Math.floor(passedMs / (1000 * 60 * 60));
                let onePercent = hoursPassed < 24 ? target / 100 : Math.round(target / (100 - (hoursPassed - 24)));

                let newLead = parseInt(leadBox.value, 10);
                let remaining = (leadBox.value && newLead > 0 && newLead <= target) ? target - newLead : target - diff;

                if (leadBox.value && (newLead > target || newLead <= 0)) {
                    infoBox.innerHTML = `Error Max lead = ${target}`;
                    return;
                }

                let hrsLeft = 0;
                while (remaining > 0) {
                    remaining -= onePercent;
                    hrsLeft++;
                }

                let minLeft = 0;
                if (!data.wars.ranked.end) {
                    minLeft = Math.floor(60 - ((passedMs / (1000 * 60)) - (hoursPassed * 60)));
                }

                if (hrsLeft < 24) {
                    infoBox.innerHTML = `${hrsLeft}H ${minLeft}M<br>Until WarEnd.`;
                } else {
                    const daysLeft = Math.floor(hrsLeft / 24);
                    hrsLeft %= 24;
                    infoBox.innerHTML = `${daysLeft}D ${hrsLeft}H ${minLeft}M<br>Until WarEnd.`;
                }
            },
            onerror: err => {
                console.error('Network Error:', err);
                infoBox.innerHTML = 'NETWORK ERROR';
            }
        });
    });

    GM_addStyle(`
        #myScriptContainer {
            position: fixed;
            width: 135px;
            top: 70%;
            right: 0%;
            background-color: #f0f0f0;
            border: 1px solid #ccc;
            padding: 10px;
            z-index: 9999;
            display: flex;
            flex-direction: column;
            gap: 5px;
        }
        #myAPIBox, #myTextBox, #myLeadBox {
            padding: 5px;
            border: 1px solid #ddd;
        }
        #myInfoBox {
            color: black;
            padding: 5px;
            text-align: center;
            background-color: #f2f2f2;
        }
        #myUpdateButton, #myToggleButton {
            padding: 5px 10px;
            cursor: pointer;
        }
    `);
})();