OC Role Display

Dynamically numbers duplicate OC roles based on slot order

// ==UserScript==
// @name         OC Role Display
// @namespace    http://tampermonkey.net/
// @version      1.2.0
// @description  Dynamically numbers duplicate OC roles based on slot order
// @author       Allenone [2033011]
// @match        https://www.torn.com/factions.php?step=your*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=torn.com
// @grant        GM_info
// @license      MIT
// ==/UserScript==

(async function() {
    'use strict';

    let globalObserver = null;
    let processing = false;
    const roleMappings = {};
    const debounceDelay = 200;

    const requestIdleCallback = window.requestIdleCallback || function(callback) {
        return setTimeout(callback, 200);
    };

    function processScenario(element) {
        const ocName = element.querySelector('.panelTitle___aoGuV')?.innerText.trim() || "Unknown";
        const slots = element.querySelectorAll('.wrapper___Lpz_D');

        if (!roleMappings[ocName]) {
            const slotsWithPosition = Array.from(slots).map(slot => {
                const slotFiberKey = Object.keys(slot).find(key => key.startsWith("__reactFiber$"));
                if (!slotFiberKey) return null;

                const fiberNode = slot[slotFiberKey];
                const positionKey = fiberNode.return.key.replace('slot-', '');
                const positionNumber = parseInt(positionKey.match(/P(\d+)/)?.[1] || 0, 10);

                return { slot, positionNumber };
            }).filter(Boolean);

            slotsWithPosition.sort((a, b) => a.positionNumber - b.positionNumber);

            const originalNames = slotsWithPosition.map(({ slot }) => {
                return slot.querySelector('.title___UqFNy')?.innerText.trim() || "Unknown";
            });

            const frequencyMap = originalNames.reduce((acc, name) => {
                acc[name] = (acc[name] || 0) + 1;
                return acc;
            }, {});

            const displayNames = [];
            const countTracker = {};

            originalNames.forEach(name => {
                if (frequencyMap[name] > 1) {
                    countTracker[name] = (countTracker[name] || 0) + 1;
                    displayNames.push(`${name} ${countTracker[name]}`);
                } else {
                    displayNames.push(name);
                }
            });

            roleMappings[ocName] = displayNames;
        }

        slots.forEach(slot => {
            const slotFiberKey = Object.keys(slot).find(key => key.startsWith("__reactFiber$"));
            if (!slotFiberKey) return;

            const fiberNode = slot[slotFiberKey];
            const positionKey = fiberNode.return.key.replace('slot-', '');
            const positionNumber = parseInt(positionKey.match(/P(\d+)/)?.[1] || 0, 10);
            const roleIndex = positionNumber - 1;
            const displayName = roleMappings[ocName][roleIndex];

            const roleElement = slot.querySelector('.title___UqFNy');
            if (displayName && roleElement && roleElement.innerText !== displayName) {
                roleElement.innerText = displayName;
            }
        });
    }

    function doOnHashChange() {
        if (processing) return;
        processing = true;

        requestIdleCallback(() => {
            try {
                const ocElements = document.querySelectorAll('.wrapper___U2Ap7:not(.role-processed)');
                ocElements.forEach(element => {
                    element.classList.add('role-processed');
                    processScenario(element);
                });
            } finally {
                processing = false;
            }
        });
    }

    function observeButtonContainer() {
        let buttonContainer = document.querySelector('.buttonsContainer___aClaa');
        if (buttonContainer) {
            buttonContainer.addEventListener('click', () => {
                document.querySelectorAll('.wrapper___U2Ap7').forEach(el => {
                    el.classList.remove('role-processed');
                });
                doOnHashChange();
            });
        } else {
            setTimeout(observeButtonContainer, 500);
        }
    }

    function setupHashChangeListener() {
        window.addEventListener('hashchange', () => {
            document.querySelectorAll('.wrapper___U2Ap7.role-processed').forEach(el => {
                el.classList.remove('role-processed');
            });
            doOnHashChange();
        });
    }

    function initializeScript() {
        if (globalObserver) globalObserver.disconnect();

        const targetNode = document.querySelector('#factionCrimes-root') || document.body;

        globalObserver = new MutationObserver(debounce(() => {
            if (!document.querySelector('.wrapper___U2Ap7')) return;
            doOnHashChange();
        }, debounceDelay));

        globalObserver.observe(targetNode, {
            childList: true,
            subtree: true,
            attributes: false,
            characterData: false
        });

        doOnHashChange();
        observeButtonContainer();
        setupHashChangeListener();
    }

    function debounce(func, wait) {
        let timeout;
        return function(...args) {
            clearTimeout(timeout);
            timeout = setTimeout(() => func.apply(this, args), wait);
        };
    }

    if (document.readyState === 'complete') {
        initializeScript();
    } else {
        window.addEventListener('load', initializeScript);
    }
})();

QingJ © 2025

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