您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
View items you are searching for in bazaars!
当前为
// ==UserScript== // @name Bazaar Item Search powered by IronNerd // @namespace [email protected] // @version 0.5.1 // @description View items you are searching for in bazaars! // @author Nurv [669537] // @match https://www.torn.com/page.php?sid=ItemMarket* // @grant GM_xmlhttpRequest // @grant GM_setValue // @grant GM_getValue // @run-at document-end // @license Copyright IronNerd.me // @connect ironnerd.me // ==/UserScript== (function () { 'use strict'; const BACKEND_URL = 'https://www.ironnerd.me'; const ongoingRequests = new Set(); let allBazaarItems = []; let currentItemData = null; let lastUrl = location.href; let sortKey = 'price'; let sortOrder = 'asc'; function init() { injectAdditionalStyles(); ensureBazaarEnhancerContainer().then(container => { initTabbedInterface(container); observeDarkMode(); const info = getItemInfoFromURL(); if (info.itemID) { currentItemData = info; fetchBazaarItems(info.itemID); } else { clearListingsData(); } adjustBazaarEnhancerContainerTheme(); }); } function getItemInfoFromURL() { const url = new URL(window.location.href); let itemID = null; let itemName = ''; if (url.hash) { let hash = url.hash.startsWith("#/") ? url.hash.substring(2) : url.hash.substring(1); let params = new URLSearchParams(hash); itemID = params.get("itemID"); itemName = decodeURIComponent(params.get("itemName") ?? ""); } if (!itemID) { let params = url.searchParams; itemID = params.get("itemID"); itemName = decodeURIComponent(params.get("itemName") ?? ""); } return { itemID: itemID ? parseInt(itemID, 10) : null, itemName: itemName }; } function clearListingsData() { const topCheapestView = document.getElementById('topCheapestView'); const fullListingsView = document.getElementById('fullListingsView'); if (topCheapestView) { topCheapestView.innerHTML = `<p>No item selected.</p>`; } if (fullListingsView) { fullListingsView.innerHTML = `<p>No item selected.</p>`; } } function createCellWithLink(url, text) { const td = document.createElement('td'); const a = document.createElement('a'); a.href = url; a.innerText = text; a.target = '_blank'; a.style.color = '#007bff'; a.style.textDecoration = 'none'; a.addEventListener('mouseover', () => { a.style.textDecoration = 'underline'; }); a.addEventListener('mouseout', () => { a.style.textDecoration = 'none'; }); td.appendChild(a); return td; } function createCell(content) { const td = document.createElement('td'); td.innerText = content; return td; } function createCellWithImage(src, alt) { const td = document.createElement('td'); const img = document.createElement('img'); img.src = src; img.alt = alt; img.style.height = '30px'; img.setAttribute('loading', 'lazy'); td.appendChild(img); return td; } function formatTimestamp(unixTime) { if (unixTime.toString().length === 10) { unixTime = unixTime * 1000; } const date = new Date(unixTime); const now = new Date(); const diff = Math.floor((now - date) / 1000); if (diff < 60) { return diff + 's ago'; } const minutes = Math.floor(diff / 60); if (minutes < 60) { return minutes + 'm ago'; } const hours = Math.floor(minutes / 60); if (hours < 24) { return hours + 'h ago'; } const days = Math.floor(hours / 24); return days + 'd ago'; } function ensureBazaarEnhancerContainer() { return new Promise(resolve => { if (document.querySelector('.captcha-container')) { return; } let container = document.getElementById('bazaar-enhancer-container'); if (container) { return resolve(container); } container = document.createElement('div'); container.id = 'bazaar-enhancer-container'; container.style.overflow = 'hidden'; let target = document.querySelector('.delimiter___zFh2E'); if (target && target.parentNode) { target.parentNode.insertBefore(container, target.nextSibling); return resolve(container); } const observer = new MutationObserver((mutations, obs) => { if (document.querySelector('.captcha-container')) { obs.disconnect(); return; } target = document.querySelector('.delimiter___zFh2E'); if (target && target.parentNode) { target.parentNode.insertBefore(container, target.nextSibling); obs.disconnect(); return resolve(container); } }); observer.observe(document.body, { childList: true, subtree: true }); }); } function initTabbedInterface(container) { container.innerHTML = ''; const nav = document.createElement('div'); nav.id = 'bazaar-nav'; nav.style.display = 'flex'; nav.style.justifyContent = 'center'; nav.style.marginBottom = '10px'; const btnTopCheapest = document.createElement('button'); btnTopCheapest.innerText = 'Top 3'; btnTopCheapest.addEventListener('click', () => { setActiveTab(0); }); const btnFullListings = document.createElement('button'); btnFullListings.innerText = 'All Bazaars'; btnFullListings.addEventListener('click', () => { setActiveTab(1); }); nav.appendChild(btnTopCheapest); nav.appendChild(btnFullListings); container.appendChild(nav); const topCheapestView = document.createElement('div'); topCheapestView.id = 'topCheapestView'; topCheapestView.style.padding = '10px'; topCheapestView.innerHTML = '<p>Loading top cheapest listings...</p>'; const fullListingsView = document.createElement('div'); fullListingsView.id = 'fullListingsView'; fullListingsView.style.padding = '10px'; fullListingsView.style.maxHeight = '350px'; fullListingsView.style.height = 'auto'; fullListingsView.style.overflowY = 'auto'; fullListingsView.innerHTML = '<p>Loading full listings...</p>'; container.appendChild(topCheapestView); container.appendChild(fullListingsView); setActiveTab(0); } function setActiveTab(tabIndex) { const topCheapestView = document.getElementById('topCheapestView'); const fullListingsView = document.getElementById('fullListingsView'); if (tabIndex === 0) { topCheapestView.style.display = 'block'; fullListingsView.style.display = 'none'; } else { topCheapestView.style.display = 'none'; fullListingsView.style.display = 'block'; } } function sortItems(items) { return items.slice().sort((a, b) => { let valA = a[sortKey], valB = b[sortKey]; if (valA < valB) return sortOrder === 'asc' ? -1 : 1; if (valA > valB) return sortOrder === 'asc' ? 1 : -1; return 0; }); } function renderListings() { if (currentItemData && allBazaarItems.length > 0) { const sortedItems = sortItems(allBazaarItems); const fullListingsView = document.getElementById('fullListingsView'); const topCheapestView = document.getElementById('topCheapestView'); displayFullListings(sortedItems, fullListingsView); displayTopCheapestItems(sortedItems.slice(0, 3), currentItemData.itemName, topCheapestView); } } function createSortableHeader(text, key) { const th = document.createElement('th'); th.innerText = text; th.style.border = '1px solid #ccc'; th.style.padding = '8px'; th.style.backgroundColor = '#e0e0e0'; th.style.textAlign = 'center'; th.style.fontSize = '14px'; th.style.cursor = 'pointer'; th.addEventListener('click', () => { if (sortKey === key) { sortOrder = sortOrder === 'asc' ? 'desc' : 'asc'; } else { sortKey = key; sortOrder = 'asc'; } renderListings(); }); return th; } function fetchBazaarItems(itemID) { if (!itemID) { return; } const fullListingsView = document.getElementById('fullListingsView'); const topCheapestView = document.getElementById('topCheapestView'); if (ongoingRequests.has(`bazaar_items_${itemID}`)) return; ongoingRequests.add(`bazaar_items_${itemID}`); fullListingsView.innerHTML = `<p>Loading full listings...</p><div class="loading-spinner"></div>`; topCheapestView.innerHTML = `<p>Loading top 3 cheapest items...</p><div class="loading-spinner"></div>`; GM_xmlhttpRequest({ method: 'GET', url: `${BACKEND_URL}/get_bazaar_items/${itemID}`, headers: { 'Accept': 'application/json' }, onload: function(response) { ongoingRequests.delete(`bazaar_items_${itemID}`); if (response.status === 200) { try { const data = JSON.parse(response.responseText); if (data.bazaar_items) { allBazaarItems = data.bazaar_items; const sortedItems = sortItems(allBazaarItems); displayFullListings(sortedItems, fullListingsView); displayTopCheapestItems(sortedItems.slice(0, 3), currentItemData ? currentItemData.itemName : "", topCheapestView); } else { fullListingsView.innerHTML = `<p>No items found.</p>`; topCheapestView.innerHTML = `<p>No items found.</p>`; } } catch(e) { fullListingsView.innerHTML = `<p>Error parsing server response.</p>`; topCheapestView.innerHTML = `<p>Error parsing server response.</p>`; console.error("Error parsing bazaar items response:", e); } } else { fullListingsView.innerHTML = `<p>Error: ${response.status} - ${response.statusText}</p>`; topCheapestView.innerHTML = `<p>Error: ${response.status} - ${response.statusText}</p>`; } }, onerror: function(error) { ongoingRequests.delete(`bazaar_items_${itemID}`); fullListingsView.innerHTML = `<p>Network error occurred. Please try again later.</p>`; topCheapestView.innerHTML = `<p>Network error occurred. Please try again later.</p>`; console.error("Network error (bazaar items):", error); } }); } function displayFullListings(items, targetElement) { targetElement.innerHTML = ''; if (items.length === 0) { targetElement.innerHTML = `<p>No items found.</p>`; return; } const title = document.createElement('h3'); title.innerText = `Full Listings`; title.style.textAlign = 'center'; title.style.marginTop = '2px'; title.style.marginBottom = '10px'; targetElement.appendChild(title); const tableContainer = document.createElement('div'); tableContainer.style.overflowX = 'auto'; tableContainer.style.width = '100%'; const table = document.createElement('table'); table.className = 'top-cheapest-table'; table.style.width = '100%'; table.style.borderCollapse = 'collapse'; const thead = document.createElement('thead'); const headerRow = document.createElement('tr'); headerRow.appendChild(createSortableHeader("Price ($) ↑↓", "price")); headerRow.appendChild(createSortableHeader("Quantity ↑↓", "quantity")); headerRow.appendChild(createSortableHeader("Updated ↑↓", "last_updated")); headerRow.appendChild(createCell("Seller")); thead.appendChild(headerRow); table.appendChild(thead); const tbody = document.createElement('tbody'); items.forEach((item, index) => { const tr = document.createElement('tr'); tr.appendChild(createCellWithLink( `https://www.torn.com/bazaar.php?userID=${item.user_id}`, `$${item.price.toLocaleString()}` )); tr.appendChild(createCell(item.quantity)); tr.appendChild(createCell(formatTimestamp(item.last_updated))); const sellerText = item.player_name ? item.player_name : item.user_id; tr.appendChild(createCellWithLink( `https://www.torn.com/profiles.php?XID=${item.user_id}`, sellerText )); tbody.appendChild(tr); }); table.appendChild(tbody); tableContainer.appendChild(table); targetElement.appendChild(tableContainer); adjustUnifiedTableTheme(); } function displayTopCheapestItems(items, itemName, targetElement) { targetElement.innerHTML = ''; if (!items || items.length === 0) { targetElement.innerHTML = `<p>No items found.</p>`; return; } const title = document.createElement('h3'); title.innerText = `Top 3 Cheapest ${itemName} Bazaar Items`; title.style.textAlign = 'center'; title.style.marginTop = '2px'; title.style.marginBottom = '10px'; targetElement.appendChild(title); const tableContainer = document.createElement('div'); tableContainer.style.overflowX = 'auto'; tableContainer.style.width = '100%'; const table = document.createElement('table'); table.className = 'top-cheapest-table'; table.style.width = '100%'; table.style.borderCollapse = 'collapse'; const thead = document.createElement('thead'); const headerRow = document.createElement('tr'); headerRow.appendChild(createSortableHeader("Price ($) ↑↓", "price")); headerRow.appendChild(createSortableHeader("Quantity ↑↓", "quantity")); headerRow.appendChild(createSortableHeader("Updated ↑↓", "last_updated")); headerRow.appendChild(createCell("Seller")); thead.appendChild(headerRow); table.appendChild(thead); const tbody = document.createElement('tbody'); items.slice(0, 3).forEach((item, index) => { const tr = document.createElement('tr'); tr.appendChild(createCellWithLink( `https://www.torn.com/bazaar.php?userID=${item.user_id}`, `$${item.price.toLocaleString()}` )); const quantityTd = document.createElement('td'); quantityTd.innerText = item.quantity; quantityTd.style.border = '1px solid #ccc'; quantityTd.style.padding = '6px'; quantityTd.style.textAlign = 'center'; quantityTd.style.fontSize = '14px'; tr.appendChild(quantityTd); const updatedTd = document.createElement('td'); updatedTd.innerText = formatTimestamp(item.last_updated); updatedTd.style.border = '1px solid #ccc'; updatedTd.style.padding = '6px'; updatedTd.style.textAlign = 'center'; updatedTd.style.fontSize = '14px'; tr.appendChild(updatedTd); const sellerText = item.player_name ? item.player_name : item.user_id; tr.appendChild(createCellWithLink( `https://www.torn.com/profiles.php?XID=${item.user_id}`, sellerText )); tbody.appendChild(tr); }); table.appendChild(tbody); tableContainer.appendChild(table); targetElement.appendChild(tableContainer); adjustUnifiedTableTheme(); } function adjustUnifiedTableTheme() { const isDarkMode = document.body.classList.contains('dark-mode'); const tables = document.querySelectorAll('.top-cheapest-table'); tables.forEach(table => { if (isDarkMode) { table.style.backgroundColor = '#1c1c1c'; table.style.color = '#f0f0f0'; table.querySelectorAll('th').forEach(th => { th.style.backgroundColor = '#444'; th.style.color = '#ffffff'; }); table.querySelectorAll('tr:nth-child(even)').forEach(tr => { tr.style.backgroundColor = '#2a2a2a'; }); table.querySelectorAll('tr:nth-child(odd)').forEach(tr => { tr.style.backgroundColor = '#1e1e1e'; }); table.querySelectorAll('td a').forEach(a => { a.style.color = '#4ea8de'; }); } else { table.style.backgroundColor = '#fff'; table.style.color = '#000'; table.querySelectorAll('th').forEach(th => { th.style.backgroundColor = '#f2f2f2'; th.style.color = '#000'; }); table.querySelectorAll('tr:nth-child(even)').forEach(tr => { tr.style.backgroundColor = '#f9f9f9'; }); table.querySelectorAll('tr:nth-child(odd)').forEach(tr => { tr.style.backgroundColor = '#fff'; }); table.querySelectorAll('td a').forEach(a => { a.style.color = '#007bff'; }); } }); } function adjustBazaarEnhancerContainerTheme() { const container = document.getElementById('bazaar-enhancer-container'); const isDarkMode = document.body.classList.contains('dark-mode'); if (container) { if (isDarkMode) { container.style.backgroundColor = 'rgba(0,0,0,0.6)'; container.style.color = '#f0f0f0'; container.style.border = '1px solid rgba(255,255,255,0.1)'; container.style.boxShadow = '0 4px 8px rgba(0,0,0,0.4)'; } else { container.style.backgroundColor = '#ffffff'; container.style.color = '#000000'; container.style.border = '1px solid #ddd'; container.style.boxShadow = '0 4px 8px rgba(0,0,0,0.1)'; } } } function observeDarkMode() { const observer = new MutationObserver(() => { adjustUnifiedTableTheme(); adjustBazaarEnhancerContainerTheme(); }); observer.observe(document.body, { attributes: true, attributeFilter: ['class'] }); } function injectAdditionalStyles() { const style = document.createElement('style'); style.type = 'text/css'; style.innerHTML = ` @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } #bazaar-enhancer-container { background-color: #ffffff; color: #000000; border: 1px solid #ddd; box-shadow: 0 4px 8px rgba(0,0,0,0.1); border-radius: 8px; padding: 10px; margin: 10px 0; transition: background-color 0.3s, color 0.3s; } .dark-mode #bazaar-enhancer-container { background-color: rgba(0,0,0,0.6); color: #f0f0f0; border: 1px solid rgba(255,255,255,0.1); box-shadow: 0 4px 8px rgba(0,0,0,0.4); } #showBazaarModal table.bazaar-table, #bazaar-enhancer-container table.top-cheapest-table { width: 100%; border-collapse: collapse; margin-top: 10px; table-layout: auto; } #showBazaarModal table.bazaar-table th, #showBazaarModal table.bazaar-table td, #bazaar-enhancer-container table.top-cheapest-table th, #bazaar-enhancer-container table.top-cheapest-table td { text-align: center; padding: 8px; border: 1px solid #ccc; } #showBazaarModal table.bazaar-table th, #bazaar-enhancer-container table.top-cheapest-table th { background-color: #f2f2f2; } .dark-mode #showBazaarModal table.bazaar-table th, .dark-mode #showBazaarModal table.bazaar-table td, .dark-mode #bazaar-enhancer-container table.top-cheapest-table th, .dark-mode #bazaar-enhancer-container table.top-cheapest-table td { background-color: #2a2a2a; color: #f0f0f0; } .dark-mode #showBazaarModal table.bazaar-table th, .dark-mode #bazaar-enhancer-container table.top-cheapest-table th { background-color: #333; } #showBazaarModal .loading-spinner, #bazaar-enhancer-container .loading-spinner { border: 4px solid #f3f3f3; border-top: 4px solid #3498db; border-radius: 50%; width: 24px; height: 24px; animation: spin 2s linear infinite; display: inline-block; margin-left: 10px; } #bazaar-enhancer-container a.visited-link, #bazaar-enhancer-container table a.visited-link, #showBazaarModal a.visited-link, #showBazaarModal table a.visited-link { color: purple !important; } #bazaar-nav { display: flex; justify-content: center; margin-bottom: 10px; } #bazaar-nav button { margin: 0 5px; padding: 5px 10px; cursor: pointer; background-color: #f2f2f2; color: #000; border: 1px solid #ccc; } .dark-mode #bazaar-nav button { background-color: #444; color: #fff; border: 1px solid #666; } #topCheapestView, #fullListingsView { width: 100%; box-sizing: border-box; padding: 10px; } #fullListingsView { height: 500px; overflow-y: auto; } `; document.head.appendChild(style); } function checkForItems(wrapper) { if (!wrapper || wrapper.id === 'bazaar-enhancer-container') return; let itemTile = wrapper.previousElementSibling; if (itemTile && itemTile.id === 'bazaar-enhancer-container') { itemTile = itemTile.previousElementSibling; } if (!itemTile) return; const nameEl = itemTile.querySelector('.name___ukdHN'); const btn = itemTile.querySelector('button[aria-controls^="wai-itemInfo-"]'); if (nameEl && btn) { const itemName = nameEl.textContent.trim(); const idParts = btn.getAttribute('aria-controls').split('-'); const itemId = idParts[idParts.length - 1]; currentItemData = { itemID: parseInt(itemId, 10), itemName: itemName }; fetchBazaarItems(currentItemData.itemID); } } function checkForItemsMobile() { if (window.innerWidth >= 784) return; const sellerList = document.querySelector('ul.sellerList___e4C9_'); const headerEl = document.querySelector('.itemsHeader___ZTO9r .title___ruNCT'); const itemName = headerEl ? headerEl.textContent.trim() : "Unknown"; const btn = document.querySelector('.itemsHeader___ZTO9r button[aria-controls^="wai-itemInfo-"]'); let itemId = null; if (btn) { const parts = btn.getAttribute('aria-controls').split('-'); itemId = parts.length > 2 ? parts[parts.length - 2] : parts[parts.length - 1]; } if (!itemId) return; currentItemData = { itemID: parseInt(itemId, 10), itemName: itemName }; fetchBazaarItems(currentItemData.itemID); } const observer = new MutationObserver(mutations => { mutations.forEach(mutation => { mutation.addedNodes.forEach(node => { if (node.nodeType !== Node.ELEMENT_NODE) return; if (window.innerWidth < 784 && node.classList.contains('sellerList___e4C9_')) { checkForItemsMobile(); } else if (window.innerWidth >= 784 && node.className.includes("sellerListWrapper")) { checkForItems(node); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); setInterval(() => { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(() => { let info = getItemInfoFromURL(); if (info.itemID) { currentItemData = info; fetchBazaarItems(info.itemID); } else { if (window.innerWidth < 784) { checkForItemsMobile(); } else { const wrapper = document.querySelector('[class*="sellerListWrapper"]'); if (wrapper) checkForItems(wrapper); else { clearListingsData(); currentItemData = null; } } } }, 100); } }, 500); init() })();