YATA

Displays various informations from YATA's API

目前為 2023-10-17 提交的版本,檢視 最新版本

// ==UserScript==
// @name         YATA
// @namespace    yata.yt
// @version      0.12
// @grant        GM_addStyle
// @description  Displays various informations from YATA's API
// @author       Kivou [2000607]
// @grant        GM.xmlHttpRequest
// @match        https://www.torn.com/factions.php*
// @match        https://www.torn.com/preferences.php*
// @match        https://www.torn.com/profiles.php*
// @icon         https://yata.yt/media/yata-small.png
// @require      https://gf.qytechs.cn/scripts/477604-kiv-lib/code/kiv-lib.js?version=1266053
// @run-at       document-end
// @license      WTFPL
// ==/UserScript==

// Copyright © 2023 Kivou [2000607] <[email protected]>
// This work is free. You can redistribute it and/or modify it under the
// terms of the Do What The Fuck You Want To Public License, Version 2,
// as published by Sam Hocevar. See http://www.wtfpl.net/ for more details.


// ------------- //
// SETUP API KEY //
// ------------- //
const display_status = (element) => {
    const key = localStorage.getItem('key');
    let innerHTML = "";
    innerHTML += `<div>`;
    innerHTML += `<b>[YATA]</b> API key used <span style="font-family: monospace; font-weight: bold;">${key}</span>`;
    if (key) {
        innerHTML += ` | Status <b id="yata-status" style="color: var(--default-green-color); font-weight: bold;">enabled</b>`;
        innerHTML += ` | Click <span id="yata-api-key-rm" class="t-blue" style="cursor: pointer;">here to disable</span> the script`;
    } else {
        innerHTML += ` | Status <b id="yata-status" style="color: var(--default-red-color); font-weight: bold;">disabled</b>`;
        innerHTML += ` | Click on a key to enable the script`;
    }
    innerHTML += `</div>`;
    innerHTML += `<div class="clear"></div>`;
    innerHTML += `<hr class="page-head-delimiter m-top10">`;
    element.innerHTML = innerHTML;
};

waitFor(document, "div.preferences-container").then(div => {

    let injected = false;
    const display_element = document.createElement("div");
    div.insertAdjacentElement('beforebegin', display_element);

    // triggered by clicking on crimes tab
    const callback = (mutations, observer) => {
        [...mutations].forEach(mutation => {
            [...mutation.addedNodes].filter(n => n.className && n.className.includes("keyRow___")).forEach(node => {
                const key_node = node.querySelector("input");
                key_node.style.cursor = "pointer";
                // if(!localStorage.getItem('key')) {
                //     localStorage.setItem('key', key_node.value);
                //     document.getElementById("yata-api-key").innerHTML = localStorage.getItem('key')
                // }
            });
        });
        if (!injected) {
            display_status(display_element);
            injected = true;
        }
    };
    const observer = new MutationObserver(callback);
    observer.observe(div, { childList: true, subtree: true });

    document.querySelector("div.content-wrapper").addEventListener('click', e => {
        const button = e.target;
        if (button.tagName == 'INPUT' && button.id.includes('key-row')) {
            localStorage.setItem('key', button.value);
        }
        else if (button.tagName == 'SPAN' && button.id == 'yata-api-key-rm') {
            localStorage.clear();
        }
        display_status(display_element);
    });

});


// -------------- //
// OC: NNB + rank //
// -------------- //
const display_nnb = (members, player) => {
    const urlParams = new URLSearchParams(player.children[0].children[0].href.split("?")[1]);
    const lvl = player.children[1].innerText.trim();
    if (members.members && members.members.hasOwnProperty(urlParams.get("XID"))) {
        const m = members.members[urlParams.get("XID")];
        if (m.nnb_share > 0) {
            player.children[1].innerHTML = `<span>#<b>${m.crimes_rank}</b> / <b>${m.nnb}</b> / ${lvl}</span>`;
        } else if (m.nnb_share < 0) {
            player.children[1].innerHTML = `<span title="Not on YATA">#<b>${m.crimes_rank}</b> / <b>!</b> / ${lvl}</span>`;
        } else {
            player.children[1].innerHTML = `<span title="Not sharing NNB">#<b>${m.crimes_rank}</b> / <b>?</b> / ${lvl}</span>`;
        }
    } else {
        player.children[1].innerHTML = `<span title="Not found">#<b>?</b> / <b>err</b> / ${lvl}</span>`;
    }
};

waitFor(document, "div#faction-crimes").then(div => {

    const key = localStorage.getItem('key');

    if (!key) { return; }

    const profile_url = new URLSearchParams(window.location.search);
    const target_id = profile_url.get("XID");

    gmGet(`https://yata.yt/api/v1/faction/members/?key=${key}`, 'nnb').then(members => {

        // triggered if directly landing on crimes
        div.querySelectorAll("ul.details-list, ul.plans-list").forEach(ul => {
            ul.querySelectorAll("ul.item").forEach(player => {
                display_nnb(members, player);
            });
        });
        div.querySelectorAll("ul.title li.level").forEach(t => {
            t.innerHTML = 'Rank / NNB / Level';
        });

        // triggered by clicking on crimes tab
        const callback = (mutations, observer) => {
            [...mutations].forEach(mutation => {
                [...mutation.addedNodes].filter(n => n.className && n.className.includes("faction-crimes-wrap")).forEach(node => {
                    node.querySelectorAll("ul.details-list, ul.plans-list").forEach(ul => {
                        const ocs = ul.querySelectorAll("ul.item");
                        console.log(`[yata] displaying NNB for OC: ${ocs.length}`);
                        ocs.forEach(player => {
                            display_nnb(members, player);
                        });
                    });
                    node.querySelectorAll("ul.title li.level").forEach(t => {
                        t.innerHTML = 'Rank / NNB / Level';
                    });
                });
            });
        };
        const observer = new MutationObserver(callback);
        observer.observe(div, { childList: true });


    }).catch(error => {
        alert(`[yata] ${error.message}`);
        if (error.message == "Incorrect key") {
            localStorage.removeItem('key');
        }
    });
});


// ---------------------- //
// PROFILE                //
// ---------------------- //

waitFor(document, "a.profile-button-report").then(a => {

    const div = document.getElementById("profileroot");
    const key = localStorage.getItem('key');
    const profile_url = new URLSearchParams(a.href.split("?")[1]);
    const target_id = profile_url.get("userID");

    if (!target_id) { return; }
    if (!key) { return; }

    gmGet(`https://yata.yt/api/v1/bs/${target_id}/?key=${key}`, `bs-${target_id}`).then(bs => {

        let innerHTML = "";
        innerHTML += `<hr class="page-head-delimiter m-top10 m-bottom10">`;
        innerHTML += `<div>`;
        innerHTML += `<b>[YATA]</b> <b>Battle stats</b> ${floatFormat(bs[target_id].total, 3)}`;
        innerHTML += ` | <b>Build</b> ${bs[target_id].type} (${bs[target_id].skewness}%)`;
        innerHTML += `</div>`;
        innerHTML += `<hr class="page-head-delimiter m-top10 m-bottom10">`;
        innerHTML += `<div class="clear"></div>`;
        const bs_node = document.createElement("div");
        bs_node.innerHTML = innerHTML;
        div.querySelector("div.profile-wrapper").insertAdjacentElement('afterend', bs_node);
        console.log(`[yata] battle stats estimate on profile page: ${target_id}`);

    }).catch(error => {
        alert(`[yata] ${error.message}`);
        if (error.code == 4) {
            localStorage.removeItem('key');
        }
    });

});


// -------------------------- //
// FACTIONS: Helper functions //
// -------------------------- //
const bse_html = (id, bs) => {

    if (!bs.hasOwnProperty("type")) {
        return `<a style="text-decoration: none; display: inline-block;" href="/loader.php?sid=attack&user2ID=${id}" target="_blank"><span title='${bs["message"]}' style="color: var(--default-red-color);">err</span></a>`;
    }

    let color = "var(--default-blue-color)";
    if (bs.type == "Offensive" && bs.skewness > 20) {
        color = "var(--default-red-color)";
    } else if (bs.type == "Defensive" && bs.skewness > 20) {
        color = "var(--default-green-color)";
    }
    const title = `Total stats: ${bs.total.toLocaleString("en-GB")} Build: ${bs.type} (${bs.skewness}%)`;
    return `<a style="text-decoration: none; display: inline-block;" href="/loader.php?sid=attack&user2ID=${id}" target="_blank"><span title="${title}" style="color: ${color};">${floatFormat(bs.total, 3)}</span></a>`;
};

// ---------------------- //
// FACTIONS: Members list //
// ---------------------- //
const members_list_filter = (div) => {
    if (div.tagName != "DIV") { return false; }
    if (div.classList.contains("faction-info-wrap")) { return true; }
    return Boolean(div.querySelector("div.faction-info-wrap"));
};

const members_list_display = (members, key) => {
    console.log(`[yata] battle stats estimate for members list: ${members.length}`);
    [...members].forEach(member => {
        const url = new URLSearchParams(member.querySelector("div.member.icons").querySelector("div[class^=userWrap]").querySelector("a[class^=linkWrap]").href.split("?")[1]);
        const member_id = url.get("XID");

        gmGet(`https://yata.yt/api/v1/bs/${member_id}/?key=${key}`, `bs-${member_id}`).then(bs => {
            const node = document.createElement("span");
            node.innerHTML = bse_html(member_id, bs[member_id]);
            node.style.width = "4em";
            // member.querySelector("div.member-icons").insertAdjacentElement('afterbegin', node);
            member.querySelector("div.position").insertAdjacentElement('afterbegin', node);
        }).catch((error) => {
            const node = document.createElement("span");
            node.innerHTML = bse_html(member_id, error);
            node.style.width = "4em";
            // member.querySelector("div.member-icons").insertAdjacentElement('afterbegin', node);
            member.querySelector("div.position").insertAdjacentElement('afterbegin', node);
        });
    });
};

// -------------- //
// FACTIONS: Wars //
// -------------- //
const wars_list_filter = (node) => {
    if (node.tagName != "DIV") { return false; }
    return node.tagName == "DIV" && node.classList.contains("faction-war");
};

const wars_list_display = (members, type, key) => {
    console.log(`[yata] battle stats estimate for ${type}: ${members.length}`);

    [...members].forEach(member => {
        // STEP 1: get target ID

        let member_id = undefined;

        if (type == "wall") {
            const url = new URLSearchParams(
                member.querySelector("a.user.name").href.split("?")[1]
            );
            member_id = url.get("XID");
        } else {
            const url = new URLSearchParams(
                member.querySelector("div.member.icons")
                    .querySelector("div[class^=userWrap]")
                    .querySelector("a[class^=linkWrap]").href.split("?")[1]
            );
            member_id = url.get("XID");
        }

        // STEP 2: make call
        gmGet(`https://yata.yt/api/v1/bs/${member_id}/?key=${key}`, `bs-${member_id}`).then(bs => {
            const node = document.createElement("span");
            node.innerHTML = bse_html(member_id, bs[member_id]);

            if (type == "wall") {
                // replace attack link
                node.style.paddingRight = "0.5em";
                member.children[4].innerHTML = node.outerHTML;
            } else if (type == "rank-right") {
                // prepend to level
                node.style.paddingRight = "1em";
                member.children[1].insertAdjacentElement('afterbegin', node);
            } else if (type == "rank-left") {
                // replace link
                node.style.paddingRight = "0.5";
                member.children[4].innerHTML = node.outerHTML;
            } else {
                // replace attack link
                node.style.paddingRight = "0.5em";
                member.children[5].innerHTML = node.outerHTML;
            }
        }).catch((error) => {
            const node = document.createElement("span");
            node.innerHTML = bse_html(member_id, error);

            if (type == "wall") {
                // replace attack link
                node.style.paddingRight = "0.5em";
                member.children[4].innerHTML = node.outerHTML;
            } else if (type == "rank-right") {
                // prepend to level
                node.style.paddingRight = "1em";
                member.children[1].insertAdjacentElement('afterbegin', node);
            } else if (type == "rank-left") {
                // replace link
                node.style.paddingRight = "0.5";
                member.children[4].innerHTML = node.outerHTML;
            } else {
                // replace attack link
                node.style.paddingRight = "0.5em";
                member.children[5].innerHTML = node.outerHTML;
            }
        });
    });
};

const chain_list_filter = (node) => {
    if (node.tagName != "UL") { return false; }
    return node.tagName == "UL" && node.classList.contains("chain-attacks-list");
};

const chain_list_display = (attacks, key) => {
    console.log(`[yata] battle stats estimate for chain: ${attacks.length}`);

    const _f = (e) => { return Boolean(e.querySelector("div.right-player div[class^=userInfoBox]")); };
    [...attacks].filter(_f).forEach(attack => {

        const target = attack.querySelector("div.right-player");
        const url = new URLSearchParams(
            target.querySelector("div.member.icons")
                .querySelector("div[class^=userWrap]")
                .querySelector("a[class^=linkWrap]").href.split("?")[1]
        );
        const target_id = url.get("XID");
        const respect = attack.querySelector("div.respect");

        gmGet(`https://yata.yt/api/v1/bs/${target_id}/?key=${key}`, `bs-${target_id}`).then(bs => {
            const node = document.createElement("span");
            node.innerHTML = bse_html(target_id, bs[target_id]);
            node.style.float = 'right';
            respect.insertAdjacentElement('beforeend', node);
        }).catch((error) => {
            const node = document.createElement("span");
            node.innerHTML = bse_html(target_id, error);
            node.style.float = 'right';
            respect.insertAdjacentElement('beforeend', node);
        });

    });
};

// ------------------- //
// FACTIONS: observers //
// ------------------- //
const callback_factions = (key) => {
    return (mutations, observer) => {

        const tab = window.location.hash.replace("#/tab=", "");
        if (["territory", "rank", "upgrades", "armoury", "controls"].includes(tab)) { return; }

        [...mutations].forEach(mutation => {

            [...mutation.addedNodes].forEach(node => {

                // members list
                if (members_list_filter(node)) {
                    const members = node.querySelectorAll("li.table-row");
                    if (members.length) { members_list_display(members, key); }
                }

                // chains
                if (chain_list_filter(node)) {
                    chain_list_display(node.childNodes, key);

                    // observe new attacks
                    const callback_chain = (key) => {
                        return (mutations, observer) => {
                            [...mutations].forEach(mutation => {
                                const attacks = [...mutation.addedNodes];
                                chain_list_display(attacks, key);
                            });
                        };
                    };
                    const walls_observer = new MutationObserver(callback_chain(key));
                    walls_observer.observe(node, { childList: true });
                }

                // wars
                if (wars_list_filter(node)) {

                    // 2 lists for RW, 1 for walls and 1 for raids
                    [...node.querySelectorAll("div.members-cont > ul.members-list")].forEach(faction => {
                        const members = faction.querySelectorAll("li.your, li.enemy");

                        if (tab.includes("rank")) {  // RW
                            if (members[0].classList.contains("your")) {
                                wars_list_display(members, "rank-right", key);
                            } else {
                                wars_list_display(members, "rank-left", key);
                            }
                        } else if (tab.includes("raid")) {  // Raids
                            wars_list_display(members, "raid", key);
                        } else {  // walls
                            wars_list_display(members, "wall", key);

                            // observe wall jumps
                            const callback_walls = (key) => {
                                return (mutations, observer) => {
                                    const _f = (e) => { return e.className.includes("your") || e.className.includes("enemy"); };
                                    [...mutations].forEach(mutation => {
                                        const members = [...mutation.addedNodes].filter(_f);
                                        wars_list_display(members, "wall", key);
                                    });
                                };
                            };
                            const walls_observer = new MutationObserver(callback_walls(key));
                            walls_observer.observe(faction, { childList: true });
                        }
                    });
                }

            });
        });
    };
};

waitFor(document, "div#factions").then(factions => {

    const key = localStorage.getItem('key');
    if (!key) { return; }

    const obs = new MutationObserver(callback_factions(key));
    obs.observe(factions, { childList: true, subtree: true });

});

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址