OC Role Display

Dynamically numbers duplicate OC roles based on slot order

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

  1. // ==UserScript==
  2. // @name OC Role Display
  3. // @namespace http://tampermonkey.net/
  4. // @version 1.2.0
  5. // @description Dynamically numbers duplicate OC roles based on slot order
  6. // @author Allenone [2033011]
  7. // @match https://www.torn.com/factions.php?step=your*
  8. // @icon https://www.google.com/s2/favicons?sz=64&domain=torn.com
  9. // @grant GM_info
  10. // @license MIT
  11. // ==/UserScript==
  12.  
  13. (async function() {
  14. 'use strict';
  15.  
  16. let globalObserver = null;
  17. let processing = false;
  18. const roleMappings = {};
  19. const debounceDelay = 200;
  20.  
  21. const requestIdleCallback = window.requestIdleCallback || function(callback) {
  22. return setTimeout(callback, 200);
  23. };
  24.  
  25. function processScenario(element) {
  26. const ocName = element.querySelector('.panelTitle___aoGuV')?.innerText.trim() || "Unknown";
  27. const slots = element.querySelectorAll('.wrapper___Lpz_D');
  28.  
  29. if (!roleMappings[ocName]) {
  30. const slotsWithPosition = Array.from(slots).map(slot => {
  31. const slotFiberKey = Object.keys(slot).find(key => key.startsWith("__reactFiber$"));
  32. if (!slotFiberKey) return null;
  33.  
  34. const fiberNode = slot[slotFiberKey];
  35. const positionKey = fiberNode.return.key.replace('slot-', '');
  36. const positionNumber = parseInt(positionKey.match(/P(\d+)/)?.[1] || 0, 10);
  37.  
  38. return { slot, positionNumber };
  39. }).filter(Boolean);
  40.  
  41. slotsWithPosition.sort((a, b) => a.positionNumber - b.positionNumber);
  42.  
  43. const originalNames = slotsWithPosition.map(({ slot }) => {
  44. return slot.querySelector('.title___UqFNy')?.innerText.trim() || "Unknown";
  45. });
  46.  
  47. const frequencyMap = originalNames.reduce((acc, name) => {
  48. acc[name] = (acc[name] || 0) + 1;
  49. return acc;
  50. }, {});
  51.  
  52. const displayNames = [];
  53. const countTracker = {};
  54.  
  55. originalNames.forEach(name => {
  56. if (frequencyMap[name] > 1) {
  57. countTracker[name] = (countTracker[name] || 0) + 1;
  58. displayNames.push(`${name} ${countTracker[name]}`);
  59. } else {
  60. displayNames.push(name);
  61. }
  62. });
  63.  
  64. roleMappings[ocName] = displayNames;
  65. }
  66.  
  67. slots.forEach(slot => {
  68. const slotFiberKey = Object.keys(slot).find(key => key.startsWith("__reactFiber$"));
  69. if (!slotFiberKey) return;
  70.  
  71. const fiberNode = slot[slotFiberKey];
  72. const positionKey = fiberNode.return.key.replace('slot-', '');
  73. const positionNumber = parseInt(positionKey.match(/P(\d+)/)?.[1] || 0, 10);
  74. const roleIndex = positionNumber - 1;
  75. const displayName = roleMappings[ocName][roleIndex];
  76.  
  77. const roleElement = slot.querySelector('.title___UqFNy');
  78. if (displayName && roleElement && roleElement.innerText !== displayName) {
  79. roleElement.innerText = displayName;
  80. }
  81. });
  82. }
  83.  
  84. function doOnHashChange() {
  85. if (processing) return;
  86. processing = true;
  87.  
  88. requestIdleCallback(() => {
  89. try {
  90. const ocElements = document.querySelectorAll('.wrapper___U2Ap7:not(.role-processed)');
  91. ocElements.forEach(element => {
  92. element.classList.add('role-processed');
  93. processScenario(element);
  94. });
  95. } finally {
  96. processing = false;
  97. }
  98. });
  99. }
  100.  
  101. function observeButtonContainer() {
  102. let buttonContainer = document.querySelector('.buttonsContainer___aClaa');
  103. if (buttonContainer) {
  104. buttonContainer.addEventListener('click', () => {
  105. document.querySelectorAll('.wrapper___U2Ap7').forEach(el => {
  106. el.classList.remove('role-processed');
  107. });
  108. doOnHashChange();
  109. });
  110. } else {
  111. setTimeout(observeButtonContainer, 500);
  112. }
  113. }
  114.  
  115. function setupHashChangeListener() {
  116. window.addEventListener('hashchange', () => {
  117. document.querySelectorAll('.wrapper___U2Ap7.role-processed').forEach(el => {
  118. el.classList.remove('role-processed');
  119. });
  120. doOnHashChange();
  121. });
  122. }
  123.  
  124. function initializeScript() {
  125. if (globalObserver) globalObserver.disconnect();
  126.  
  127. const targetNode = document.querySelector('#factionCrimes-root') || document.body;
  128.  
  129. globalObserver = new MutationObserver(debounce(() => {
  130. if (!document.querySelector('.wrapper___U2Ap7')) return;
  131. doOnHashChange();
  132. }, debounceDelay));
  133.  
  134. globalObserver.observe(targetNode, {
  135. childList: true,
  136. subtree: true,
  137. attributes: false,
  138. characterData: false
  139. });
  140.  
  141. doOnHashChange();
  142. observeButtonContainer();
  143. setupHashChangeListener();
  144. }
  145.  
  146. function debounce(func, wait) {
  147. let timeout;
  148. return function(...args) {
  149. clearTimeout(timeout);
  150. timeout = setTimeout(() => func.apply(this, args), wait);
  151. };
  152. }
  153.  
  154. if (document.readyState === 'complete') {
  155. initializeScript();
  156. } else {
  157. window.addEventListener('load', initializeScript);
  158. }
  159. })();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址