Shows role positions with mobile scaling support
当前为
// ==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);
}
})();