OC Role Display

Shows role positions with mobile scaling support

当前为 2025-03-06 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==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);
    }
})();