Show Weapon Bonuses on Market (No API needed)

Shows the weapon bonuses for all applicable items on the item market

目前为 2025-01-06 提交的版本。查看 最新版本

  1. // ==UserScript==
  2. // @name Show Weapon Bonuses on Market (No API needed)
  3. // @namespace http://tampermonkey.net/
  4. // @version 0.11
  5. // @description Shows the weapon bonuses for all applicable items on the item market
  6. // @author Weav3r
  7. // @match https://www.torn.com/page.php?sid=ItemMarket*
  8. // @license MIT
  9. // @run-at document-end
  10. // ==/UserScript==
  11.  
  12. //////// GLOBAL VARIABLES ////////
  13. const GLOBAL_STATE = {
  14. userSettings: {
  15. highlightItemColors: true,
  16. },
  17. };
  18.  
  19. // To prevent processing the same item multiple times
  20. const processedItems = new WeakSet();
  21.  
  22. // Cache to store already extracted bonus percentages based on bonus elements
  23. const bonusPercentageCache = new WeakMap();
  24.  
  25. //////// VIEW ////////
  26. const stylesheet = `
  27. <style>
  28. .enhanced-item--yellow {
  29. --item-bgc: rgba(252, 247, 94, 0.1);
  30. --item-outline: #fcf75e;
  31. }
  32.  
  33. .enhanced-item--red {
  34. --item-bgc: rgba(255, 0, 0, 0.1);
  35. --item-outline: #ff0000;
  36. }
  37.  
  38. .enhanced-item--orange {
  39. --item-bgc: rgba(255, 165, 0, 0.1);
  40. --item-outline: #ffa500;
  41. }
  42.  
  43. .enhanced-item {
  44. background: var(--item-bgc);
  45. outline: 2px solid var(--item-outline);
  46. outline-offset: -2px;
  47. border-radius: 5px;
  48. }
  49.  
  50. .bonus-row {
  51. display: block;
  52. font-size: 0.85em;
  53. color: #777;
  54. margin: 2px 0;
  55. line-height: 1.2;
  56. }
  57.  
  58. .bonus-item {
  59. display: inline-block;
  60. margin-right: 8px;
  61. font-weight: bold; /* Makes the text bold */
  62. font-style: italic; /* Makes the text italic */
  63. }
  64.  
  65. .bonus-item:not(:last-child)::after {
  66. content: '•';
  67. margin-left: 8px;
  68. }
  69. </style>
  70. `;
  71.  
  72. function renderStylesheet() {
  73. if (!document.getElementById('item-enhancement-styles')) {
  74. const styleEl = document.createElement('style');
  75. styleEl.id = 'item-enhancement-styles';
  76. styleEl.textContent = stylesheet;
  77. document.head.appendChild(styleEl);
  78. console.log('Item Enhancement Stylesheet added.');
  79. }
  80. }
  81.  
  82. function capitalize(text) {
  83. if (!text) return '';
  84. return text.charAt(0).toUpperCase() + text.slice(1);
  85. }
  86.  
  87. // Helper function to simulate hover and extract percentage
  88. function getBonusPercentage(bonusEl, bonusName) {
  89. return new Promise((resolve) => {
  90. if (!bonusEl) {
  91. console.warn(`Bonus element for "${bonusName}" not found.`);
  92. return resolve('');
  93. }
  94.  
  95. // If the percentage for this bonus element is already cached, return it
  96. if (bonusPercentageCache.has(bonusEl)) {
  97. const cachedPercentage = bonusPercentageCache.get(bonusEl);
  98. console.log(`Percentage for "${bonusName}" fetched from cache: ${cachedPercentage}`);
  99. return resolve(cachedPercentage);
  100. }
  101.  
  102. // Function to handle tooltip extraction
  103. const extractPercentage = () => {
  104. const tooltipId = bonusEl.getAttribute('aria-describedby');
  105. if (!tooltipId) {
  106. console.warn(`No aria-describedby found for bonus "${bonusName}".`);
  107. return false;
  108. }
  109.  
  110. const tooltipEl = document.getElementById(tooltipId);
  111. if (!tooltipEl) {
  112. console.warn(`Tooltip element with id "${tooltipId}" not found for bonus "${bonusName}".`);
  113. return false;
  114. }
  115.  
  116. const tooltipText = tooltipEl.innerText;
  117. if (!tooltipText.toLowerCase().includes(bonusName.toLowerCase())) {
  118. console.warn(`Tooltip text does not include the bonus name "${bonusName}".`);
  119. return false;
  120. }
  121.  
  122. // Extract percentage using regex (e.g., "16% chance", "128% increased")
  123. const match = tooltipText.match(/(\d+%)/);
  124. if (match && match[1]) {
  125. const percentage = match[1];
  126. bonusPercentageCache.set(bonusEl, percentage); // Cache the result
  127. console.log(`Extracted percentage for "${bonusName}": ${percentage}`);
  128. resolve(percentage);
  129. return true;
  130. } else {
  131. console.warn(`No percentage found in tooltip for bonus "${bonusName}".`);
  132. resolve('');
  133. return true;
  134. }
  135. };
  136.  
  137. // Simulate mouseenter to trigger tooltip
  138. const mouseEnterEvent = new Event('mouseenter', { bubbles: true });
  139. bonusEl.dispatchEvent(mouseEnterEvent);
  140. console.log(`Simulated mouseenter for bonus "${bonusName}".`);
  141.  
  142. // Polling mechanism to wait for tooltip to appear and contain the correct bonus name
  143. const startTime = Date.now();
  144. const maxWait = 300; // Maximum wait time of 300ms
  145. const intervalTime = 10; // Check every 10ms
  146.  
  147. const interval = setInterval(() => {
  148. const isExtracted = extractPercentage();
  149. if (isExtracted) {
  150. clearInterval(interval);
  151. // Simulate mouseleave to hide the tooltip
  152. const mouseLeaveEvent = new Event('mouseleave', { bubbles: true });
  153. bonusEl.dispatchEvent(mouseLeaveEvent);
  154. console.log(`Simulated mouseleave for bonus "${bonusName}".`);
  155. } else if (Date.now() - startTime > maxWait) {
  156. // Timed out
  157. clearInterval(interval);
  158. console.warn(`Timed out extracting percentage for bonus "${bonusName}".`);
  159. // Simulate mouseleave to hide the tooltip
  160. const mouseLeaveEvent = new Event('mouseleave', { bubbles: true });
  161. bonusEl.dispatchEvent(mouseLeaveEvent);
  162. resolve('');
  163. }
  164. }, intervalTime);
  165. });
  166. }
  167.  
  168. //////// MAIN ////////
  169. async function processItem(itemEl) {
  170. if (!itemEl || processedItems.has(itemEl)) return;
  171.  
  172. const bonusElements = itemEl.querySelectorAll('.bonuses___a8gmz i');
  173. if (bonusElements.length === 0) return;
  174.  
  175. const titleContainer = itemEl.querySelector('.title___bQI0h');
  176. const priceElement = titleContainer?.querySelector('.priceAndTotal___eEVS7');
  177. if (!titleContainer || !priceElement) return;
  178.  
  179. // Create bonus-row only if it doesn't exist
  180. let bonusRow = titleContainer.querySelector('.bonus-row');
  181. if (!bonusRow) {
  182. bonusRow = document.createElement('div');
  183. bonusRow.className = 'bonus-row';
  184. } else {
  185. // If bonus-row exists, clear its content to update
  186. bonusRow.innerHTML = '';
  187. }
  188.  
  189. // Process bonuses sequentially to ensure correct tooltip association
  190. for (const bonus of bonusElements) {
  191. // Extract bonus name from aria-label
  192. const bonusName = bonus.getAttribute('aria-label');
  193. if (!bonusName) {
  194. console.warn('Bonus element missing aria-label attribute.');
  195. continue;
  196. }
  197.  
  198. // Get percentage by simulating hover
  199. const percentage = await getBonusPercentage(bonus, bonusName);
  200.  
  201. const displayText = percentage ? `${percentage} ${bonusName}` : bonusName;
  202.  
  203. const bonusSpan = document.createElement('span');
  204. bonusSpan.className = 'bonus-item';
  205. bonusSpan.textContent = displayText;
  206. bonusRow.appendChild(bonusSpan);
  207. }
  208.  
  209. // Append the bonus-row to the titleContainer before the priceElement
  210. titleContainer.insertBefore(bonusRow, priceElement);
  211. console.log(`Appended bonus-row for item "${titleContainer.querySelector('.name___ukdHN').innerText}".`);
  212.  
  213. // Add highlighting based on glow color
  214. if (GLOBAL_STATE.userSettings.highlightItemColors) {
  215. const imageWrapper = itemEl.querySelector('.imageWrapper___RqvUg');
  216. if (imageWrapper) {
  217. // Extract glow color from class (e.g., "glow-yellow-border")
  218. const glowClass = [...imageWrapper.classList].find(cls => cls.startsWith('glow-'));
  219. if (glowClass) {
  220. // Normalize the class by removing '-border' to extract the color
  221. const normalizedClass = glowClass.replace('-border', '');
  222. // Extract color using regex
  223. const colorMatch = normalizedClass.match(/glow-([a-z]+)/i);
  224. if (colorMatch && colorMatch[1]) {
  225. const color = colorMatch[1].toLowerCase();
  226. // Define valid colors
  227. const validColors = ['yellow', 'red', 'orange']; // Extend this array with more colors if needed
  228. if (validColors.includes(color)) {
  229. itemEl.classList.add('enhanced-item', `enhanced-item--${color}`);
  230. console.log(`Added highlighting class: enhanced-item--${color} for item "${titleContainer.querySelector('.name___ukdHN').innerText}".`);
  231. } else {
  232. console.warn(`Color "${color}" not defined in CSS. Skipping highlight for item "${titleContainer.querySelector('.name___ukdHN').innerText}".`);
  233. }
  234. } else {
  235. console.warn(`Failed to extract color from glow class "${normalizedClass}" for item "${titleContainer.querySelector('.name___ukdHN').innerText}".`);
  236. }
  237. } else {
  238. console.warn(`No glow class found on imageWrapper for item "${titleContainer.querySelector('.name___ukdHN').innerText}".`);
  239. }
  240. } else {
  241. console.warn(`No imageWrapper found for item "${titleContainer.querySelector('.name___ukdHN').innerText}".`);
  242. }
  243. }
  244.  
  245. // Mark this item as processed
  246. processedItems.add(itemEl);
  247. console.log(`Processed item: "${titleContainer.querySelector('.name___ukdHN').innerText}".`);
  248. }
  249.  
  250. async function processExistingItems() {
  251. const itemList = document.querySelector('.itemList___u4Hg1');
  252. if (!itemList) return;
  253.  
  254. const items = itemList.querySelectorAll('.itemTile___cbw7w');
  255. for (const item of items) {
  256. await processItem(item);
  257. }
  258. }
  259.  
  260. (function() {
  261. renderStylesheet();
  262. processExistingItems();
  263.  
  264. // Set up a single MutationObserver to watch for new items anywhere in the document
  265. const observer = new MutationObserver(async (mutations) => {
  266. for (const mutation of mutations) {
  267. // Check if new nodes are added
  268. if (mutation.type === 'childList' && mutation.addedNodes.length > 0) {
  269. for (const node of mutation.addedNodes) {
  270. if (node.nodeType !== Node.ELEMENT_NODE) continue;
  271.  
  272. // If the added node is an item list, process its items
  273. if (node.matches('.itemList___u4Hg1')) {
  274. const items = node.querySelectorAll('.itemTile___cbw7w');
  275. for (const item of items) {
  276. await processItem(item);
  277. }
  278. }
  279.  
  280. // If the added node is an individual item, process it
  281. if (node.matches && node.matches('.itemTile___cbw7w')) {
  282. await processItem(node);
  283. }
  284.  
  285. // Additionally, check within the added node for any items
  286. const nestedItems = node.querySelectorAll('.itemTile___cbw7w');
  287. for (const item of nestedItems) {
  288. await processItem(item);
  289. }
  290. }
  291. }
  292. }
  293. });
  294.  
  295. observer.observe(document.body, {
  296. childList: true,
  297. subtree: true
  298. });
  299.  
  300. console.log('Item Display Enhancement script initialized.');
  301. })();

QingJ © 2025

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