OC Role Display

Shows role positions with mobile scaling support

目前为 2025-03-06 提交的版本。查看 最新版本

// ==UserScript==
// @name         OC Role Display
// @namespace    http://tampermonkey.net/
// @version      1.0.4
// @description  Shows role positions with mobile scaling support
// @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.xmlHttpRequest
// @grant        GM_info
// @connect      tornprobability.com
// @license      MIT
// ==/UserScript==

(async function() {
    'use strict';

    let roleData = null;
    let globalObserver = null;
    let processing = false;
    const debounceDelay = 200; // ms

    // Polyfill for requestIdleCallback for browsers that don't support it (common on mobile)
    const requestIdleCallback = window.requestIdleCallback || function(callback) {
        return setTimeout(callback, 200);
    };

    // Fetch role names from API
    try {
        roleData = await RoleNames();
    } catch (error) {
        console.error('Error fetching role data:', error);
        return;
    }

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

        requestIdleCallback(() => {
            try {
                // Use custom marker class "role-processed" instead of "processed" so as not to conflict with mobile styles
                const ocElements = document.querySelectorAll('.wrapper___U2Ap7:not(.role-processed)');
                if (ocElements.length === 0) return;

                for (let element of ocElements) {
                    element.classList.add('role-processed');
                    const reactFiberKey = Object.keys(element).find(key => key.startsWith("__reactFiber$"));
                    if (!reactFiberKey) continue;

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

                    for (let slot of slots) {
                        const slotFiberKey = Object.keys(slot).find(key => key.startsWith("__reactFiber$"));
                        if (!slotFiberKey) continue;

                        const roleName = slot.querySelector('.title___UqFNy');
                        if (!roleName) continue;

                        try {
                            const fiberNode = slot[slotFiberKey];
                            const key = fiberNode.return.key;
                            const roleText = getOCRoles(ocName, key);

                            if (roleText && roleName.innerText !== roleText) {
                                roleName.innerText = roleText;
                            }
                        } catch (error) {
                            console.error("Error accessing React properties:", error);
                        }
                    }
                }
            } finally {
                processing = false;
            }
        });
    }

    function getOCRoles(name, position) {
        if (!roleData) return;
        const ocRoles = roleData[name];
        if (!ocRoles) return;
        const positionKey = position.replace('slot-', '');
        return ocRoles[positionKey];
    }

    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 waitForElementsToReload(selector) {
        return new Promise(resolve => {
            const tempObserver = new MutationObserver(() => {
                if (document.querySelector(selector)) {
                    tempObserver.disconnect();
                    resolve();
                }
            });

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

    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);
        };
    }

    function RoleNames() {
        return new Promise((resolve, reject) => {
            GM.xmlHttpRequest({
                method: 'GET',
                url: 'https://tornprobability.com:3000/GetRoleNames',
                headers: { 'Content-Type': 'application/json' },
                onload: (response) => {
                    try {
                        resolve(JSON.parse(response.responseText));
                    } catch (err) {
                        reject(err);
                    }
                },
                onerror: (err) => reject(err)
            });
        });
    }

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