OC Role Display

Shows role positions with mobile scaling support

06.03.2025 itibariyledir. En son verisyonu görün.

Bu betiği kurabilmeniz için Tampermonkey, Greasemonkey ya da Violentmonkey gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

Bu betiği yüklemek için Tampermonkey gibi bir uzantı yüklemeniz gerekir.

Bu betiği kurabilmeniz için Tampermonkey ya da Violentmonkey gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

Bu betiği kurabilmeniz için Tampermonkey ya da Userscripts gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

Bu betiği indirebilmeniz için ayrıca Tampermonkey gibi bir eklenti kurmanız gerekmektedir.

Bu komut dosyasını yüklemek için bir kullanıcı komut dosyası yöneticisi uzantısı yüklemeniz gerekecek.

(Zaten bir kullanıcı komut dosyası yöneticim var, kurmama izin verin!)

Bu stili yüklemek için Stylus gibi bir uzantı yüklemeniz gerekir.

Bu stili yüklemek için Stylus gibi bir uzantı kurmanız gerekir.

Bu stili yükleyebilmek için Stylus gibi bir uzantı yüklemeniz gerekir.

Bu stili yüklemek için bir kullanıcı stili yöneticisi uzantısı yüklemeniz gerekir.

Bu stili yüklemek için bir kullanıcı stili yöneticisi uzantısı kurmanız gerekir.

Bu stili yükleyebilmek için bir kullanıcı stili yöneticisi uzantısı yüklemeniz gerekir.

(Zateb bir user-style yöneticim var, yükleyeyim!)

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