NPO FF

Friendly Fire Protection in Browser and PDA

// ==UserScript==
// @name         NPO FF
// @namespace    https://www.torn.com/profiles.php?XID=3833584
// @version      2025-09-29
// @description  Friendly Fire Protection in Browser and PDA
// @author       -Thelemite [3833584]
// @match        https://www.torn.com/profiles.php*
// @match        https://torn.com/profiles.php*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=torn.com
// @run-at       document-end
// @grant        none
// @license      MIT
// ==/UserScript==

(function () {
    'use strict';

    const Allies = [
        { id: "10610", name: "NPO - Strength" },        // -- INDEX 0
        { id: "44758", name: "NPO - Prosperity" },      // -- INDEX 1
        { id: "12645", name: "NPO - Endurance" },       // -- INDEX 2
        { id: "14052", name: "NPO - Serenity" },        // -- INDEX 3
        { id: "18714", name: "NPO - Peace" },           // -- INDEX 4
        { id: "26885", name: "NPO - Valour" },          // -- INDEX 5

        { id: "19", name: "39th Street Killers" },      // -- INDEX 6
        { id: "16312", name: "39th Street Killers X" }, // -- INDEX 7
        { id: "7049", name: "39th Street Healers" },    // -- INDEX 8
        { id: "22680", name: "39th Street Reapers" },   // -- INDEX 9
        { id: "31764", name: "39th Street Warriors" },  // -- INDEX 10
        { id: "36691", name: "Rabid Chihuahuas" },      // -- INDEX 11
        { id: "11162", name: "InQuest" },               // -- INDEX 12
        { id: "7197", name: "HeLa" },                   // -- INDEX 13
        { id: "30009", name: "White Rabbits" }          // -- INDEX 14
    ];

    // Utility: wait for a selector to appear (handles PDA late DOM)
    function waitForSelector(selector, { root = document, timeout = 10000 } = {}) {
        return new Promise((resolve) => {
            const el = root.querySelector(selector);
            if (el) return resolve(el);

            const obs = new MutationObserver(() => {
                const found = root.querySelector(selector);
                if (found) { obs.disconnect(); resolve(found); }
            });

            obs.observe(root.documentElement || root, { childList: true, subtree: true });

            if (timeout > 0) {
                setTimeout(() => { obs.disconnect(); resolve(null); }, timeout);
            }
        });
    }

    // Extract factionId from /factions.php?step=profile&ID=... links
    function getFactionId() {
        const anchors = document.querySelectorAll('a[href*="/factions.php?step=profile&ID="]');
        for (const a of anchors) {
            try {
                const url = new URL(a.getAttribute('href'), location.href);
                if (url.pathname.endsWith('/factions.php') && url.searchParams.get('step') === 'profile') {
                    const id = url.searchParams.get('ID');
                    if (id) return String(id).trim();
                }
            } catch { /* ignore malformed hrefs */ }
        }
        return null;
    }

    // (Optional) userId, if you need it later
    function getUserIdFromAttackBtn(btn) {
        const id = btn?.id ?? '';
        const parts = id.split('-');
        return parts.length ? parts[parts.length - 1] : null;
    }

    // Update decorateAndIntercept to accept allyName
    function decorateAndIntercept(attackBtn, factionId, allyName) {
        if (!attackBtn) return;
        if (attackBtn.dataset.allyDecorated === '1') return;
        attackBtn.dataset.allyDecorated = '1';

        // Positioning for overlay
        const cs = getComputedStyle(attackBtn);
        if (cs.position === 'static') attackBtn.style.position = 'relative';

        // Green X overlay (slightly smaller for mobile)
        const x = document.createElement('span');
        x.textContent = '✕';
        x.setAttribute('aria-hidden', 'true');
        x.style.position = 'absolute';
        x.style.top = '2px';
        x.style.right = '2px';
        x.style.fontWeight = '900';
        x.style.fontSize = '36px';
        x.style.lineHeight = '1';
        x.style.padding = '2px 4px';
        x.style.borderRadius = '4px';
        x.style.background = 'rgba(0, 128, 0, 0.15)';
        x.style.color = '#0f0';
        x.style.pointerEvents = 'none';
        x.title = `Ally faction (${allyName}) – confirm before attacking`;
        attackBtn.appendChild(x);

        // Confirm dialog allowing proceed
        const onAttemptAttack = (e) => {
            e.preventDefault();
            e.stopPropagation();

            const proceed = confirm(
                `This player is in an allied faction (${allyName}).\n\nAre you sure you want to attack?`
            );
            if (!proceed) return;

            const href = attackBtn.getAttribute('href');
            if (!href) return;

            // Respect modifier keys / middle click
            if (e.metaKey || e.ctrlKey || e.button === 1) {
                window.open(href, '_blank');
            } else {
                window.location.href = href;
            }
        };

        attackBtn.addEventListener('click', onAttemptAttack, { capture: true });
    }

    async function init() {
        // Wait for either: faction link appears OR just proceed after a beat
        await waitForSelector('a[href*="/factions.php?step=profile&ID="]', { timeout: 5000 });
        const factionId = getFactionId();

        const allyObj = Allies.find(a => String(a.id) === String(factionId));
        const isAlly = !!allyObj;

        // Log for debugging
        const attackBtnNow = document.querySelector('a.profile-button-attack');
        const userId = getUserIdFromAttackBtn(attackBtnNow);
        console.log(`NPO FF: User:${userId} Faction:${factionId} IsAlly:${isAlly}`);

        if (!isAlly) return;

        // Ensure we catch the attack button even if it renders later
        const attackBtn = await waitForSelector('a.profile-button-attack', { timeout: 8000 });
        if (!attackBtn) return;

        decorateAndIntercept(attackBtn, factionId, allyObj.name);
    }

    // Run at document-end, plus handle full load as a fallback
    init();
    window.addEventListener('load', init, { once: true });
})();

QingJ © 2025

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