Torn Spy Integration

Adds Torn Spy integration to Torn.

// ==UserScript==
// @name         Torn Spy Integration
// @namespace    tornspy-integration
// @version      0.6
// @description  Adds Torn Spy integration to Torn.
// @author       Neodork
// @match        https://www.torn.com/*
// @connect      www.tornspy.com
// @grant        GM_xmlhttpRequest
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_deleteValue
// @grant        GM_listValues
// @grant        GM_addStyle
// @license      GNU GPLv3
// ==/UserScript==

(function() {
    'use strict';

    const DOMAIN = 'https://www.tornspy.com';
    const TORN_SPY_KEY = 'tspy-key';
    const MINI_PROFILE_BREAKDOWN = 'tspy-mini-bd';
    const PAYMENT_TRACKER = 'tspy-ptrack';
    const SPY_TRACKER = 'tspy-strack';

    /**
     * Global functions
     */
    const purchase = (step, id, money, tag, theanon, callback) => {
        getAction({
            type: "post",
            action: "/sendcash.php",
            data: {
                step: step,
                ID: id,
                money: money,
                tag: tag,
                theanon: theanon
            },
            success: callback
        })
    };

    const spy = (id, specialId, amount, usersId, callback) => {
        ajaxWrapper({
            url: 'companies.php?step=specialgo',
            type: 'POST',
            data: [
                {name: 'ID', value: id},
                {name: 'specialid', value: specialId},
                {name: 'amount', value: amount},
                {name: 'usersID', value: usersId},
            ],
            oncomplete: callback
        });
    };

    const waitForElement = (selector) => {
        return new Promise(resolve => {
            if (document.querySelector(selector)) {
                return resolve(document.querySelector(selector));
            }

            const observer = new MutationObserver(mutations => {
                if (document.querySelector(selector)) {
                    observer.disconnect();
                    resolve(document.querySelector(selector));
                }
            });

            observer.observe(document.body, {
                childList: true,
                subtree: true
            });
        });
    }

    /**
     * Settings controls
     */
    const hasApiKey = () => {
        return GM_getValue(TORN_SPY_KEY)?.length > 16;
    }

    const getApiKey = () => {
        return GM_getValue(TORN_SPY_KEY);
    }

    const getMiniBreakdown = () => {
        return GM_getValue(MINI_PROFILE_BREAKDOWN) ?? true;
    }

    const getPaymentTracker = () => {
        return GM_getValue(PAYMENT_TRACKER) ?? true;
    }

    const hasPaymentTracked = (tag) => {
        return GM_getValue(`${PAYMENT_TRACKER}_${tag}`) ?? false;
    }

    /**
     * Profile page support: https://www.torn.com/profiles.php?XID=*
     */
    if(window.location.pathname === '/profiles.php' && hasApiKey()) {
        waitForElement('.medals-wrapper').then(()=> {
            profile(profile_id());
        });
    }

    const profile = (id) => {
        GM_xmlhttpRequest(
            {
                method: 'POST',
                url: `${DOMAIN}/userscripts/profile`,
                data: JSON.stringify({'xid': id}),
                responseType: 'json',
                headers: { 'TORNSPY-KEY': getApiKey() },
                onload: (response) => {
                    response = response.response ?? JSON.parse(response.responseText);
                    document.getElementById('profileroot').querySelector('.medals-wrapper').insertAdjacentHTML('beforeBegin', response.statBar);
                    if(document.getElementById("tspy-buy")){
                        document.getElementById("tspy-buy").addEventListener("click", ()=>{
                            document.getElementById("tspy-latest").style.display = 'none';
                            document.getElementById("tspy-purchase").style.display = 'block';
                        });
                    }
                    if(document.getElementById("tspy-cancel-btn")){
                        document.getElementById("tspy-cancel-btn").addEventListener("click", ()=>{
                            document.getElementById("tspy-purchase").style.display = 'none';
                            document.getElementById("tspy-latest").style.display = 'block';
                        });
                    }
                    if(document.getElementById("tspy-purchase-btn")) {
                        if(hasPaymentTracked(document.getElementById('tspy-purchase-btn').dataset.tag)) {
                            document.getElementById("tspy-latest").style.display = 'none';
                            document.getElementById("tspy-complete").style.display = 'block';
                        }
                        document.getElementById("tspy-purchase-btn").addEventListener("click", (event)=> {
                            document.getElementById("tspy-purchase-btn").disabled = true;
                            document.getElementById("tspy-purchase-btn").innerHTML = 'Sending payment...'
                            document.getElementById("tspy-cancel-btn").style.display = 'none';
                            purchase('cash1', event.srcElement.dataset.target, event.srcElement.dataset.price, event.srcElement.dataset.tag, false, (response) => {
                                response = JSON.parse(response);
                                if(response.error){
                                    document.getElementById("tspy-purchase").style.display = 'none';
                                    document.getElementById("tspy-error").innerHTML = response.error.replace("area", '').replace('This', 'Buying reports');
                                    document.getElementById("tspy-failure").style.display = 'block';
                                }else if(response.success === false){
                                    document.getElementById("tspy-error").innerHTML = response.text.replace('<b>', '<b class="t-red">').replace('<b>', '<b class="t-green">');
                                    document.getElementById("tspy-purchase").style.display = 'none';
                                    document.getElementById("tspy-failure").style.display = 'block';
                                }else{
                                    document.getElementById("tspy-purchase").style.display = 'none';
                                    document.getElementById("tspy-complete").style.display = 'block';
                                    if(getPaymentTracker()){
                                        GM_setValue(`${PAYMENT_TRACKER}_${event.srcElement.dataset.tag}`, Date.now())
                                    }
                                }
                            });
                        });
                    }
                    if(document.querySelector('.buttons-list')) {
                        response.buttons.forEach((button) =>{document.querySelector('.buttons-list').insertAdjacentHTML('beforeend', button)});
                    }
                }
            }
        );
    };

    const profile_id = () => {
        return (new URLSearchParams(window.location.search)).get('XID');
    }

    /**
     * Mini profile support.
     */
    if(hasApiKey() && getMiniBreakdown()){
        waitForElement('.profile-mini-root').then(()=> {
            waitForElement('div[class^="profile-mini-_userImage"]').then(() => {
                mini_profile(mini_profile_id());
                const observer = new MutationObserver((mutationList, observer) => {
                    if(document.getElementById('profile-mini-root').querySelector('div[class^="profile-mini-"]')){
                        waitForElement('div[class^="profile-mini-_userImage"]').then(() => {
                            mini_profile(mini_profile_id());
                        });
                    }
                });
                observer.observe(document.getElementById('profile-mini-root'), { childList: true });
            });
        });
    }

    const mini_profile = (id) => {
        GM_xmlhttpRequest({
            method: 'POST',
            url: `${DOMAIN}/userscripts/mini-profile`,
            data: JSON.stringify({'xid': id}),
            responseType: 'json',
            headers: { 'TORNSPY-KEY': getApiKey() },
            onload: (response) => {
                response = response.response ?? JSON.parse(response.responseText);
                const height = getComputedStyle(document.getElementById('profile-mini-root').querySelector('div[class^="profile-mini-_userProfileWrapper"]')).height;
                if(document.getElementById("profile-mini-root").querySelector('.icons') && response.statBar) {
                    document.getElementById("profile-mini-root").querySelector('.icons').insertAdjacentHTML('beforebegin', response.statBar);
                }
                if(document.getElementById("profile-mini-root").querySelector('.buttons-list')){
                    response.buttons.forEach((button) =>{document.getElementById("profile-mini-root").querySelector('.buttons-list').insertAdjacentHTML('beforeend', button)});
                }
                if(document.getElementById("profile-mini-root").querySelector('div[class^="profile-mini-_wrapper___"]')) {
                    // Allow the mini profile to scale larger than the set height.
                    document.getElementById("profile-mini-root").querySelector('div[class^="profile-mini-_wrapper___"]').style.maxHeight = 'fit-content';
                }
                // Adjust height of the mini profile.
                if(getComputedStyle(document.getElementById('profile-mini-root').querySelector('div[class^="profile-mini-_arrow"]')).bottom === '-8px' && response.statBar){
                    const adjustedHeight = getComputedStyle(document.getElementById('profile-mini-root').querySelector('div[class^="profile-mini-_userProfileWrapper"]')).height;
                    const heightDiff = adjustedHeight.replace('px', '') - height.replace('px', '');
                    const top = document.getElementById('profile-mini-root').querySelector('div[class^="profile-mini-_wrapper"]').style.top.replace('px', '')
                    document.getElementById('profile-mini-root').querySelector('div[class^="profile-mini-_wrapper"]').style.top = `${(top - heightDiff)}px`;
                }
            }
        });
    };

    const mini_profile_id = () => {
        return document.querySelector('a[class^="profile-mini-_linkWrap___"][class*=" profile-mini-_flexCenter___"][href^="/profiles.php?XID="]').href.replace('https://www.torn.com/profiles.php?XID=', '');
    };

    /**
     * Faction page support: https://www.torn.com/factions.php
     */
    if(window.location.pathname === '/factions.php' && hasApiKey() && !document.getElementById('tspy-view-bazaar')) {
        waitForElement('.content-title').then(()=> {
            waitForElement('.faction-profile').then(() => {
                waitForElement('.f-war-list.members-list').then(()=> {
                    faction_profile();
                });
            });
        });
    }

    var purchase_overview_loaded = false;
    var spy_overview_loaded = false;
    var activeTable = 'default';
    const faction_profile = (ids) => {
        GM_xmlhttpRequest({
            method: 'POST',
            url: `${DOMAIN}/userscripts/faction/profile`,
            data: JSON.stringify({'fid': faction_id()}),
            responseType: 'json',
            headers: { 'TORNSPY-KEY': getApiKey() },
            onload: (response) => {
                response = response.response ?? JSON.parse(response.responseText);

                if(response.style){
                    GM_addStyle(response.style);
                }

                document.querySelector('.faction-info-wrap.restyle.another-faction').insertAdjacentHTML('beforeBegin', response.actions);

                document.getElementById('tspy-spy-purchase-icon').addEventListener("click", (event) => {
                    if(purchase_overview_loaded){
                        toggle_active_table('purchase');
                        toggle_purchase_overview();
                        activeTable = activeTable!=='purchase'?'purchase':'default';
                    }
                    if(!purchase_overview_loaded){
                        purchase_overview_loaded = true;
                        document.getElementById('tspy-spy-purchase').innerHTML = 'Loading...';
                        purchase_overview_load();
                    }
                });

                document.getElementById('tspy-spy-spy-icon').addEventListener("click", (event) => {
                    if(spy_overview_loaded){
                        toggle_active_table('spy');
                        toggle_spy_overview();
                        activeTable = activeTable!=='spy'?'spy':'default';
                    }
                    if(!spy_overview_loaded){
                        spy_overview_loaded = true;
                        document.getElementById('tspy-spy-spy').innerHTML = 'Loading...';
                        spy_overview_load();
                    }
                });
            }
        });
    };

    const toggle_purchase_overview = () => {
        const tspyPurchaseHead = document.getElementById('tspy-purchase-thead');
        if(tspyPurchaseHead){
            tspyPurchaseHead.style.display = (tspyPurchaseHead.style.display ==='none')?'flex':'none';
        }

        document.querySelector('.f-war-list.members-list').querySelector('.table-body').querySelectorAll('.table-row').forEach((element) => {
            const spyPurchaseRow = element.querySelector('.tspy-purchase-row');

            element.querySelectorAll('.tspy-purchase-row').forEach((element)=>{
                element.style.display = (element.style.display)==='none'?'flex':'none';
            });
        });
    };

    const purchase_overview_load = () => {
        GM_xmlhttpRequest({
            method: 'POST',
            url: `${DOMAIN}/userscripts/faction/purchase`,
            data: JSON.stringify({'mids': member_ids()}),
            responseType: 'json',
            headers: { 'TORNSPY-KEY': getApiKey() },
            onload: (response) => {
                response = response.response ?? JSON.parse(response.responseText);
                purchase_overview(response);
                purchase_overview_loaded = true;
            }
        });
    };

    const purchase_overview = (response) => {
        toggle_active_table('purchase');
        activeTable = activeTable!=='purchase'?'purchase':'default';
        document.getElementById('tspy-spy-purchase').innerHTML = 'Purchase reports';
        document.querySelector('.f-war-list.members-list').querySelector('ul.table-header').insertAdjacentHTML('beforeEnd', response.heading);
        document.querySelector('.f-war-list.members-list').querySelector('.table-body').querySelectorAll('.table-row').forEach((row) => {
            const responseRow = response.spyRows[row.querySelector('a[href^="/profiles.php?XID="]').href.replace('https://www.torn.com/profiles.php?XID=', '')]
            row.insertAdjacentHTML('beforeEnd', responseRow.purchase);
            const reportHash = row.querySelector('.tspy-purchase-btn')?.dataset.tag;
            if(reportHash && hasPaymentTracked(reportHash)){
                row.querySelector(`.tspy-cta`).style.display = 'none';
                row.querySelector(`.tspy-purchase-details`).style.display = 'none';
                row.querySelector(`.tspy-complete`).style.display = 'block';
            }
            if(reportHash){
                row.querySelector(`.tspy-sale-btn`).addEventListener("click", (event) => {
                    row.querySelector(`.tspy-cta`).style.display = 'none';
                    row.querySelector(`.tspy-purchase-details`).style.display = 'none';
                    row.querySelector(`.tspy-purchase`).style.display = 'block';
                });
                row.querySelectorAll(`.tspy-cancel-btn`).forEach((element) => {
                    element.addEventListener('click', (event) => {
                        row.querySelector(`.tspy-purchase`).style.display = 'none';
                        row.querySelector(`.tspy-cta`).style.display = 'block';
                        row.querySelector(`.tspy-purchase-details`).style.display = 'block';
                    })
                });
                row.querySelectorAll(`.tspy-purchase-btn`).forEach((element) => {
                    element.addEventListener('click', (event) => {
                        event.srcElement.disabled = true;
                        event.srcElement.innerHTML = 'Sending payment...'
                        row.querySelectorAll(`.tspy-cancel-btn`).forEach((element) => {
                            element.style.display = 'none';
                        });
                        purchase('cash1', event.srcElement.dataset.target, event.srcElement.dataset.price, event.srcElement.dataset.tag, false, (response) => {
                            response = JSON.parse(response);
                            if(response.error){
                                row.querySelector(`.tspy-purchase`).style.display = 'none';
                                row.querySelector(`.tspy-error`).innerHTML = response.error.replace("area", '').replace('This', 'Buying reports');
                                row.querySelector(`.tspy-failure`).style.display = 'block';
                            }else if(response.success === false){
                                row.querySelector(`.tspy-error`).innerHTML = response.text.replace('<b>', '<b class="t-red">').replace('<b>', '<b class="t-green">');
                                row.querySelector(`.tspy-purchase`).style.display = 'none';
                                row.querySelector(`.tspy-failure`).style.display = 'block';
                            }else{
                                row.querySelector(`.tspy-purchase`).style.display = 'none';
                                row.querySelector(`.tspy-complete`).style.display = 'block';
                                if(getPaymentTracker()){
                                    GM_setValue(`${PAYMENT_TRACKER}_${event.srcElement.dataset.tag}`, Date.now())
                                }
                            }
                        });
                    })
                });
            }
        });
    };

    const spy_overview_load = () => {
        GM_xmlhttpRequest({
            method: 'POST',
            url: `${DOMAIN}/userscripts/faction/spy`,
            data: JSON.stringify({'mids': member_ids()}),
            responseType: 'json',
            headers: { 'TORNSPY-KEY': getApiKey() },
            onload: (response) => {
                response = response.response ?? JSON.parse(response.responseText);
                spy_overview(response);
                spy_overview_loaded = true;
            }
        });
    };

    const spy_overview = (response) => {
        toggle_active_table('spy');
        activeTable = activeTable!=='spy'?'spy':'default';
        document.getElementById('tspy-spy-spy').innerHTML = 'Spy';
        document.querySelector('.f-war-list.members-list').querySelector('ul.table-header').insertAdjacentHTML('beforeEnd', response.heading);
        document.querySelector('.f-war-list.members-list').querySelector('.table-body').querySelectorAll('.table-row').forEach((row) => {
            const userId = row.querySelector('a[href^="/profiles.php?XID="]').href.replace('https://www.torn.com/profiles.php?XID=', '');
            const responseRow = response.spyRows[userId]
            row.insertAdjacentHTML('beforeEnd', responseRow.spy);

            cachedSpyRecord(userId, row);

            row.querySelector(`.tspy-spy-btn`).addEventListener("click", (event) => {
                row.querySelector('.tspy-spy-btn').disabled = true;
                row.querySelector('.tspy-spy-btn').innerHTML = '...';
                spy(event.srcElement.dataset.id, event.srcElement.dataset.special, event.srcElement.dataset.amount, event.srcElement.dataset.spy, (response) => {
                    response = JSON.parse(response.responseText);

                    if(response.success === false){
                        updateSpyRecordError(response.text.replace("area", '').replace('This', 'Using your job special'), row);
                        return;
                    }

                    if(response.result.error){
                        updateSpyRecordError(response.result.error, row);
                        return;
                    }

                    row.querySelector('.tspy-spy-btn').disabled = false;
                    row.querySelector('.tspy-spy-btn').innerHTML = 'Spy';
                    updateSpyRecordView(
                        updateSpyRecord(response.result.msg, response.result.user, response.amount),
                        row
                    );
                    document.getElementById('tspy-spy-thead-amount').innerHTML = response.pointsLeft;
                    document.getElementById('tspy-spy-thead-amount').parentNode.style.display = 'block';
                });
            });
        });
    };

    const cachedSpyRecord = (userId, row) => {
        var record = GM_getValue(`${SPY_TRACKER}_${userId}`)
        if(!record){
            return;
        }

        record = JSON.parse(record);

        const difference = record.timestamp / 1000 - row.querySelector('.tspy-spy-details').dataset.timestamp;
        if(difference < 1){
            GM_deleteValue(`${SPY_TRACKER}_${userId}`);
            return;
        }

        updateSpyRecordView(record, row);
    };

    const updateSpyRecordError = (error, row) => {
        row.querySelector('.tspy-spy-btn').innerHTML = 'Oops!';
        row.querySelector('.tspy-spy-btn').classList.add('t-red');
        row.querySelector('.tspy-spy-btn').classList.remove('t-yellow');
        row.querySelector('.tspy-spy-btn').classList.remove('t-green');
        row.querySelector('.tspy-record-head').innerHTML = `<span class="t-red">Oops!</span> Something went wrong...`;
        row.querySelector('.tspy-record-body').innerHTML = `<span>${error}</span`;
        row.querySelector('.tspy-record-head-mobile').innerHTML = `<span class="t-red">Oops!</span>`;
        row.querySelector('.tspy-record-body-mobile').innerHTML = `<span class="t-red">${error}</span>`;

        row.querySelector('.tspy-record').style.display = 'flex';
        row.querySelector('.tspy-spy-details').style.display = 'none';
    };

    const updateSpyRecordView = (record, row) => {
        if(Object.keys(record.exposed).length > 3){
            row.querySelector('.tspy-spy-btn').classList.add("t-green")
            row.querySelector('.tspy-spy-btn').classList.remove("t-yellow")
            row.querySelector('.tspy-spy-btn').disabled = true;
            row.querySelector('.tspy-spy-btn').innerHTML = 'Full';
            row.querySelector('.tspy-record-head').innerHTML = `<span class="t-green">Full</span>`;
            row.querySelector('.tspy-record-body').innerHTML = `<span>${Object.keys(record.exposed).join(', ')}</span>`;
            row.querySelector('.tspy-record-head-mobile').innerHTML = `<span class="t-green">Full </span><div><strong>JP: </strong> ${record.pointsUsed}</div>`;
        }else{
            row.querySelector('.tspy-spy-btn').classList.add("t-yellow")
            row.querySelector('.tspy-spy-btn').classList.remove("t-green")
            row.querySelector('.tspy-record-head').innerHTML = `<span class="t-yellow">Partial</span>`;
            row.querySelector('.tspy-record-body').innerHTML = `<span>${Object.keys(record.exposed).join(', ')}</span>`;
            row.querySelector('.tspy-record-head-mobile').innerHTML = `<span class="t-yellow">Partial</span><div><strong>JP: </strong> ${record.pointsUsed}</div>`;
        }

        row.querySelector('.tspy-record').style.display = 'flex';
        row.querySelector('.tspy-spy-details').style.display = 'none';

        row.querySelector('.tspy-record-head').innerHTML = row.querySelector('.tspy-record-head').innerHTML + `<span> (${new Date(record.timestamp).toLocaleString('en-GB', { timeZone: 'UTC' })})<span> <strong>JP used: </strong> ${record.pointsUsed}`;
        row.querySelector('.tspy-record-body-mobile').innerHTML = `<span>${new Date(record.timestamp).toLocaleString('en-GB', { timeZone: 'UTC' })}<span>`;
    };

    const updateSpyRecord = (spyDetails, user, amount) => {
        var record = GM_getValue(`${SPY_TRACKER}_${user.userID}`);

        if(!record){
            record = {
                timestamp: Date.now(),
                exposed: {},
                pointsUsed: 0
            }
        }else{
            record = JSON.parse(record);
        }

        record.timestamp = Date.now();

        for (const [key, value] of Object.entries(spyDetails)) {
            if(['money', 'moneyshow'].includes(key)){
                continue;
            }

            if(value !== 'N/A'){
                record.exposed[key] = value;

            }
        }

        record.pointsUsed += parseInt(amount);

        GM_setValue(`${SPY_TRACKER}_${user.userID}`, JSON.stringify(record));

        return record;
    };

    const toggle_spy_overview = () => {
        const tspySpyHead = document.getElementById('tspy-spy-thead');
        if(tspySpyHead){
            tspySpyHead.style.display = (tspySpyHead.style.display ==='none')?'flex':'none';
        }
        document.querySelector('.f-war-list.members-list').querySelector('.table-body').querySelectorAll('.table-row').forEach((element) => {
            element.querySelectorAll('.tspy-spy-row').forEach((element)=>{
                element.style.display = (element.style.display)==='none'?'flex':'none';
            });
        });
    };

    const toggle_active_table = (targetTable) => {
        if(activeTable === targetTable){
            toggle_table();
            return;
        }

        if(activeTable === 'purchase'){
            toggle_purchase_overview();
        }else if(activeTable === 'spy'){
            toggle_spy_overview();
        }else{
            toggle_table();
        }
    };

    const toggle_table = () => {
        const memberIcons = document.querySelector('.f-war-list.members-list').querySelector('ul.table-header').querySelector('.member-icons');
        const position = document.querySelector('.f-war-list.members-list').querySelector('ul.table-header').querySelector('.position');
        const days = document.querySelector('.f-war-list.members-list').querySelector('ul.table-header').querySelector('.days');
        const status = document.querySelector('.f-war-list.members-list').querySelector('ul.table-header').querySelector('.status');
        const lvl = document.querySelector('.f-war-list.members-list').querySelector('ul.table-header').querySelector('.lvl');

        position.style.display = (position.style.display ==='none')?'flex':'none';
        days.style.display = (days.style.display ==='none')?'flex':'none';
        status.style.display = (status.style.display ==='none')?'flex':'none';

        if(!memberIcons.classList.contains('hide-mobile')){
            memberIcons.classList.add('hide-mobile');
            memberIcons.classList.add('hide-desktop');
        }else{
            memberIcons.classList.remove('hide-mobile');
            memberIcons.classList.remove('hide-desktop');
        }

        if(!lvl.classList.contains('hide-mobile')){
            lvl.classList.add('hide-mobile')
        }else{
            lvl.classList.remove('hide-mobile');
        }

        const memberRows = document.querySelector('.f-war-list.members-list').querySelector('.table-body').querySelectorAll('.table-row');
        memberRows.forEach((element) => {
            const memberIconsInline = element.querySelector('.member-icons');
            const positionInline = element.querySelector('.position');
            const daysInline = element.querySelector('.days');
            const statusInline = element.querySelector('.status');
            const lvl = element.querySelector('.lvl');

            positionInline.style.display = (positionInline.style.display ==='none')?'flex':'none';
            daysInline.style.display = (daysInline.style.display ==='none')?'flex':'none';
            statusInline.style.display = (statusInline.style.display ==='none')?'flex':'none';

            if(!memberIconsInline.classList.contains('hide-mobile')){
                memberIconsInline.classList.add('hide-mobile');
                memberIconsInline.classList.add('hide-desktop');
            }else{
                memberIconsInline.classList.remove('hide-mobile');
                memberIconsInline.classList.remove('hide-desktop');
            }

            if(!lvl.classList.contains('hide-mobile')){
                lvl.classList.add('hide-mobile')
            }else{
                lvl.classList.remove('hide-mobile');
            }
        });
    };

    const faction_id = () => {
        const id = (new URLSearchParams(window.location.search)).get('ID');
        if(id){
            return id;
        }

        if(document.getElementById('top-page-links-list').querySelector('a[href^="/page.php?sid=factionWarfare#/ranked/"]')){
            return document.getElementById('top-page-links-list').querySelector('a[href^="/page.php?sid=factionWarfare#/ranked/"]').href.replace('https://www.torn.com/page.php?sid=factionWarfare#/ranked/', '')
        }

        return document.querySelector('.forum-thread').href.replace('https://www.torn.com/forums.php#!p=forums&f=999&b=1&a=', '');
    }

    const member_ids = () => {
        let mids = [];
        document.querySelector('.f-war-list.members-list').querySelector('.table-body').querySelectorAll('.table-row').forEach((element)=>{
            mids.push(element.querySelector('a[href^="/profiles.php?XID="]').href.replace('https://www.torn.com/profiles.php?XID=', ''));
        })
        return mids;
    };

    /**
     * Settings page support: https://www.torn.com/preferences.php?tab=tornspy
     */
    if(window.location.pathname === '/preferences.php' && (new URLSearchParams(window.location.search)).get('tab') === 'tornspy'){
        waitForElement('.ui-tabs-nav').then(()=> {
            settings();
        });
    }

    const settings = (id) => {
        GM_xmlhttpRequest({
            method: 'POST',
            url: `${DOMAIN}/userscripts/settings`,
            responseType: 'json',
            onload: (response) => {
                response = response.response ?? JSON.parse(response.responseText);
                if(document.querySelector('.ui-tabs-nav')){
                    document.querySelector('#prefs-tab-menu').innerHTML = response.page;
                    document.querySelector('.prefs-tab-title').innerHTML = 'Torn Spy settings'
                    document.getElementById('tspy-apikey').value = getApiKey() ?? '';
                    document.getElementById('tspy-mini-bd-on').checked = getMiniBreakdown() ?? true;
                    document.getElementById('tspy-mini-bd-off').checked = !getMiniBreakdown() ?? false;
                    document.getElementById('tspy-payment-track-on').checked = getPaymentTracker() ?? true;
                    document.getElementById('tspy-payment-track-off').checked = !getPaymentTracker() ?? false;
                }
                if(document.getElementById('tspy-settings')){
                    document.getElementById('tspy-save-btn').addEventListener("click", () => {
                        if(document.getElementById('tspy-apikey').value.length <= 16){
                            document.getElementById('tspy-key-error').innerHTML = 'Oops! your key needs to be longer than 16 characters!';
                        }else{
                            document.getElementById('tspy-key-error').style.display = 'none';
                            GM_setValue(TORN_SPY_KEY, document.getElementById('tspy-apikey').value);
                        }
                        GM_setValue(MINI_PROFILE_BREAKDOWN, document.getElementById('tspy-mini-bd-on').checked);
                        document.getElementById('tspy-success').innerHTML = 'Settings updated!';
                    });
                    document.getElementById('tspy-reset-btn').addEventListener("click", () => {
                        GM_deleteValue(TORN_SPY_KEY);
                        GM_deleteValue(MINI_PROFILE_BREAKDOWN);
                        document.getElementById('tspy-apikey').value = '';
                        document.getElementById('tspy-success').innerHTML = 'Torn Spy integration settings reset!';
                    });
                }
                if(document.getElementById('tspy-ptrack-clear-btn')){
                    document.getElementById('tspy-ptrack-clear-btn').addEventListener("click", () => {
                        GM_listValues().forEach((value) => {
                            if(value.startsWith(`${PAYMENT_TRACKER}_`)){
                                GM_deleteValue(value);
                            }
                        })
                        document.getElementById('tspy-ptrack-message').innerHTML = 'Tracked payments cleared!'
                    });
                }
            }
        });
    };

    /**
     * Icon support
     */
    waitForElement('ul[class^="status-icons"').then(()=> {
        const icon = `<li id="tspy-icon" style="background-image:url('https://www.tornspy.com/assets/img/favicon.ico');"><a href="https://www.torn.com/preferences.php?tab=tornspy" aria-label="Torn Spy" tabindex="0" data-is-tooltip-opened="false"></a></li>`;
        document.querySelector('ul[class^="status-icons"').insertAdjacentHTML('beforeEnd', icon)

        const observer = new MutationObserver((mutationList, observer) => {
            if(document.querySelector('ul[class^="status-icons"').querySelector('#tspy-icon')){
                return;
            }

            document.querySelector('ul[class^="status-icons"').insertAdjacentHTML('beforeEnd', icon)
        });
        observer.observe(document.getElementById('sidebarroot'), { childList: true, subtree: true });
    });

    /**
     * No API key is set prompt.
     */
    if(!hasApiKey()){
        document.querySelector('.content-title').insertAdjacentHTML('afterEnd', `<div class="info-msg-cont green border-round m-top10">
		<div class="info-msg border-round">
			<i class="info-icon"></i>
			<div class="delimiter">
				<div class="msg right-round" role="alert" aria-live="polite">
					<ul><li>Torn Spy API key is not set, click <a class="t-blue h" href="preferences.php?tab=tornspy">here</a> to set your API Key!</li></ul>
				</div>
			</div>
		</div>
	</div>`);
    }

})();