Torn Item Market Helper

Items market 2.0 helper

  1. // ==UserScript==
  2. // @name Torn Item Market Helper
  3. // @namespace Nurv.IronNerd.me
  4. // @version 0.7
  5. // @description Items market 2.0 helper
  6. // @author Nurv [669537]
  7. // @match https://www.torn.com/page.php?sid=ItemMarket*
  8. // @grant GM_xmlhttpRequest
  9. // @grant GM_setValue
  10. // @grant GM_getValue
  11. // @run-at document-start
  12. // @icon https://img.icons8.com/?size=100&id=NrG0tlrTAiph&format=png&color=000000
  13. // @license Copyright IronNerd.me
  14. // ==/UserScript==
  15.  
  16. (function () {
  17. 'use strict';
  18.  
  19. if (window.tornBuyMugInitialized) return;
  20. window.tornBuyMugInitialized = true;
  21.  
  22. const BACKEND_BASE_URL = "https://www.ironnerd.me";
  23. const CACHE_DURATION = 5000;
  24. const dataCache = {};
  25. let currentPopups = [];
  26. const processedRows = new Set();
  27.  
  28. function setSetting(name, value) {
  29. GM_setValue(name, value);
  30. }
  31. function getSetting(name) {
  32. return GM_getValue(name, null);
  33. }
  34.  
  35. function addGlobalStyles() {
  36. const css = `
  37. .mugButton {
  38. cursor: pointer;
  39. margin-right: 10px;
  40. display: inline-flex;
  41. align-items: center;
  42. justify-content: center;
  43. color: white;
  44. border-radius: 50%;
  45. width: 30px;
  46. height: 30px;
  47. z-index: 1500 !important;
  48. box-shadow: 0 2px 4px rgba(0,0,0,0.2);
  49. position: relative;
  50. }
  51. .mugButton svg {
  52. width: 30px;
  53. height: 30px;
  54. }
  55. .mugPanel {
  56. display: none;
  57. position: fixed;
  58. top: 50%;
  59. left: 50%;
  60. transform: translate(-50%, -50%);
  61. width: 300px;
  62. background: linear-gradient(to bottom right, #ffffff, #f7f7f7);
  63. border: 1px solid #ccc;
  64. border-radius: 8px;
  65. padding: 20px;
  66. box-shadow: 0 8px 20px rgba(0,0,0,0.2);
  67. z-index: 3000;
  68. font-size: 14px;
  69. font-family: Arial, sans-serif;
  70. color: #333;
  71. }
  72. .mugPanel label {
  73. display: block;
  74. margin-bottom: 5px;
  75. font-weight: bold;
  76. color: #222;
  77. }
  78. .mugPanel input {
  79. width: 100%;
  80. margin-bottom: 15px;
  81. padding: 6px;
  82. border: 1px solid #ccc;
  83. border-radius: 4px;
  84. font-size: 13px;
  85. }
  86. .mugPanel .closeButton {
  87. position: absolute;
  88. top: 10px;
  89. right: 10px;
  90. background: #d9534f;
  91. color: white;
  92. border: none;
  93. border-radius: 50%;
  94. width: 25px;
  95. height: 25px;
  96. cursor: pointer;
  97. font-size: 16px;
  98. line-height: 25px;
  99. text-align: center;
  100. }
  101. .mugPanel button.saveSettings {
  102. background: #28a745;
  103. color: white;
  104. padding: 8px 12px;
  105. border: none;
  106. border-radius: 4px;
  107. cursor: pointer;
  108. margin-top: 10px;
  109. font-weight: bold;
  110. }
  111. .mugPanel button.saveSettings:hover {
  112. background: #218838;
  113. }
  114. .infoIcon {
  115. margin-left: 5px;
  116. cursor: pointer;
  117. display: inline-flex;
  118. align-items: center;
  119. justify-content: center;
  120. background: #007bff;
  121. color: white;
  122. border-radius: 50%;
  123. width: 16px;
  124. height: 16px;
  125. font-size: 12px;
  126. text-align: center;
  127. line-height: 16px;
  128. z-index: 1000 !important;
  129. }
  130. .infoPopup {
  131. position: absolute;
  132. color: black;
  133. border: 1px solid #ccc;
  134. padding: 10px;
  135. border-radius: 5px;
  136. box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
  137. font-size: 12px;
  138. z-index: 2000;
  139. display: none;
  140. background-color: white;
  141. }
  142. .infoPopup.visible {
  143. display: block !important;
  144. }
  145. .popupCloseButton {
  146. position: absolute;
  147. top: 5px;
  148. right: 5px;
  149. background: #d9534f;
  150. color: white;
  151. border: none;
  152. border-radius: 50%;
  153. width: 20px;
  154. height: 20px;
  155. font-size: 14px;
  156. line-height: 20px;
  157. text-align: center;
  158. cursor: pointer;
  159. }
  160. .popupCloseButton:hover {
  161. background: #c9302c;
  162. }
  163. `;
  164. const styleElement = document.createElement('style');
  165. styleElement.textContent = css;
  166. document.head.appendChild(styleElement);
  167. }
  168.  
  169. function createMugButtonAndPanel() {
  170. if (document.querySelector('.mugButton')) return;
  171.  
  172. const mugButton = document.createElement('div');
  173. mugButton.className = 'mugButton';
  174. mugButton.innerHTML = `<svg fill="#ffffff" height="256px" width="256px" version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="-51.2 -51.2 614.40 614.40" xml:space="preserve" transform="matrix(-1, 0, 0, 1, 0, 0)">
  175. <g id="SVGRepo_bgCarrier" stroke-width="0"></g>
  176. <g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round" stroke="#ff0505" stroke-width="13.311974000000001">
  177. <g><g><path d="M182.705,70.577c-1.157-7.628-8.271-12.872-15.91-11.717c-3.187,0.484-6.377,0.844-9.566,1.137l-3.845-33.66
  178. c-0.32-2.804-2.805-4.849-5.618-4.625l-16.658,1.326c-4.652,0.37-9.326-0.326-13.668-2.036l-15.549-6.124
  179. c-2.626-1.034-5.599,0.199-6.723,2.787L81.681,48.743c-2.966-1.209-5.913-2.485-8.82-3.875c-6.962-3.33-15.302-0.389-18.633,6.571
  180. c-3.33,6.96-0.387,15.301,6.572,18.632c5.719,2.736,11.545,5.157,17.451,7.294c-0.463,1.6-0.836,3.245-1.09,4.937
  181. c-3.109,20.68,11.135,39.963,31.815,43.071c20.68,3.109,39.963-11.135,43.071-31.815c0.264-1.751,0.392-3.49,0.413-5.212
  182. c6.175-0.328,12.356-0.927,18.529-1.864C178.616,85.327,183.862,78.205,182.705,70.577z"></path></g></g>
  183. <g><g><path d="M293.065,163.552c-7.081-0.336-22.746-1.078-38.84-1.841v-8.307c0-2.279-1.847-4.126-4.127-4.126h-8.74
  184. c-2.279,0-4.127,1.848-4.127,4.126c0,2.777,0,4.715,0,7.501c-0.061-0.002-0.122-0.006-0.184-0.009
  185. c0.068-0.023-1.659-0.05-2.815,0.164c-8.75,1.382-14.32,10.182-11.813,18.777l-65.756,23.648l-39.853-38.6l29.359,16.761
  186. l0.75-4.992c2.261-15.041-7.307-28.948-21.37-31.062l-33.314-5.663c-5.397-0.918-12.278-1.024-18.744,3.721L6.488,196.082
  187. c-5.079,3.974-7.455,10.491-6.124,16.802l18.452,87.497c1.678,7.959,8.702,13.42,16.524,13.42c6.01,0,11.756-3.192,14.827-8.782
  188. l9.589,77.436L33.05,469.738c-3.468,11.334,2.91,23.333,14.243,26.8c11.333,3.468,23.333-2.907,26.801-14.244l28.044-91.658
  189. c0.882-2.884,1.147-5.921,0.777-8.916l-8.74-70.583l2.063,0.342l27.352,71.832l-4.97,91.54
  190. c-0.642,11.835,8.432,21.951,20.267,22.593c11.716,0.666,21.946-8.344,22.593-20.265l5.217-96.088
  191. c0.162-2.997-0.305-5.996-1.373-8.801c-25.131-65.865-34.333-89.652-34.333-89.652l4.297-28.584l-41.994-64.881l47.417,45.926
  192. c4.665,4.52,11.455,5.932,17.482,3.765l84.144-30.263c6.118-2.201,10.177-7.566,11.017-13.586c2.235,2.491,5.89,3.169,8.883,1.542
  193. c3.535-1.919,4.843-6.34,2.924-9.875l-3.049-5.613c2.083-0.099,4.132-0.196,6.134-0.29c10.621-0.504,19.843-0.941,24.817-1.177
  194. c2.202-0.105,3.931-1.915,3.931-4.119v-7.815C296.996,165.469,295.264,163.657,293.065,163.552z M46.079,265.805l-10.458-49.587
  195. l20.392-15.957L46.079,265.805z"></path></g></g>
  196. <g><g><path d="M490.36,141.071c-2.138-13.934-14.156-23.324-26.841-20.975l-42.061,7.788c-12.685,2.349-21.235,15.549-19.096,29.483
  197. l4.07,26.512l8.565-1.089l10.237-13.234l-14.362,29.01l-36.041,4.174c1.474-3.934-1.448-8.044-5.568-8.044h-27.181
  198. c-4.306,0-7.194,4.442-5.444,8.378l2.728,6.137c-5.724,4.003-9.127,10.938-8.266,18.363c0.508,4.393,2.431,8.276,5.258,11.252
  199. c-3.445,3.809-7.005,7.992-10.375,12.378c-9.169,11.931-16.91,25.327-16.91,36.572c0,27.595,20.863,49.964,46.6,49.964
  200. s46.6-22.37,46.6-49.964c0-9.52-5.547-20.582-12.811-30.989c-3.634-5.206-7.695-10.244-11.711-14.848l48.242-5.587
  201. c6.612-0.766,12.386-4.828,15.34-10.794c3.401-6.871,28.329-57.221,31.892-64.417l-17.012,70.989
  202. c-2.054,10.326-10.459,18.678-19.509,19.845c-8.37,1.057,0,0-19.441,2.473c1.962,12.783-0.95-6.186,6.563,42.761l-0.102,178.021
  203. c0,12.292,9.965,22.256,22.257,22.256s22.257-9.965,22.257-22.256V290.155l17.312-3.206v188.279
  204. c0,7.379-2.026,14.283-5.543,20.197c2.835,1.31,5.982,2.061,9.309,2.061c12.292,0,22.257-9.965,22.257-22.256l0.426-193.178
  205. L490.36,141.071z M358.695,306.836v1.844c0,0.889-1.207,1.716-2.414,1.716c-1.399,0-2.416-0.827-2.416-1.716v-1.589
  206. c-7.627-0.255-13.855-4.195-13.855-8.262c0-2.161,1.906-5.338,4.322-5.338c2.669,0,4.83,3.75,9.533,4.576V287.77
  207. c-5.847-2.225-12.711-4.958-12.711-13.092c0-8.071,5.974-11.948,12.711-12.901v-1.779c0-0.89,1.017-1.716,2.416-1.716
  208. c1.207,0,2.414,0.827,2.414,1.716v1.588c3.941,0.128,11.377,1.145,11.377,5.53c0,1.716-1.144,5.212-3.941,5.212
  209. c-2.097,0-3.305-2.034-7.436-2.351v9.278c5.784,2.161,12.521,5.148,12.521,13.728C371.216,300.862,366.131,305.628,358.695,306.836z"></path></g></g>
  210. <g><g><circle cx="398.348" cy="78.006" r="37.885"></circle></g></g>
  211. <g><g><path d="M358.06,289.486v8.516c2.161-0.508,3.876-1.716,3.876-4.004C361.938,291.901,360.348,290.566,358.06,289.486z"></path></g></g>
  212. <g><g><path d="M350.434,273.724c0,1.844,1.652,2.987,4.068,4.004v-7.564C351.641,270.737,350.434,272.199,350.434,273.724z"></path></g></g>
  213. </g>
  214. </svg>`;
  215.  
  216. const mugPanel = document.createElement('div');
  217. mugPanel.className = 'mugPanel';
  218. mugPanel.innerHTML = `
  219. <button class="closeButton">&times;</button>
  220. <label>Enter Torn API Key:</label>
  221. <input type="text" id="apiKeyInput" placeholder="API Key" />
  222. <label>Mug Merits (0-10):</label>
  223. <input type="number" id="mugMeritsInput" placeholder="0 to 10" min="0" max="10" />
  224. <label>Plunder % (20% to 49%):</label>
  225. <input type="number" id="plunderInput" placeholder="Plunder %" min="20" max="49" step="0.01" />
  226. <label>Minimum Threshold ($):</label>
  227. <input type="number" id="thresholdInput" placeholder="Minimum Threshold" min="0" />
  228. <button class="saveSettings">Save</button>
  229. `;
  230.  
  231. mugPanel.querySelector('.closeButton').addEventListener('click', () => {
  232. mugPanel.style.display = 'none';
  233. });
  234.  
  235. const savedApiKey = getSetting('tornBuyMugApiKey');
  236. if (savedApiKey) mugPanel.querySelector('#apiKeyInput').value = savedApiKey;
  237. const savedMerits = getSetting('tornBuyMugMerits');
  238. if (savedMerits) mugPanel.querySelector('#mugMeritsInput').value = savedMerits;
  239. const savedPlunder = getSetting('tornBuyMugPlunder');
  240. if (savedPlunder) {
  241. mugPanel.querySelector('#plunderInput').value = parseFloat(savedPlunder).toFixed(2);
  242. }
  243. const savedThreshold = getSetting('tornBuyMugThreshold');
  244. if (savedThreshold) mugPanel.querySelector('#thresholdInput').value = savedThreshold;
  245.  
  246. mugPanel.querySelector('.saveSettings').addEventListener('click', function () {
  247. const apiKeyVal = mugPanel.querySelector('#apiKeyInput').value.trim();
  248. const mugMeritsVal = parseInt(mugPanel.querySelector('#mugMeritsInput').value.trim(), 10);
  249. let plunderInputVal = parseFloat(mugPanel.querySelector('#plunderInput').value.trim());
  250. const thresholdVal = parseInt(mugPanel.querySelector('#thresholdInput').value.trim(), 10);
  251.  
  252. if (!apiKeyVal) {
  253. alert("API Key cannot be empty.");
  254. return;
  255. }
  256. if (plunderInputVal === '' || parseFloat(plunderInputVal) === 0) {
  257. plunderInputVal = 0;
  258. } else {
  259. plunderInputVal = parseFloat(plunderInputVal);
  260. if (plunderInputVal < 20 || plunderInputVal > 50) {
  261. alert("Plunder percentage must be between 20% and 49%, or leave it empty if not using.");
  262. return;
  263. }
  264. }
  265. setSetting('tornBuyMugApiKey', apiKeyVal);
  266. setSetting('tornBuyMugMerits', isNaN(mugMeritsVal) ? 0 : Math.min(Math.max(mugMeritsVal, 0), 10));
  267. setSetting('tornBuyMugPlunder', plunderInputVal);
  268. setSetting('tornBuyMugThreshold', isNaN(thresholdVal) ? 0 : thresholdVal);
  269. alert("Settings saved successfully!");
  270. mugPanel.style.display = 'none';
  271. });
  272.  
  273. mugButton.addEventListener('click', () => {
  274. mugPanel.style.display = (mugPanel.style.display === 'none' || mugPanel.style.display === '') ? 'block' : 'none';
  275. });
  276.  
  277. const appHeader = document.querySelector('.appHeaderWrapper___uyPti .linksContainer___LiOTN');
  278. if (appHeader) {
  279. appHeader.prepend(mugPanel);
  280. appHeader.prepend(mugButton);
  281. }
  282. }
  283.  
  284. function waitForElements(selector, callback, maxAttempts = 10, interval = 500) {
  285. let attempts = 0;
  286. const check = () => {
  287. attempts++;
  288. const elements = document.querySelectorAll(selector);
  289. if (elements.length > 0) {
  290. callback(elements);
  291. } else if (attempts < maxAttempts) {
  292. setTimeout(check, interval);
  293. }
  294. };
  295. check();
  296. }
  297.  
  298. function extractUserId(href) {
  299. const match = href.match(/XID=(\d+)/);
  300. return match ? match[1] : null;
  301. }
  302.  
  303. function createInfoPopup(data, newCostPerItem) {
  304. const popup = document.createElement("div");
  305. popup.className = "infoPopup";
  306. popup.innerHTML = `
  307. <button class="popupCloseButton">×</button>
  308. <strong>Level:</strong> ${data.level}<br>
  309. <strong>Status:</strong> ${data.status}<br>
  310. <strong>Hospital:</strong> ${data.hospital_time}<br>
  311. <strong>Total Money:</strong> $${data.total_money.toLocaleString()}<br>
  312. ${data.clothing_note ? `<strong>${data.clothing_note}</strong><br>` : ""}
  313. <strong>Potential Mug:</strong> ~${data.mug_percentage.toFixed(2)}% $${data.potential_mug.toLocaleString()}<br>
  314. <strong>New Cost Per Item:</strong> $${newCostPerItem.toLocaleString()}
  315. `;
  316. popup.style.backgroundColor = data.background_color;
  317. const closeButton = popup.querySelector(".popupCloseButton");
  318. closeButton.addEventListener("click", () => {
  319. popup.remove();
  320. const index = currentPopups.indexOf(popup);
  321. if (index > -1) {
  322. currentPopups.splice(index, 1);
  323. }
  324. });
  325. return popup;
  326. }
  327.  
  328. function positionPopup(icon, popup) {
  329. const rect = icon.getBoundingClientRect();
  330. let top = rect.bottom + window.scrollY + 5;
  331. let left = rect.left + window.scrollX;
  332.  
  333. popup.style.visibility = 'hidden';
  334. popup.style.display = 'block';
  335. const popupHeight = popup.offsetHeight;
  336. const popupWidth = popup.offsetWidth;
  337. popup.style.display = '';
  338. popup.style.visibility = '';
  339.  
  340. if (rect.bottom + popupHeight + 5 > window.innerHeight) {
  341. top = rect.top + window.scrollY - popupHeight - 5;
  342. }
  343. if (left + popupWidth > window.innerWidth) {
  344. left = window.innerWidth - popupWidth - 5;
  345. }
  346. popup.style.top = `${top}px`;
  347. popup.style.left = `${left}px`;
  348. }
  349.  
  350. function closeAllPopups() {
  351. currentPopups.forEach(popup => popup.remove());
  352. currentPopups = [];
  353. }
  354.  
  355. function fetchUserData(apiKey, playerId, mugMerits, plunderPercent, totalMoney, threshold, available) {
  356. const requestData = {
  357. api_key: apiKey,
  358. player_id: playerId,
  359. mug_merits: mugMerits,
  360. plunder_percent: plunderPercent,
  361. total_money: totalMoney,
  362. threshold: threshold,
  363. available: available,
  364. };
  365. const cacheKey = `${apiKey}_${playerId}_${mugMerits}_${plunderPercent}_${totalMoney}_${threshold}_${available}`;
  366. const now = Date.now();
  367. if (dataCache[cacheKey] && (now - dataCache[cacheKey].timestamp < CACHE_DURATION)) {
  368. return Promise.resolve(dataCache[cacheKey].data);
  369. }
  370. return new Promise((resolve, reject) => {
  371. GM_xmlhttpRequest({
  372. method: "POST",
  373. url: `${BACKEND_BASE_URL}/api/torn-data`,
  374. headers: {
  375. "Content-Type": "application/json"
  376. },
  377. data: JSON.stringify(requestData),
  378. onload: (response) => {
  379. if (response.status === 200) {
  380. try {
  381. const data = JSON.parse(response.responseText);
  382. dataCache[cacheKey] = { data: data, timestamp: now };
  383. resolve(data);
  384. } catch (e) {
  385. reject("Invalid backend response");
  386. }
  387. } else {
  388. reject(`Backend error: ${response.status}`);
  389. }
  390. },
  391. onerror: () => {
  392. reject("Network error");
  393. },
  394. });
  395. });
  396. }
  397.  
  398. async function handleMugIconClick(totalMoney, quantity, threshold, sellerLink, icon) {
  399. const apiKey = getSetting("tornBuyMugApiKey");
  400. const mugMerits = parseInt(getSetting("tornBuyMugMerits") || "0", 10);
  401. const plunderPercent = parseFloat(getSetting("tornBuyMugPlunder") || 0);
  402. try {
  403. const playerId = extractUserId(sellerLink.href);
  404. const data = await fetchUserData(apiKey, playerId, mugMerits, plunderPercent, totalMoney, threshold, quantity);
  405. const newCostPerItem = Math.floor((totalMoney - data.potential_mug) / quantity);
  406. const popup = createInfoPopup(data, newCostPerItem);
  407. document.body.appendChild(popup);
  408. positionPopup(icon, popup);
  409. popup.classList.add("visible");
  410. currentPopups.push(popup);
  411. } catch (error) {
  412. console.error("Error fetching user data:", error);
  413. alert("Failed to fetch user data. Please check your API key.");
  414. }
  415. }
  416.  
  417. async function attachInfoIconForMarketRow(row) {
  418. if (processedRows.has(row) && row.querySelector(".infoIcon")) return;
  419. if (processedRows.has(row) && !row.querySelector(".infoIcon")) {
  420. processedRows.delete(row);
  421. }
  422. const honorElem = row.querySelector('.honorWrap___BHau4 a.linkWrap___ZS6r9');
  423. const priceElement = row.querySelector(".price___Uwiv2") || row.querySelector(".price___v8rRx");
  424. if (!honorElem || !priceElement) return;
  425. const price = parseInt(priceElement.textContent.replace("$", "").replace(/,/g, ""), 10);
  426. const availableText = row.querySelector(".available___xegv_")?.textContent.replace(/ available|,/g, "") ||
  427. row.querySelector(".available___jtANf")?.textContent.replace(/ available|,/g, "") || "0";
  428. const available = parseInt(availableText, 10);
  429. const totalMoney = price * available;
  430. const threshold = parseInt(getSetting("tornBuyMugThreshold") || "0", 10);
  431. if (totalMoney < threshold) return;
  432. if (row.querySelector(".infoIcon")) {
  433. processedRows.add(row);
  434. return;
  435. }
  436. const infoIcon = document.createElement("div");
  437. infoIcon.className = "infoIcon";
  438. infoIcon.textContent = "i";
  439. priceElement.parentNode.insertBefore(infoIcon, priceElement.nextSibling);
  440. infoIcon.addEventListener("click", (e) => {
  441. e.stopPropagation();
  442. closeAllPopups();
  443. handleMugIconClick(totalMoney, available, threshold, honorElem, infoIcon);
  444. });
  445. processedRows.add(row);
  446. }
  447.  
  448. async function attachInfoIconForBazaarRow(row) {
  449. if (processedRows.has(row) && row.querySelector(".infoIcon")) return;
  450. if (processedRows.has(row) && !row.querySelector(".infoIcon")) {
  451. processedRows.delete(row);
  452. }
  453. const cells = row.querySelectorAll("td");
  454. if (cells.length < 4) return;
  455. const priceText = cells[0].innerText;
  456. const quantityText = cells[1].innerText;
  457. const price = parseInt(priceText.replace("$", "").replace(/,/g, ""), 10);
  458. const quantity = parseInt(quantityText.replace(/,/g, ""), 10);
  459. const totalMoney = price * quantity;
  460. const threshold = parseInt(getSetting("tornBuyMugThreshold") || "0", 10);
  461. if (totalMoney < threshold) return;
  462. const sellerLink = cells[3].querySelector("a[href*='profiles.php?XID=']");
  463. if (!sellerLink) return;
  464. if (row.querySelector(".infoIcon")) {
  465. processedRows.add(row);
  466. return;
  467. }
  468. const infoIcon = document.createElement("div");
  469. infoIcon.className = "infoIcon";
  470. infoIcon.textContent = "i";
  471. cells[3].appendChild(infoIcon);
  472. infoIcon.addEventListener("click", (e) => {
  473. e.stopPropagation();
  474. closeAllPopups();
  475. handleMugIconClick(totalMoney, quantity, threshold, sellerLink, infoIcon);
  476. });
  477. processedRows.add(row);
  478. }
  479.  
  480. function processAllMarketRows() {
  481. const allRows = document.querySelectorAll('.rowWrapper___me3Ox, .sellerRow___Ca2pK');
  482. allRows.forEach(row => attachInfoIconForMarketRow(row));
  483. }
  484.  
  485. function observeMarketRows() {
  486. const container = document.querySelector('.sellerListWrapper___PN32N');
  487. if (!container) return;
  488. const observer = new MutationObserver((mutations) => {
  489. mutations.forEach(mutation => {
  490. mutation.addedNodes.forEach(node => {
  491. if (node.nodeType === 1) {
  492. if (node.matches('.rowWrapper___me3Ox, .sellerRow___Ca2pK')) {
  493. attachInfoIconForMarketRow(node);
  494. } else {
  495. const newRows = node.querySelectorAll?.('.rowWrapper___me3Ox, .sellerRow___Ca2pK');
  496. newRows?.forEach(row => attachInfoIconForMarketRow(row));
  497. }
  498. }
  499. });
  500. });
  501. });
  502. observer.observe(container, { childList: true, subtree: true });
  503. }
  504.  
  505. function processAllBazaarRows() {
  506. const bazaarRows = document.querySelectorAll('#fullListingsView table tbody tr, #topCheapestView table tbody tr');
  507. bazaarRows.forEach(row => attachInfoIconForBazaarRow(row));
  508. }
  509.  
  510. function observeBazaarRows() {
  511. const bazaarContainers = document.querySelectorAll('#fullListingsView, #topCheapestView');
  512. bazaarContainers.forEach(container => {
  513. const observer = new MutationObserver((mutations) => {
  514. mutations.forEach(mutation => {
  515. mutation.addedNodes.forEach(node => {
  516. if (node.nodeType === 1 && node.matches("table tbody tr")) {
  517. attachInfoIconForBazaarRow(node);
  518. } else if (node.nodeType === 1) {
  519. const newRows = node.querySelectorAll?.("table tbody tr");
  520. newRows?.forEach(row => attachInfoIconForBazaarRow(row));
  521. }
  522. });
  523. });
  524. });
  525. observer.observe(container, { childList: true, subtree: true });
  526. });
  527. }
  528.  
  529. function setupURLChangeListener() {
  530. const originalPushState = history.pushState;
  531. history.pushState = function() {
  532. originalPushState.apply(history, arguments);
  533. window.dispatchEvent(new Event('locationchange'));
  534. };
  535. window.addEventListener('popstate', () => {
  536. window.dispatchEvent(new Event('locationchange'));
  537. });
  538. window.addEventListener('hashchange', () => {
  539. window.dispatchEvent(new Event('locationchange'));
  540. });
  541. window.addEventListener('locationchange', () => {
  542. processedRows.clear();
  543. processAllMarketRows();
  544. });
  545. }
  546.  
  547. document.addEventListener('click', (e) => {
  548. if (!currentPopups.some(popup => popup.contains(e.target))) {
  549. closeAllPopups();
  550. }
  551. });
  552. window.addEventListener('load', () => {
  553. setTimeout(() => {
  554. addGlobalStyles();
  555. createMugButtonAndPanel();
  556. waitForElements('.rowWrapper___me3Ox, .sellerRow___Ca2pK', () => {
  557. processAllMarketRows();
  558. observeMarketRows();
  559. });
  560. waitForElements('#fullListingsView, #topCheapestView', () => {
  561. processAllBazaarRows();
  562. observeBazaarRows();
  563. });
  564. setupURLChangeListener();
  565. setInterval(processAllMarketRows, 2000);
  566. }, 1000);
  567. });
  568. })();