OC Role Display

Shows role positions with mobile scaling support

目前為 2025-03-06 提交的版本,檢視 最新版本

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 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);
    }
})();