Faction Target Finder

Adds a button to the top of the page that opens a live raid target from the faction list.

// ==UserScript==
// @name         Faction Target Finder
// @version      1.0.6
// @namespace    http://tampermonkey.net/
// @description  Adds a button to the top of the page that opens a live raid target from the faction list.
// @author       Omanpx [1906686], Titanic_ [2968477]
// @license      MIT
// @match        https://www.torn.com/*
// @grant        GM_xmlhttpRequest
// ==/UserScript==

(function() {
    'use strict';

    // These are faction IDs, listed in priority order
    // If no live target is found in the first one on the list, it moves on to the next
    // The current list includes Insignia + Med-Evil factions
    let facIDs = [
        50231,50157,50586,50498,
        51275,50597,51684,50994,51668,50664,50194,
        50186,52471,50103,51562,51612,50406,51313,
        50273,50375,50272,50386,50328,50401,50216,
        51145,50433,50094,52528,52442,51382,52377,
        52429,52445,52378,48264,
    ];

    let maxLevel, apiKey, attackLink, newTab, randTarget, randFaction;
    init();

    function init() {
        maxLevel = localStorage.getItem('FTF_LEVEL') || 100;
        apiKey = localStorage.getItem('FTF_API') || null;
        attackLink = localStorage.getItem('FTF_PROFILE') === 'true'; // Default to opening profile
        newTab = localStorage.getItem('FTF_NEWTAB') === 'true'; // Default to opening in new tab
        randFaction = localStorage.getItem('FTF_RAND_FACTION') === 'true';
        randTarget = localStorage.getItem('FTF_RAND_TARGET') === 'true';
    }

    // Prompt the user to enter their API key
    function promptAPIKey() {
        const key = prompt('Enter a public API key here:');
        if(key && key.trim() !== '') {
            localStorage.setItem('FTF_API', key);
            init();
        } else {
            alert('No valid API key entered!');
        }
    }

    function changeSettings() {
        const newApiKey = document.querySelector('#ftf-api').value;
        const newLevel = document.querySelector('#ftf-max-level').value;
        const newProfile = document.querySelector('#ftf-profile').checked;
        const newNewTab = document.querySelector('#ftf-newtab').checked;
        const newRandFaction = document.querySelector('#ftf-random-faction').checked;
        const newRandTarget = document.querySelector('#ftf-random-target').checked;

        localStorage.setItem('FTF_PROFILE', newProfile);
        localStorage.setItem('FTF_NEWTAB', newNewTab);
        localStorage.setItem('FTF_RAND_FACTION', newRandFaction);
        localStorage.setItem('FTF_RAND_TARGET', newRandTarget);

        if(newApiKey && newApiKey.trim() !== '') {
            localStorage.setItem('FTF_API', newApiKey);
        } else {
            alert('Invalid API key entered!');
            return;
        }

        if (newLevel >= 0 && newLevel <= 100) {
            localStorage.setItem('FTF_LEVEL', newLevel);
        } else {
            alert('Invalid max level, please enter a value between 0 and 100!');
            return;
        }

        init();
        alert("Settings saved!");
    }

    // Function to process each URL
    function processUrls(index = 0, checked = new Set()) {
        if (!apiKey) promptAPIKey();

        if (checked.size >= facIDs.length) {
            alert("No players met the conditions (or API key is invalid).");
            return;
        }

        if (randFaction) {
            do {
                index = Math.floor(Math.random() * facIDs.length);
            } while (checked.has(index));
        }

        checked.add(index);

        const url = `https://api.torn.com/faction/${facIDs[index]}?selections=basic&timestamp=${Date.now()}&key=${apiKey}`;
        console.log(url);
        //console.log(`[${Date.now()}] Checking faction ID: ${facIDs[index]}`);

        GM_xmlhttpRequest({
            method: "GET",
            url,
            onload(response) {
                const roster = JSON.parse(response.responseText);
                const target = checkCondition(roster);

                if(target) {
                    let profileLink;

                    if(attackLink) profileLink = `https://www.torn.com/loader.php?sid=attack&user2ID=${target}`;
                    else profileLink = `https://www.torn.com/profiles.php?XID=${target}`;


                    if(newTab) {
                        window.open(profileLink, '_blank');
                    } else {
                        window.location.href = profileLink
                    }
                } else {
                    processUrls(index + 1);
                }
            },
            onerror() {
                console.log(`Error loading URL: ${url}`);
                processUrls(index + 1, checked);
            }
        });
    }

    // Condition to check on each response
    function checkCondition(roster) {
        if("error" in roster) {
            console.log("Failed fetching faction roster, reason:", roster);
            return null;
        }

        const targets = Object.keys(roster.members).filter(userId => {
            const member = roster.members[userId];
            return member.level <= maxLevel && member.status.state === "Okay" && member.days_in_faction >= 15;
        })

        if(targets.length === 0) return null;

        if(randTarget) {
            const index = Math.floor(Math.random() * targets.length);
            return targets[index];
        }

        return targets[0];
    }

    // Create buttons
    const raidBtn = createButton('Raid', 'ftf-btn', () => processUrls());
    const settBtn = createButton('Settings', 'ftf-settings', toggleSettings);

    const settDiv = createDiv('ftf-settings-container');
    settDiv.append(settBtn);
    const container = createDiv('ftf-container');
    container.append(raidBtn, settDiv);

    document.body.appendChild(container);

    function toggleSettings() {
        const container = document.getElementsByClassName("ftf-settings-container")[0]
        if(!container.classList.contains("ftf-settings-container-expanded")) {
            container.classList.toggle("ftf-settings-container-expanded");
            document.querySelector(".ftf-settings").textContent = "Close Settings";

            const appendElements = (parent, ...elements) => {
                const tempDiv = document.createElement('div');
                tempDiv.classList.add('temp-div');
                elements.forEach(el => tempDiv.append(el));
                parent.append(tempDiv);
            };

            // Create and append settings
            const { input: apiKeyInput, label: apiKeyLabel } = createInput('ftf-api', "API Key (Public)", apiKey, "text");
            appendElements(container, apiKeyLabel, apiKeyInput);

            const { input: maxInput, label: maxLabel } = createInput('ftf-max-level', "Max Level (0-100)", maxLevel, "number");
            appendElements(container, maxLabel, maxInput);

            const { checkbox: profileCheckbox, label: profileLabel } = createCheckbox('ftf-profile', "Open directly to attack page?", attackLink);
            appendElements(container, profileLabel, profileCheckbox);

            const { checkbox: tabCheckbox, label: tabLabel } = createCheckbox('ftf-newtab', "Open in new tab?", newTab);
            appendElements(container, tabLabel, tabCheckbox);

            const { checkbox: randomFCheckbox, label: randomFLabel } = createCheckbox('ftf-random-faction', "Switch to random faction?", randFaction);
            appendElements(container, randomFLabel, randomFCheckbox);

            const { checkbox: randomTCheckbox, label: randomTLabel } = createCheckbox('ftf-random-target', "Switch to random targets?", randTarget);
            appendElements(container, randomTLabel, randomTCheckbox);

            const saveBtn = createButton('Save', 'ftf-save', changeSettings);
            container.append(saveBtn);
        } else {
            container.classList.toggle("ftf-settings-container-expanded");
            document.querySelector(".ftf-settings").textContent = "Settings";

            while(container.children.length > 1) {
                container.removeChild(container.lastChild);
            }
            // remove the rest of the stuff that is added
        }
    }

    function addGlobalStyle(css) {
        var head, style;
        head = document.getElementsByTagName('head')[0];
        if (!head) { return; }
        style = document.createElement('style');
        style.type = 'text/css';
        style.innerHTML = css;
        head.appendChild(style);
    }

    addGlobalStyle(`
        .ftf-btn, .ftf-save {
            background-color: green;
            color: white;
            border: 1px solid white;
            padding: 3px;
            border-radius: 3px;
            cursor: pointer;
        }
        .ftf-settings {
            padding: 3px;
            cursor: pointer;
            border: 1px solid white;
            border-radius: 3px;
        }
        .ftf-container {
            align-items: end;
            display: flex;
            flex-direction: column;
            gap: 3px;
            position: fixed;
            top: 30%;
            right: 0;
            z-index: 9999;
            background-color: transparent;
        }
        .ftf-settings-container {
           color: black;
           display: flex;
           flex-direction: column;
           align-items: flex-start;
           background-color: orange;
           border-radius: 3px;
        }
        .ftf-settings-container-expanded {
           width: 200px;
           height: fit-content;
           border: 1px solid white;
           align-items: center;
           justify-content: flex-start;
           gap: 2px;
        }
        #ftf-api, #ftf-max-level { width:100px ; text-align: center }
        .temp-div { display:flex; }
    `);

    function createButton(text, className, onClick) {
        const button = document.createElement('button');
        button.className = className;
        button.textContent = text;
        button.addEventListener('click', onClick);

        return button;
    }

    function createDiv(className) {
        const div = document.createElement('div');
        div.className = className;

        return div;
    }

    function createInput(id, text, value, type) {
        const input = document.createElement('input');
        input.type = type;
        input.id = id;
        input.value = value;

        const label = document.createElement('label');
        label.htmlFor = id;
        label.textContent = text;

        return {
            input,
            label
        };
    }

    function createCheckbox(id, text, value) {
        const checkbox = document.createElement('input');
        checkbox.type = 'checkbox';
        checkbox.id = id;
        checkbox.checked = value;

        const label = document.createElement('label');
        label.htmlFor = id;
        label.textContent = text;

        return {
            checkbox,
            label
        };
    }

})();