您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Displays bazaar listings in Item Market
// ==UserScript== // @name Bazaars in Item Market Developed by Byte Core Vault(based on TornW3B's bazaar script) // @namespace http://tampermonkey.net/ // @version 1.3 // @description Displays bazaar listings in Item Market // @author Mr_Awaken[3255504] // @match https://www.torn.com/* // @grant GM.xmlHttpRequest // @grant GM_setValue // @grant GM_getValue // @grant GM_deleteValue // @grant GM_listValues // @grant GM.setValue // @grant GM.getValue // @grant GM.deleteValue // @grant GM.listValues // @connect byte-core-vault.onrender.com // @run-at document-end // ==/UserScript== (function () { 'use strict'; if (typeof GM_setValue === 'undefined' && typeof GM !== 'undefined') { const GM_getValue = function(key, defaultValue) { let value; try { value = localStorage.getItem('GMcompat_' + key); if (value !== null) { return JSON.parse(value); } GM.getValue(key, defaultValue).then(val => { if (val !== undefined) { localStorage.setItem('GMcompat_' + key, JSON.stringify(val)); } }); return defaultValue; } catch (e) { console.error('Error in GM_getValue compatibility:', e); return defaultValue; } }; const GM_setValue = function(key, value) { try { // Store in both localStorage and GM.setValue localStorage.setItem('GMcompat_' + key, JSON.stringify(value)); GM.setValue(key, value); } catch (e) { console.error('Error in GM_setValue compatibility:', e); } }; const GM_deleteValue = function(key) { try { localStorage.removeItem('GMcompat_' + key); GM.deleteValue(key); } catch (e) { console.error('Error in GM_deleteValue compatibility:', e); } }; const GM_listValues = function() { // This is an approximation - we can only list keys with our prefix const keys = []; try { for (let i = 0; i < localStorage.length; i++) { const key = localStorage.key(i); if (key.startsWith('GMcompat_')) { keys.push(key.substring(9)); // Remove the prefix } } } catch (e) { console.error('Error in GM_listValues compatibility:', e); } return keys; }; window.GM_getValue = GM_getValue; window.GM_setValue = GM_setValue; window.GM_deleteValue = GM_deleteValue; window.GM_listValues = GM_listValues; } const CACHE_DURATION_MS = 60000, CARD_WIDTH = 180; let currentSortKey = "price", currentSortOrder = "asc", allListings = [], currentDarkMode = document.body.classList.contains('dark-mode'), currentItemName = "", displayMode = "percentage", isMobileView = false; const scriptSettings = { defaultSort: "price", defaultOrder: "asc", apiKey: "", listingFee: parseFloat(GM_getValue("bytecoreListingFee") || "0"), defaultDisplayMode: "percentage", linkBehavior: GM_getValue("bytecoreLinkBehavior") || "new_tab" }; // Missing functions - add these back let cachedItemsData = null; function getByteCoreStoredItems() { if (cachedItemsData === null) { try { cachedItemsData = JSON.parse(GM_getValue("bytecoreItems") || "{}"); } catch (e) { cachedItemsData = {}; console.error("Stored items got funky:", e); } } return cachedItemsData; } // Add alias for consistency function getStoredItems() { return getByteCoreStoredItems(); } function getByteCoreCache(itemId) { try { const key = "bytecoreCache_" + itemId, cached = GM_getValue(key); if (cached) { const payload = JSON.parse(cached); if (Date.now() - payload.timestamp < CACHE_DURATION_MS) return payload.data; } } catch (e) {} return null; } function setByteCoreCache(itemId, data) { try { GM_setValue("bytecoreCache_" + itemId, JSON.stringify({ timestamp: Date.now(), data })); } catch (e) {} } // Add aliases for consistency function getCache(itemId) { return getByteCoreCache(itemId); } function setCache(itemId, data) { return setByteCoreCache(itemId, data); } const updateStyles = () => { let styleEl = document.getElementById('bazaar-enhanced-styles'); if (!styleEl) { styleEl = document.createElement('style'); styleEl.id = 'bazaar-enhanced-styles'; document.head.appendChild(styleEl); } styleEl.textContent = ` .bytecore-profit-tooltip { position: fixed; background: ${currentDarkMode ? '#333' : '#fff'}; color: ${currentDarkMode ? '#fff' : '#333'}; border: 1px solid ${currentDarkMode ? '#555' : '#ddd'}; padding: 10px 14px; border-radius: 5px; box-shadow: 0 3px 10px rgba(0,0,0,0.3); z-index: 99999; min-width: 200px; max-width: 280px; width: auto; pointer-events: none; transition: opacity 0.2s ease; font-size: 13px; line-height: 1.4; } @media (max-width: 768px) { .bytecore-profit-tooltip { font-size: 12px; max-width: 260px; padding: 8px 12px; } } `; }; updateStyles(); const darkModeObserver = new MutationObserver((mutations) => { mutations.forEach((mutation) => { if (mutation.attributeName === 'class') { const newDarkMode = document.body.classList.contains('dark-mode'); if (newDarkMode !== currentDarkMode) { currentDarkMode = newDarkMode; updateStyles(); } } }); }); darkModeObserver.observe(document.body, { attributes: true }); function checkMobileView() { isMobileView = window.innerWidth < 784; return isMobileView; } checkMobileView(); window.addEventListener('resize', function() { checkMobileView(); processMobileSellerList(); }); function loadByteCoreSettings() { try { const saved = GM_getValue("bytecoreSettings"); if (saved) { const parsedSettings = JSON.parse(saved); Object.assign(scriptSettings, parsedSettings); if (parsedSettings.defaultSort) { currentSortKey = parsedSettings.defaultSort; } if (parsedSettings.defaultOrder) { currentSortOrder = parsedSettings.defaultOrder; } if (parsedSettings.defaultDisplayMode) { displayMode = parsedSettings.defaultDisplayMode; } } } catch (e) { console.error("Oops, settings failed to load:", e); } } function saveByteCoreSettings() { try { GM_setValue("bytecoreSettings", JSON.stringify(scriptSettings)); GM_setValue("bytecoreApiKey", scriptSettings.apiKey || ""); GM_setValue("bytecoreDefaultSort", scriptSettings.defaultSort || "price"); GM_setValue("bytecoreDefaultOrder", scriptSettings.defaultOrder || "asc"); GM_setValue("bytecoreListingFee", scriptSettings.listingFee || 0); GM_setValue("bytecoreDefaultDisplayMode", scriptSettings.defaultDisplayMode || "percentage"); GM_setValue("bytecoreLinkBehavior", scriptSettings.linkBehavior || "new_tab"); } catch (e) { console.error("Settings save hiccup:", e); } } // Add alias for consistency function saveSettings() { return saveByteCoreSettings(); } loadByteCoreSettings(); const style = document.createElement("style"); style.textContent = ` .bytecore-button { padding: 3px 6px; border: 1px solid #ccc; border-radius: 4px; background-color: #fff; color: #000; cursor: pointer; font-size: 12px; margin-left: 4px; } .dark-mode .bytecore-button { border: 1px solid #444; background-color: #1a1a1a; color: #fff; } .bytecore-modal-overlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0,0,0,0.7); display: flex; justify-content: center; align-items: center; z-index: 99999; } .bytecore-info-container { font-size: 13px; border-radius: 8px; margin: 8px 0; padding: 15px; display: flex; flex-direction: column; gap: 12px; background: linear-gradient(135deg, #f0f8ff 0%, #e6f3ff 100%); color: #1a365d; border: 2px solid #3182ce; box-sizing: border-box; width: 100%; overflow: hidden; box-shadow: 0 4px 12px rgba(49, 130, 206, 0.15); } .dark-mode .bytecore-info-container { background: linear-gradient(135deg, #1a202c 0%, #2d3748 100%); color: #e2e8f0; border: 2px solid #4299e1; box-shadow: 0 4px 12px rgba(66, 153, 225, 0.25); } .bytecore-info-header { font-size: 18px; font-weight: 600; color: #2b6cb0; text-align: center; padding: 8px 0; border-bottom: 2px solid #3182ce; margin-bottom: 5px; } .dark-mode .bytecore-info-header { color: #63b3ed; border-bottom-color: #4299e1; } .bytecore-sort-controls { display: flex; align-items: center; gap: 8px; font-size: 13px; padding: 12px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); border-radius: 8px; border: none; color: white; box-shadow: 0 3px 8px rgba(102, 126, 234, 0.3); flex-wrap: nowrap; overflow-x: auto; } .dark-mode .bytecore-sort-controls { background: linear-gradient(135deg, #4c51bf 0%, #553c9a 100%); box-shadow: 0 3px 8px rgba(76, 81, 191, 0.4); } @media (max-width: 768px) { .bytecore-sort-controls { padding: 10px; gap: 6px; } .bytecore-sort-controls > * { flex-shrink: 0; } } .bytecore-sort-select { padding: 6px 28px 6px 12px; border: 2px solid rgba(255,255,255,0.3); border-radius: 6px; background: rgba(255,255,255,0.9) url("data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTAiIGhlaWdodD0iNiIgdmlld0JveD0iMCAwIDEwIDYiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTAgMGw1IDYgNS02eiIgZmlsbD0iIzQ5NTFiZiIvPjwvc3ZnPg==") no-repeat right 10px center; background-size: 12px 7px; -webkit-appearance: none; -moz-appearance: none; appearance: none; cursor: pointer; color: #4c51bf; font-weight: 500; min-width: 80px; flex-shrink: 0; } .bytecore-profit-tooltip { position: fixed; background: #fff; color: #333; border: 1px solid #ddd; padding: 8px 12px; border-radius: 4px; box-shadow: 0 2px 8px rgba(0,0,0,0.3); z-index: 99999; min-width: 200px; max-width: 280px; width: auto; pointer-events: none; transition: opacity 0.2s ease; } .dark-mode .bytecore-profit-tooltip { background: #333; color: #fff; border: 1px solid #555; } .dark-mode .bytecore-sort-select { border: 2px solid rgba(255,255,255,0.2); background: rgba(45, 55, 72, 0.9) url("data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTAiIGhlaWdodD0iNiIgdmlld0JveD0iMCAwIDEwIDYiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTAgMGw1IDYgNS02eiIgZmlsbD0iIzYzYjNlZCIvPjwvc3ZnPg==") no-repeat right 10px center; color: #e2e8f0; background-size: 12px 7px; } .bytecore-sort-select:focus { outline: none; border-color: #63b3ed; box-shadow: 0 0 0 3px rgba(99, 179, 237, 0.3); } .bytecore-min-qty { background-color: rgba(255,255,255,0.9); color: #4c51bf; font-size: 12px; border: 2px solid rgba(255,255,255,0.3); border-radius: 6px; padding: 6px 8px; width: 50px !important; min-width: 50px; flex-shrink: 0; font-weight: 500; } .dark-mode .bytecore-min-qty { border: 2px solid rgba(255,255,255,0.2); background-color: rgba(45, 55, 72, 0.9); color: #e2e8f0; } .bytecore-min-qty:focus { outline: none; border-color: #63b3ed !important; box-shadow: 0 0 0 3px rgba(99, 179, 237, 0.3); } .bytecore-button { padding: 6px 12px; border: 2px solid rgba(255,255,255,0.3); border-radius: 6px; background: rgba(255,255,255,0.9); color: #4c51bf; cursor: pointer; font-size: 12px; font-weight: 500; white-space: nowrap; flex-shrink: 0; } .dark-mode .bytecore-button { border: 2px solid rgba(255,255,255,0.2); background: rgba(45, 55, 72, 0.9); color: #e2e8f0; } .bytecore-table-container { width: 100%; overflow-x: auto; border-radius: 6px; background: white; box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); max-width: 900px; margin: 0 auto; } .dark-mode .bytecore-table-container { background: #2d3748; box-shadow: 0 2px 10px rgba(0, 0, 0, 0.3); } .bytecore-listings-table { width: 100%; border-collapse: collapse; font-size: 14px; min-width: 650px; } .bytecore-table-header { background-color: #4a90e2; color: white; font-weight: bold; text-transform: uppercase; } .dark-mode .bytecore-table-header { background-color: #3182ce; } .bytecore-table-header th { padding: 18px 20px; text-align: center; border-bottom: 1px solid #ddd; white-space: nowrap; font-size: 13px; } .dark-mode .bytecore-table-header th { border-bottom-color: #4a5568; } .bytecore-table-row { border-bottom: 1px solid #ddd; transition: background-color 0.2s ease; } .bytecore-table-row:nth-child(even) { background-color: #f9f9f9; } .bytecore-table-row:hover { background-color: #f1f1f1; } .dark-mode .bytecore-table-row { border-bottom-color: #4a5568; } .dark-mode .bytecore-table-row:nth-child(even) { background-color: #374151; } .dark-mode .bytecore-table-row:hover { background-color: #4a5568; } .bytecore-table-row td { padding: 18px 20px; text-align: center; border-bottom: 1px solid #ddd; vertical-align: middle; font-size: 14px; color: #1a202c; } .dark-mode .bytecore-table-row td { border-bottom-color: #4a5568; color: #f7fafc; } .dark-mode .bytecore-table-row td[style*="color: #065f46"] { color: #10b981 !important; } .dark-mode .bytecore-table-row td[style*="color: #1e40af"] { color: #60a5fa !important; } .dark-mode .bytecore-table-row td[style*="color: #b45309"] { color: #f59e0b !important; } .dark-mode .bytecore-table-row td[style*="color: #374151"] { color: #d1d5db !important; } .bytecore-view-all-container { margin-top: 10px; } @media (max-width: 768px) { .bytecore-table-container { max-width: 100%; margin: 0; } .bytecore-listings-table { font-size: 13px; min-width: 600px; } .bytecore-table-header th { padding: 15px 10px; font-size: 12px; } .bytecore-table-row td { padding: 15px 10px; font-size: 13px; } } .bytecore-settings-grid { display: grid; grid-template-columns: 1fr; gap: 20px; padding: 20px; } .bytecore-settings-card { background: linear-gradient(135deg, #f8fafc 0%, #e2e8f0 100%); border: 2px solid #cbd5e0; border-radius: 12px; padding: 20px; box-shadow: 0 4px 12px rgba(0,0,0,0.1); } .dark-mode .bytecore-settings-card { background: linear-gradient(135deg, #2d3748 0%, #4a5568 100%); border-color: #718096; } .bytecore-settings-card h3 { margin: 0 0 15px 0; color: #2d3748; font-size: 16px; font-weight: 600; } .dark-mode .bytecore-settings-card h3 { color: #e2e8f0; } .bytecore-settings-row { display: flex; align-items: center; justify-content: space-between; margin-bottom: 12px; } .bytecore-settings-row label { font-weight: 500; color: #4a5568; min-width: 120px; } .dark-mode .bytecore-settings-row label { color: #cbd5e0; } .bytecore-modern-select { padding: 8px 12px; border: 2px solid #cbd5e0; border-radius: 8px; background: white; color: #2d3748; font-weight: 500; min-width: 150px; } .dark-mode .bytecore-modern-select { background: #4a5568; border-color: #718096; color: #e2e8f0; } .bytecore-modern-input { padding: 8px 12px; border: 2px solid #cbd5e0; border-radius: 8px; background: white; color: #2d3748; font-weight: 500; min-width: 80px; } .dark-mode .bytecore-modern-input { background: #4a5568; border-color: #718096; color: #e2e8f0; } .bytecore-refresh-btn { background: linear-gradient(135deg, #48bb78 0%, #38a169 100%); color: white; border: none; padding: 8px 16px; border-radius: 6px; font-weight: 500; cursor: pointer; } .bytecore-tools-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 15px; margin-top: 20px; } .bytecore-tool-card { background: rgba(66, 153, 225, 0.1); border: 1px solid #4299e1; border-radius: 8px; padding: 15px; } .bytecore-tool-card h4 { margin: 0 0 8px 0; color: #2b6cb0; font-size: 14px; } .bytecore-tool-card p { margin: 0; font-size: 12px; color: #4a5568; } .dark-mode .bytecore-tool-card { background: rgba(66, 153, 225, 0.2); } .dark-mode .bytecore-tool-card h4 { color: #63b3ed; } .dark-mode .bytecore-tool-card p { color: #cbd5e0; } .bytecore-settings-modal { background-color: #fff; border-radius: 8px; padding: 24px; width: 500px; max-width: 95vw; max-height: 90vh; overflow-y: auto; box-shadow: 0 4px 20px rgba(0,0,0,0.3); position: relative; z-index: 100000; font-family: 'Arial', sans-serif; } .dark-mode .bytecore-settings-modal { background-color: #2a2a2a; color: #e0e0e0; border: 1px solid #444; } .bytecore-settings-title { font-size: 20px; font-weight: bold; margin-bottom: 20px; color: #333; } .dark-mode .bytecore-settings-title { color: #fff; } .bytecore-tabs { display: flex; border-bottom: 1px solid #ddd; margin-bottom: 20px; padding-bottom: 0; flex-wrap: wrap; } .dark-mode .bytecore-tabs { border-bottom: 1px solid #444; } .bytecore-tab { padding: 10px 16px; cursor: pointer; margin-right: 5px; margin-bottom: 5px; border: 1px solid transparent; border-bottom: none; border-radius: 4px 4px 0 0; font-weight: normal; background-color: #f5f5f5; color: #555; position: relative; bottom: -1px; } .dark-mode .bytecore-tab { background-color: #333; color: #ccc; } .bytecore-tab.active { background-color: #fff; color: #333; border-color: #ddd; font-weight: bold; padding-bottom: 11px; } .dark-mode .bytecore-tab.active { background-color: #2a2a2a; color: #fff; border-color: #444; } .bytecore-tab-content { display: none; } .bytecore-tab-content.active { display: block; } .bytecore-settings-group { margin-bottom: 20px; } .bytecore-settings-item { margin-bottom: 18px; } .bytecore-settings-item label { display: block; margin-bottom: 8px; font-weight: bold; font-size: 14px; } .bytecore-settings-item input[type="text"], .bytecore-settings-item select, .bytecore-number-input { width: 100%; padding: 8px 12px; border: 1px solid #ccc; border-radius: 4px; font-size: 14px; background-color: #fff; color: #333; max-width: 200px; } .dark-mode .bytecore-settings-item input[type="text"], .dark-mode .bytecore-settings-item select, .dark-mode .bytecore-number-input { border: 1px solid #444; background-color: #222; color: #e0e0e0; } .bytecore-settings-item select { max-width: 200px; } .bytecore-number-input { -moz-appearance: textfield; appearance: textfield; width: 60px !important; } .bytecore-number-input::-webkit-outer-spin-button, .bytecore-number-input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; } .bytecore-api-note { font-size: 12px; margin-top: 6px; color: #666; line-height: 1.4; } .dark-mode .bytecore-api-note { color: #aaa; } .bytecore-script-item { margin-bottom: 16px; padding-bottom: 16px; border-bottom: 1px solid #eee; } .dark-mode .bytecore-script-item { border-bottom: 1px solid #333; } .bytecore-script-item:last-child { border-bottom: none; } .bytecore-script-name { font-weight: bold; font-size: 16px; margin-bottom: 5px; } .bytecore-script-desc { margin-bottom: 8px; line-height: 1.4; color: #555; } .dark-mode .bytecore-script-desc { color: #bbb; } .bytecore-script-link { display: inline-block; margin-top: 5px; color: #2196F3; text-decoration: none; } .bytecore-script-link:hover { text-decoration: underline; } .bytecore-changelog { margin-bottom: 20px; } .bytecore-changelog-version { font-weight: bold; margin-bottom: 8px; font-size: 15px; } .bytecore-changelog-date { font-style: italic; color: #666; font-size: 13px; margin-bottom: 5px; } .dark-mode .bytecore-changelog-date { color: #aaa; } .bytecore-changelog-list { margin-left: 20px; margin-bottom: 15px; } .bytecore-changelog-item { margin-bottom: 5px; line-height: 1.4; } .bytecore-credits { margin-top: 20px; padding-top: 15px; border-top: 1px solid #eee; } .dark-mode .bytecore-credits { border-top: 1px solid #444; } .bytecore-credits h3 { font-size: 16px; margin-bottom: 10px; } .bytecore-credits p { line-height: 1.4; margin-bottom: 8px; } .bytecore-provider { font-weight: bold; } .bytecore-settings-buttons { display: flex; justify-content: flex-end; gap: 12px; margin-top: 30px; } .bytecore-settings-save, .bytecore-settings-cancel { padding: 8px 16px; border: none; border-radius: 4px; font-size: 14px; cursor: pointer; font-weight: bold; } .bytecore-settings-save { background-color: #4CAF50; color: white; } .bytecore-settings-save:hover { background-color: #45a049; } .bytecore-settings-cancel { background-color: #f5f5f5; color: #333; border: 1px solid #ddd; } .dark-mode .bytecore-settings-cancel { background-color: #333; color: #e0e0e0; border: 1px solid #444; } .bytecore-settings-cancel:hover { background-color: #e9e9e9; } .dark-mode .bytecore-settings-cancel:hover { background-color: #444 !important; border-color: #555 !important; } .bytecore-settings-footer { margin-top: 20px; font-size: 12px; color: #777; text-align: center; padding-top: 15px; border-top: 1px solid #eee; } .dark-mode .bytecore-settings-footer { color: #999; border-top: 1px solid #444; } .bytecore-settings-footer a { color: #2196F3; text-decoration: none; } .bytecore-settings-footer a:hover { text-decoration: underline; } @media (max-width: 600px) { .bytecore-settings-modal { padding: 16px; width: 100%; max-width: 100%; border-radius: 0; max-height: 100vh; } .bytecore-settings-title { font-size: 18px; margin-bottom: 16px; } .bytecore-tab { padding: 8px 12px; font-size: 14px; } .bytecore-settings-item label { font-size: 13px; } .bytecore-settings-item input[type="text"], .bytecore-settings-item select, .bytecore-number-input { padding: 6px 10px; font-size: 13px; max-width: 100%; } .bytecore-settings-item { margin-bottom: 14px; } .bytecore-settings-save, .bytecore-settings-cancel { padding: 6px 12px; font-size: 13px; } .bytecore-api-note { font-size: 11px; } .bytecore-settings-buttons { margin-top: 20px; } .bytecore-settings-footer { font-size: 11px; } } `; document.head.appendChild(style); function fetchJSON(url, callback) { let retryCount = 0; const MAX_RETRIES = 2; const TIMEOUT_MS = 10000; const RETRY_DELAY_MS = 2000; function makeRequest(options) { if (typeof GM_xmlhttpRequest !== 'undefined') { return GM_xmlhttpRequest(options); } else if (typeof GM !== 'undefined' && typeof GM.xmlHttpRequest !== 'undefined') { return GM.xmlHttpRequest(options); } else { console.error('Neither GM_xmlhttpRequest nor GM.xmlHttpRequest are available'); options.onerror && options.onerror(new Error('XMLHttpRequest API not available')); return null; } } function attemptFetch() { let timeoutId = setTimeout(() => { console.warn(`Request to ${url} timed out, ${retryCount < MAX_RETRIES ? 'retrying...' : 'giving up.'}`); if (retryCount < MAX_RETRIES) { retryCount++; setTimeout(attemptFetch, RETRY_DELAY_MS); } else { callback(null); } }, TIMEOUT_MS); makeRequest({ method: 'GET', url, timeout: TIMEOUT_MS, onload: res => { clearTimeout(timeoutId); try { if (res.status >= 200 && res.status < 300) { callback(JSON.parse(res.responseText)); } else { console.warn(`Request to ${url} failed with status ${res.status}`); if (retryCount < MAX_RETRIES) { retryCount++; setTimeout(attemptFetch, RETRY_DELAY_MS); } else { callback(null); } } } catch (e) { console.error(`Error parsing response from ${url}:`, e); callback(null); } }, onerror: (error) => { clearTimeout(timeoutId); console.warn(`Request to ${url} failed:`, error); if (retryCount < MAX_RETRIES) { retryCount++; setTimeout(attemptFetch, RETRY_DELAY_MS); } else { callback(null); } }, ontimeout: () => { clearTimeout(timeoutId); console.warn(`Request to ${url} timed out natively`); if (retryCount < MAX_RETRIES) { retryCount++; setTimeout(attemptFetch, RETRY_DELAY_MS); } else { callback(null); } } }); } attemptFetch(); } function getRelativeTime(ts) { const diffSec = Math.floor((Date.now() - ts * 1000) / 1000); if (diffSec < 60) return diffSec + 's ago'; if (diffSec < 3600) return Math.floor(diffSec / 60) + 'm ago'; if (diffSec < 86400) return Math.floor(diffSec / 3600) + 'h ago'; return Math.floor(diffSec / 86400) + 'd ago'; } const svgTemplates = { rightArrow: `<svg viewBox="0 0 320 512"><path fill="currentColor" d="M310.6 233.4c12.5 12.5 12.5 32.8 0 45.3l-192 192c-12.5 12.5-32.8 12.5-45.3 0s-12.5-32.8 0-45.3L242.7 256 73.4 86.6c-12.5-12.5-12.5-32.8 0-45.3s32.8-12.5-45.3 0l192 192z"/></svg>`, leftArrow: `<svg viewBox="0 0 320 512"><path fill="currentColor" d="M9.4 233.4c-12.5 12.5-12.5 32.8 0 45.3l192 192c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L77.3 256 246.6 86.6c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0l-192 192z"/></svg>`, warningIcon: `<path fill="currentColor" d="M256 32c14.2 0 27.3 7.5 34.5 19.8l216 368c7.3 12.4 7.3 27.7 .2 40.1S486.3 480 472 480H40c-14.3 0-27.6-7.7-34.7-20.1s-7-27.8 .2-40.1l216-368C228.7 39.5 241.8 32 256 32zm0 128c-13.3 0-24 10.7-24 24V296c0 13.3 10.7 24 24 24s24-10.7 24-24V184c0-13.3-10.7-24-24-24zm32 224a32 32 0 1 0 -64 0 32 32 0 1 0 64 0z"/>`, infoIcon: `<path fill="currentColor" d="M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512zM216 336h24V272H216c-13.3 0-24-10.7-24-24s10.7-24 24-24h48c13.3 0 24 10.7 24 24v88h8c13.3 0 24 10.7 24 24s-10.7 24-24 24zm40-208a32 32 0 1 1 0 64 32 32 0 1 1 0-64z"/>` }; function createListingRow(listing, index) { const row = document.createElement('tr'); row.className = 'bytecore-table-row'; row.dataset.index = index; const listingKey = listing.player_id + '-' + listing.price + '-' + listing.quantity; row.dataset.listingKey = listingKey; row.dataset.quantity = listing.quantity; let profileVisitedColor = '#3182ce'; try { const key = `visited_profile_${listing.player_id}`; const data = JSON.parse(GM_getValue(key)); if (data && data.lastClicked) { profileVisitedColor = '#805ad5'; } } catch (e) {} const displayName = listing.player_name || listing.player_id; // Profile link for username const profileLink = scriptSettings.linkBehavior === "new_tab" ? `<a href="https://www.torn.com/profiles.php?XID=${listing.player_id}" target="_blank" style="color: ${profileVisitedColor}; text-decoration: none; font-weight: 500;">${displayName}</a>` : scriptSettings.linkBehavior === "new_window" ? `<a href="https://www.torn.com/profiles.php?XID=${listing.player_id}" target="_blank" onclick="window.open(this.href, '_blank', 'width=800,height=600'); return false;" style="color: ${profileVisitedColor}; text-decoration: none; font-weight: 500;">${displayName}</a>` : `<a href="https://www.torn.com/profiles.php?XID=${listing.player_id}" style="color: ${profileVisitedColor}; text-decoration: none; font-weight: 500;">${displayName}</a>`; // Bazaar link button const bazaarLink = scriptSettings.linkBehavior === "new_tab" ? `<a href="https://www.torn.com/bazaar.php?userId=${listing.player_id}&itemId=${listing.item_id}&highlight=1#/" target="_blank" style="background: #4299e1; color: white; padding: 8px 16px; border-radius: 6px; text-decoration: none; font-size: 12px; font-weight: 500; display: inline-block; margin: 4px 0; min-width: 90px; text-align: center;">View Bazaar</a>` : scriptSettings.linkBehavior === "new_window" ? `<a href="https://www.torn.com/bazaar.php?userId=${listing.player_id}&itemId=${listing.item_id}&highlight=1#/" target="_blank" onclick="window.open(this.href, '_blank', 'width=800,height=600'); return false;" style="background: #4299e1; color: white; padding: 8px 16px; border-radius: 6px; text-decoration: none; font-size: 12px; font-weight: 500; display: inline-block; margin: 4px 0; min-width: 90px; text-align: center;">View Bazaar</a>` : `<a href="https://www.torn.com/bazaar.php?userId=${listing.player_id}&itemId=${listing.item_id}&highlight=1#/" style="background: #4299e1; color: white; padding: 8px 16px; border-radius: 6px; text-decoration: none; font-size: 12px; font-weight: 500; display: inline-block; margin: 4px 0; min-width: 90px; text-align: center;">View Bazaar</a>`; // Get market value from listing if available let marketValue = ''; if (listing.marketPrice || listing.market_price) { const price = listing.marketPrice || listing.market_price; marketValue = `$${price.toLocaleString()}`; } else { // Try to get from stored items const stored = getStoredItems(); const match = Object.values(stored).find(item => item.name && item.name.toLowerCase() === currentItemName.toLowerCase()); if (match && match.market_value) { marketValue = `$${Number(match.market_value).toLocaleString()}`; } else { const priceComparison = getPriceComparisonHtml(listing.price, listing.quantity); marketValue = priceComparison || 'N/A'; } } const relativeTime = getRelativeTime(listing.updated); const formattedPrice = `$${listing.price.toLocaleString()}`; const formattedQuantity = listing.quantity.toLocaleString(); const totalCost = listing.price * listing.quantity; // Calculate profit/loss for display under unit price let profitLossDisplay = ''; try { const stored = getStoredItems(); const match = Object.values(stored).find(item => item.name && item.name.toLowerCase() === currentItemName.toLowerCase()); if (match && match.market_value) { const marketValue = Number(match.market_value); const listingFee = scriptSettings.listingFee || 0; const feePerItem = Math.ceil(marketValue * (listingFee / 100)); const netRevenuePerItem = marketValue - feePerItem; const profitPerItem = netRevenuePerItem - listing.price; let color, text; if (profitPerItem > 0) { color = '#10b981'; // Green for profit text = `+$${profitPerItem.toLocaleString()}`; } else if (profitPerItem < 0) { color = '#ef4444'; // Red for loss text = `-$${Math.abs(profitPerItem).toLocaleString()}`; } else { color = '#6b7280'; // Gray for break-even text = '$0'; } profitLossDisplay = `<div style="font-size: 10px; color: ${color}; font-weight: 600; margin-top: 2px;">${text}</div>`; } } catch (e) { console.error("Profit/loss display error:", e); } row.innerHTML = ` <td style="text-align: left; padding-left: 15px;">${profileLink}</td> <td style="font-weight: 700; color: #065f46; font-size: 15px;"> ${formattedPrice} ${profitLossDisplay} </td> <td style="font-weight: 600; color: #1e40af;">${formattedQuantity}</td> <td style="font-weight: 600; color: #b45309; font-size: 15px;">$${totalCost.toLocaleString()}</td> <td style="font-size: 12px; color: #374151;">${marketValue}</td> <td style="padding: 12px 20px;">${bazaarLink}</td> <td style="font-size: 12px; color: #6b7280; padding-right: 15px;">${relativeTime}</td> `; // Add click tracking for profile link const profileLinkEl = row.querySelector('a[href*="profiles.php"]'); if (profileLinkEl) { profileLinkEl.addEventListener('click', () => { const key = `visited_profile_${listing.player_id}`; GM_setValue(key, JSON.stringify({ lastClicked: Date.now() })); profileLinkEl.style.color = '#805ad5'; }); } return row; } function getPriceComparisonHtml(listingPrice, quantity) { try { const stored = getStoredItems(); const match = Object.values(stored).find(item => item.name && item.name.toLowerCase() === currentItemName.toLowerCase()); if (match && match.market_value) { const marketValue = Number(match.market_value), percentDiff = ((listingPrice / marketValue) - 1) * 100, listingFee = scriptSettings.listingFee || 0, // Calculate per single item feePerItem = Math.ceil(marketValue * (listingFee / 100)), netRevenuePerItem = marketValue - feePerItem, profitPerItem = netRevenuePerItem - listingPrice, minResellPrice = Math.ceil(listingPrice / (1 - (listingFee / 100))); let color, text; const absProfit = Math.abs(profitPerItem); let abbrevValue = profitPerItem < 0 ? '-' : ''; if (absProfit >= 1000000) { abbrevValue += '$' + (absProfit / 1000000).toFixed(1).replace(/\.0$/, '') + 'm'; } else if (absProfit >= 1000) { abbrevValue += '$' + (absProfit / 1000).toFixed(1).replace(/\.0$/, '') + 'k'; } else { abbrevValue += '$' + absProfit; } if (profitPerItem > 0) { color = currentDarkMode ? '#7fff7f' : '#006400'; text = displayMode === "percentage" ? `(${percentDiff.toFixed(1)}%)` : `(${abbrevValue})`; } else if (profitPerItem < 0) { color = currentDarkMode ? '#ff7f7f' : '#8b0000'; text = displayMode === "percentage" ? `(+${percentDiff.toFixed(1)}%)` : `(${abbrevValue})`; } else { color = currentDarkMode ? '#cccccc' : '#666666'; text = displayMode === "percentage" ? `(0%)` : `($0)`; } // Tooltip shows per-item calculation const tooltipContent = ` <div style="font-weight:bold; font-size:13px; margin-bottom:6px; text-align:center;"> ${profitPerItem >= 0 ? 'PROFIT' : 'LOSS'} PER ITEM: ${profitPerItem >= 0 ? '$' : '-$'}${Math.abs(profitPerItem).toLocaleString()} </div> <hr style="margin: 4px 0; border-color: ${currentDarkMode ? '#444' : '#ddd'}"> <div>Purchase Price: $${listingPrice.toLocaleString()}</div> <div>Market Value: $${marketValue.toLocaleString()}</div> ${listingFee > 0 ? `<div>Sale Fee (${listingFee}%): $${feePerItem.toLocaleString()}</div>` : ''} <div>Net Revenue: $${netRevenuePerItem.toLocaleString()}</div> ${listingFee > 0 ? `<div style="margin-top:6px; font-weight:bold;">Min. Resell Price: $${minResellPrice.toLocaleString()}</div>` : ''} `; const span = document.createElement('span'); span.style.fontWeight = 'bold'; span.style.fontSize = '10px'; span.style.padding = '0 4px'; span.style.borderRadius = '2px'; span.style.color = color; span.style.cursor = 'help'; span.style.whiteSpace = 'nowrap'; span.textContent = text; span.className = 'bytecore-price-comparison'; span.setAttribute('data-tooltip', tooltipContent); return span.outerHTML; } } catch (e) { console.error("Price comparison error:", e); } return ''; } function renderTableRows(infoContainer) { const tableContainer = infoContainer.querySelector('.bytecore-table-container'); if (!tableContainer || !infoContainer.isConnected) return; try { const minQtyInput = infoContainer.querySelector('.bytecore-min-qty'); const minQty = minQtyInput && minQtyInput.value ? parseInt(minQtyInput.value, 10) : 0; if (!infoContainer.originalListings && allListings && allListings.length > 0) { infoContainer.originalListings = [...allListings]; } if ((!allListings || allListings.length === 0) && infoContainer.originalListings) { allListings = [...infoContainer.originalListings]; } const filteredListings = minQty > 0 ? allListings.filter(listing => listing.quantity >= minQty) : allListings; const tbody = tableContainer.querySelector('tbody'); if (!tbody) return; if (filteredListings.length === 0 && allListings.length > 0) { tbody.innerHTML = `<tr><td colspan="7" style="text-align: center; color: #666; padding: 30px;">No listings found with quantity ≥ ${minQty}. Try a lower value.</td></tr>`; return; } tbody.innerHTML = ''; // Show only top 3 listings initially const isExpanded = tableContainer.dataset.expanded === 'true'; const listingsToShow = isExpanded ? filteredListings : filteredListings.slice(0, 3); listingsToShow.forEach((listing, index) => { const row = createListingRow(listing, index); tbody.appendChild(row); }); // Show/hide view all button const viewAllContainer = infoContainer.querySelector('.bytecore-view-all-container'); if (viewAllContainer) { if (filteredListings.length > 3) { viewAllContainer.style.display = 'block'; const btn = viewAllContainer.querySelector('.bytecore-view-all-btn'); if (isExpanded) { btn.textContent = 'Show Less'; } else { btn.textContent = `View All ${filteredListings.length} Listings`; } } else { viewAllContainer.style.display = 'none'; } } // Update footer count const footerNote = infoContainer.querySelector('.bytecore-listing-footnote'); if (footerNote) { footerNote.textContent = `Displaying ${filteredListings.length} listings available`; } } catch (error) { console.error("Error rendering table rows:", error); } } function createInfoContainer(itemName, itemId) { const container = document.createElement('div'); container.className = 'bytecore-info-container'; container.dataset.itemid = itemId; currentItemName = itemName; const header = document.createElement('div'); header.className = 'bytecore-info-header'; let marketValueText = ""; try { const stored = getStoredItems(); const match = Object.values(stored).find(item => item.name && item.name.toLowerCase() === itemName.toLowerCase()); if (match && match.market_value) { marketValueText = `Market Value: $${Number(match.market_value).toLocaleString()}`; } } catch (e) { console.error("Header market value error:", e); } header.textContent = `Bazaar Listings for ${itemName} (ID: ${itemId})`; if (marketValueText) { const span = document.createElement('span'); span.style.marginLeft = '8px'; span.style.fontSize = '14px'; span.style.fontWeight = 'normal'; span.style.color = currentDarkMode ? '#aaa' : '#666'; span.textContent = `• ${marketValueText}`; header.appendChild(span); } container.appendChild(header); currentSortOrder = getSortOrderForKey(currentSortKey); const sortControls = document.createElement('div'); sortControls.className = 'bytecore-sort-controls'; sortControls.innerHTML = ` <span style="white-space: nowrap;">Sort by:</span> <select class="bytecore-sort-select"> <option value="price" ${currentSortKey === "price" ? "selected" : ""}>Price</option> <option value="quantity" ${currentSortKey === "quantity" ? "selected" : ""}>Quantity</option> <option value="profit" ${currentSortKey === "profit" ? "selected" : ""}>Profit</option> <option value="updated" ${currentSortKey === "updated" ? "selected" : ""}>Last Updated</option> </select> <button class="bytecore-button bytecore-order-toggle"> ${currentSortOrder === "asc" ? "Asc" : "Desc"} </button> <button class="bytecore-button bytecore-display-toggle" title="Toggle between percentage difference and total profit"> ${displayMode === "percentage" ? "%" : "$"} </button> <span style="white-space: nowrap;">Min Qty:</span> <input type="number" class="bytecore-min-qty" min="0" placeholder=""> `; container.appendChild(sortControls); const tableContainer = document.createElement('div'); tableContainer.className = 'bytecore-table-container'; const table = document.createElement('table'); table.className = 'bytecore-listings-table'; const thead = document.createElement('thead'); thead.className = 'bytecore-table-header'; thead.innerHTML = ` <tr> <th>Player</th> <th>Unit Price</th> <th>Quantity</th> <th>Total Cost</th> <th>Market Value</th> <th>View Bazaar</th> <th>Updated</th> </tr> `; const tbody = document.createElement('tbody'); table.appendChild(thead); table.appendChild(tbody); tableContainer.appendChild(table); // Add View All button const viewAllContainer = document.createElement('div'); viewAllContainer.className = 'bytecore-view-all-container'; viewAllContainer.style.display = 'none'; viewAllContainer.innerHTML = ` <button class="bytecore-view-all-btn" style="width: 100%; padding: 10px; margin-top: 10px; background: linear-gradient(135deg, #3182ce 0%, #2c5aa0 100%); color: white; border: none; border-radius: 6px; font-weight: 500; cursor: pointer;"> View All Listings </button> `; container.appendChild(tableContainer); container.appendChild(viewAllContainer); const footerContainer = document.createElement('div'); footerContainer.className = 'bytecore-footer-container'; footerContainer.style.cssText = 'display: flex; justify-content: space-between; align-items: center; margin-top: 8px; padding-top: 8px; border-top: 1px solid #cbd5e0;'; const footnote = document.createElement('div'); footnote.className = 'bytecore-listing-footnote'; footnote.style.cssText = 'font-size: 11px; color: #6b7280; font-weight: 500;'; footnote.textContent = `Displaying listings available`; footerContainer.appendChild(footnote); // Add registration link to footer const registrationLink = document.createElement('div'); registrationLink.style.cssText = 'font-size:11px; color:#3182ce; margin-left:auto; font-weight: 500;'; registrationLink.innerHTML = `<a href="https://byte-core-vault.onrender.com/" target="_blank" style="color:#3182ce; text-decoration:none; padding: 4px 8px; border: 1px solid #3182ce; border-radius: 4px; background: rgba(49, 130, 206, 0.1);">Register on Byte-Core</a>`; if (currentDarkMode) { registrationLink.style.color = '#63b3ed'; registrationLink.querySelector('a').style.color = '#63b3ed'; registrationLink.querySelector('a').style.borderColor = '#63b3ed'; registrationLink.querySelector('a').style.background = 'rgba(99, 179, 237, 0.2)'; } footerContainer.appendChild(registrationLink); container.appendChild(footerContainer); return container; } function sortListings(listings) { return listings.slice().sort((a, b) => { let diff; if (currentSortKey === "profit") { try { const stored = getStoredItems(); const match = Object.values(stored).find(item => item.name && item.name.toLowerCase() === currentItemName.toLowerCase()); if (match && match.market_value) { const marketValue = Number(match.market_value), fee = scriptSettings.listingFee || 0, // Calculate profit per single item aFeePerItem = Math.ceil(marketValue * (fee / 100)), bFeePerItem = Math.ceil(marketValue * (fee / 100)), aProfitPerItem = (marketValue - aFeePerItem) - a.price, bProfitPerItem = (marketValue - bFeePerItem) - b.price; diff = aProfitPerItem - bProfitPerItem; } else { diff = a.price - b.price; } } catch (e) { console.error("Profit sort error:", e); diff = a.price - b.price; } } else { diff = currentSortKey === "price" ? a.price - b.price : currentSortKey === "quantity" ? a.quantity - b.quantity : a.updated - b.updated; } return currentSortOrder === "asc" ? diff : -diff; }); } function updateInfoContainer(wrapper, itemId, itemName) { if (wrapper.hasAttribute('data-has-bytecore-info')) return; let infoContainer = document.querySelector(`.bytecore-info-container[data-itemid="${itemId}"]`); if (!infoContainer) { infoContainer = createInfoContainer(itemName, itemId); // Insert after any existing bazaar containers to avoid conflicts const existingBazaarContainer = wrapper.querySelector('.bazaar-info-container'); if (existingBazaarContainer) { existingBazaarContainer.parentNode.insertBefore(infoContainer, existingBazaarContainer.nextSibling); } else { wrapper.insertBefore(infoContainer, wrapper.firstChild); } wrapper.setAttribute('data-has-bytecore-info', 'true'); } else if (!wrapper.contains(infoContainer)) { infoContainer = createInfoContainer(itemName, itemId); const existingBazaarContainer = wrapper.querySelector('.bazaar-info-container'); if (existingBazaarContainer) { existingBazaarContainer.parentNode.insertBefore(infoContainer, existingBazaarContainer.nextSibling); } else { wrapper.insertBefore(infoContainer, wrapper.firstChild); } wrapper.setAttribute('data-has-bytecore-info', 'true'); } else { const header = infoContainer.querySelector('.bytecore-info-header'); if (header) { header.textContent = `Bazaar Listings for ${itemName} (ID: ${itemId})`; } } const tableContainer = infoContainer.querySelector('.bytecore-table-container'); const countElement = infoContainer.querySelector('.bytecore-listings-count'); const updateListingsCount = (text) => { if (countElement) { countElement.textContent = text; } }; const showEmptyState = (isError) => { if (tableContainer) { const tbody = tableContainer.querySelector('tbody'); if (tbody) { tbody.innerHTML = `<tr><td colspan="7" style="text-align:center; padding:20px;">${isError ? 'API Error - Check back later' : 'No listings available'}</td></tr>`; } } updateListingsCount(isError ? 'API Error - Check back later' : 'No listings available'); }; if (tableContainer) { const tbody = tableContainer.querySelector('tbody'); if (tbody) { tbody.innerHTML = '<tr><td colspan="7" style="text-align:center; padding:20px;">Loading bazaar listings...</td></tr>'; } } const cachedData = getCache(itemId); if (cachedData) { allListings = sortListings(cachedData.listings); if (allListings.length === 0) { showEmptyState(false); } else { renderTableRows(infoContainer); } return; } let listings = [], apiErrors = false; let requestTimeout = setTimeout(() => { console.warn('Bazaar listings request timed out'); showEmptyState(true); }, 15000); fetchJSON(`https://byte-core-vault.onrender.com/api/bazaar/listings/${itemId}`, data => { clearTimeout(requestTimeout); if (!data || !data.listings) { showEmptyState(true); return; } listings = data.listings.map(listing => ({ item_id: listing.itemId, player_id: listing.playerId, player_name: listing.playerName, quantity: listing.quantity, price: listing.pricePerUnit, marketPrice: listing.marketPrice, updated: Math.floor(new Date(listing.lastUpdated).getTime() / 1000) })); setCache(itemId, { listings }); if (listings.length === 0) { showEmptyState(false); } else { allListings = sortListings(listings); renderTableRows(infoContainer); } }); } function renderMessageInContainer(container, isApiError) { container.innerHTML = ''; const messageContainer = document.createElement('div'); messageContainer.style.cssText = 'display:flex; flex-direction:column; align-items:center; justify-content:center; padding:20px; text-align:center; width:100%; height:70px;'; const iconSvg = document.createElementNS("http://www.w3.org/2000/svg", "svg"); iconSvg.setAttribute("viewBox", "0 0 512 512"); iconSvg.setAttribute("width", "24"); iconSvg.setAttribute("height", "24"); iconSvg.style.marginBottom = "10px"; const textDiv = document.createElement('div'); if (isApiError) { iconSvg.innerHTML = svgTemplates.infoIcon; textDiv.innerHTML = "API Error<br><span style='font-size: 12px; color: #666;'>Please try again later</span>"; textDiv.style.cssText = currentDarkMode ? 'color:#aaa;' : 'color:#333;'; } else { iconSvg.innerHTML = svgTemplates.infoIcon; textDiv.textContent = "No bazaar listings available for this item."; } messageContainer.appendChild(iconSvg); messageContainer.appendChild(textDiv); container.appendChild(messageContainer); } function processSellerWrapper(wrapper) { if (!wrapper || wrapper.classList.contains('bytecore-info-container') || wrapper.hasAttribute('data-bytecore-processed')) return; const existingContainer = wrapper.querySelector(':scope > .bytecore-info-container'); if (existingContainer) return; const itemTile = wrapper.previousElementSibling; if (!itemTile) return; const nameEl = itemTile.querySelector('.name___ukdHN'), 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]; wrapper.setAttribute('data-bytecore-processed', 'true'); updateInfoContainer(wrapper, itemId, itemName); } } function processMobileSellerList() { if (!checkMobileView()) return; const sellerList = document.querySelector('ul.sellerList___e4C9_, ul[class*="sellerList"]'); if (!sellerList) { const existing = document.querySelector('.bytecore-info-container'); if (existing && !document.contains(existing.parentNode)) { existing.remove(); } return; } if (sellerList.hasAttribute('data-has-bytecore-container')) { return; } const headerEl = document.querySelector('.itemsHeader___ZTO9r .title___ruNCT, [class*="itemsHeader"] [class*="title"]'); const itemName = headerEl ? headerEl.textContent.trim() : "Unknown"; const btn = document.querySelector('.itemsHeader___ZTO9r button[aria-controls^="wai-itemInfo-"], [class*="itemsHeader"] button[aria-controls^="wai-itemInfo-"]'); let itemId = "unknown"; if (btn) { const parts = btn.getAttribute('aria-controls').split('-'); itemId = parts.length > 2 ? parts[parts.length - 2] : parts[parts.length - 1]; } const existingContainer = document.querySelector(`.bytecore-info-container[data-itemid="${itemId}"]`); if (existingContainer) { if (existingContainer.parentNode !== sellerList.parentNode || existingContainer.nextSibling !== sellerList) { sellerList.parentNode.insertBefore(existingContainer, sellerList); } return; } const infoContainer = createInfoContainer(itemName, itemId); // Insert after any existing bazaar containers const existingBazaarContainer = sellerList.parentNode.querySelector('.bazaar-info-container'); if (existingBazaarContainer) { sellerList.parentNode.insertBefore(infoContainer, existingBazaarContainer.nextSibling); } else { sellerList.parentNode.insertBefore(infoContainer, sellerList); } sellerList.setAttribute('data-has-bytecore-container', 'true'); updateInfoContainer(infoContainer, itemId, itemName); } function processAllSellerWrappers(root = document.body) { if (checkMobileView()) return; const sellerWrappers = root.querySelectorAll('[class*="sellerListWrapper"]'); sellerWrappers.forEach(wrapper => processSellerWrapper(wrapper)); } processAllSellerWrappers(); processMobileSellerList(); const observeTarget = document.querySelector('#root') || document.body; let isProcessing = false; const observer = new MutationObserver(mutations => { if (isProcessing) return; let needsProcessing = false; mutations.forEach(mutation => { const isOurMutation = Array.from(mutation.addedNodes).some(node => node.nodeType === Node.ELEMENT_NODE && (node.classList.contains('bytecore-info-container') || node.querySelector('.bytecore-info-container')) ); if (isOurMutation) return; mutation.addedNodes.forEach(node => { if (node.nodeType === Node.ELEMENT_NODE) { needsProcessing = true; } }); mutation.removedNodes.forEach(node => { if (node.nodeType === Node.ELEMENT_NODE && (node.matches('ul.sellerList___e4C9_') || node.matches('ul[class*="sellerList"]')) && checkMobileView()) { const container = document.querySelector('.bytecore-info-container'); if (container) container.remove(); } }); }); if (needsProcessing) { if (observer.processingTimeout) { clearTimeout(observer.processingTimeout); } observer.processingTimeout = setTimeout(() => { try { isProcessing = true; if (checkMobileView()) { processMobileSellerList(); } else { processAllSellerWrappers(); } } finally { isProcessing = false; observer.processingTimeout = null; } }, 100); } }); observer.observe(observeTarget, { childList: true, subtree: true }); const bodyObserver = new MutationObserver(mutations => { mutations.forEach(mutation => { if (mutation.attributeName === 'class') { currentDarkMode = document.body.classList.contains('dark-mode'); } }); }); bodyObserver.observe(document.body, { attributes: true, attributeFilter: ['class'] }); if (window.location.href.includes("bazaar.php")) { function scrollToTargetItem() { const params = new URLSearchParams(window.location.search); const targetItemId = params.get("itemId"), highlight = params.get("highlight"); if (!targetItemId || highlight !== "1") return; function removeHighlightParam() { params.delete("highlight"); history.replaceState({}, "", window.location.pathname + "?" + params.toString() + window.location.hash); } function showToast(message) { const toast = document.createElement('div'); toast.textContent = message; toast.style.cssText = 'position:fixed; bottom:20px; left:50%; transform:translateX(-50%); background-color:rgba(0,0,0,0.7); color:white; padding:10px 20px; border-radius:5px; z-index:100000; font-size:14px;'; document.body.appendChild(toast); setTimeout(() => { toast.remove(); }, 3000); } function findItemCard() { const img = document.querySelector(`img[src*="/images/items/${targetItemId}/"]`); return img ? img.closest('.item___GYCYJ') : null; } const scrollInterval = setInterval(() => { const card = findItemCard(); if (card) { clearInterval(scrollInterval); removeHighlightParam(); card.classList.add("green-outline", "pop-flash"); card.scrollIntoView({ behavior: "smooth", block: "center" }); setTimeout(() => { card.classList.remove("pop-flash"); }, 800); } else { if ((window.innerHeight + window.scrollY) >= document.body.offsetHeight) { showToast("Item not found on this page."); removeHighlightParam(); clearInterval(scrollInterval); } else { window.scrollBy({ top: 300, behavior: 'auto' }); } } }, 50); } function waitForItems() { const container = document.querySelector('.ReactVirtualized__Grid__innerScrollContainer'); if (container && container.childElementCount > 0) { scrollToTargetItem(); } else { setTimeout(waitForItems, 500); } } waitForItems(); } function dailyCleanup() { const lastCleanup = GM_getValue("lastDailyCleanup"), oneDay = 24 * 60 * 60 * 1000, now = Date.now(); if (!lastCleanup || (now - parseInt(lastCleanup, 10)) > oneDay) { const sevenDays = 7 * 24 * 60 * 60 * 1000; let keys = []; try { if (typeof GM_listValues === 'function') { keys = GM_listValues(); } if (keys.length === 0) { const checkKey = (prefix) => { let i = 0; while (true) { const testKey = `${prefix}${i}`; const value = GM_getValue(testKey); if (value === undefined) break; keys.push(testKey); i++; } }; ['visited_', 'bytecoreCache_'].forEach(prefix => { for (let id = 1; id <= 1000; id++) { const key = `${prefix}${id}`; const value = GM_getValue(key); if (value !== undefined) { keys.push(key); } } }); } } catch (e) { console.error("Error listing storage keys:", e); } keys.forEach(key => { if (key && (key.startsWith("visited_") || key.startsWith("bytecoreCache_"))) { try { const val = JSON.parse(GM_getValue(key)); let ts = null; if (key.startsWith("visited_") && val && val.lastClickedUpdated) { ts = val.lastClickedUpdated; } else if (key.startsWith("bytecoreCache_") && val && val.timestamp) { ts = val.timestamp; } else { GM_deleteValue(key); } if (ts !== null && (now - ts) > sevenDays) { GM_deleteValue(key); } } catch (e) { GM_deleteValue(key); } } }); GM_setValue("lastDailyCleanup", now.toString()); } } dailyCleanup(); document.body.addEventListener('click', event => { const container = event.target.closest('.bytecore-info-container'); if (!container) return; if (event.target.matches('.bytecore-order-toggle')) { currentSortOrder = currentSortOrder === "asc" ? "desc" : "asc"; event.target.textContent = currentSortOrder === "asc" ? "Asc" : "Desc"; performSort(container); } if (event.target.matches('.bytecore-display-toggle')) { displayMode = displayMode === "percentage" ? "profit" : "percentage"; event.target.textContent = displayMode === "percentage" ? "%" : "$"; scriptSettings.defaultDisplayMode = displayMode; saveSettings(); const allContainers = document.querySelectorAll('.bytecore-info-container'); allContainers.forEach(container => { renderTableRows(container); }); return; } if (event.target.matches('.bytecore-view-all-btn')) { const container = event.target.closest('.bytecore-info-container'); const tableContainer = container.querySelector('.bytecore-table-container'); const isExpanded = tableContainer.dataset.expanded === 'true'; if (isExpanded) { // Collapse to top 3 tableContainer.dataset.expanded = 'false'; tableContainer.style.maxHeight = ''; tableContainer.style.overflowY = ''; } else { // Expand to show all tableContainer.dataset.expanded = 'true'; tableContainer.style.maxHeight = '400px'; tableContainer.style.overflowY = 'auto'; } renderTableRows(container); return; } }); document.body.addEventListener('input', event => { const container = event.target.closest('.bytecore-info-container'); if (!container) return; if (event.target.matches('.bytecore-min-qty')) { clearTimeout(event.target.debounceTimer); event.target.debounceTimer = setTimeout(() => { if (!allListings || allListings.length === 0) { const itemId = container.getAttribute('data-itemid'); if (itemId) { const cachedData = getCache(itemId); if (cachedData && cachedData.listings && cachedData.listings.length > 0) { allListings = sortListings(cachedData.listings); } } } renderTableRows(container); }, 300); } }); document.body.addEventListener('change', event => { const container = event.target.closest('.bytecore-info-container'); if (!container) return; if (event.target.matches('.bytecore-sort-select')) { const newSortKey = event.target.value; if (newSortKey !== currentSortKey) { currentSortKey = newSortKey; currentSortOrder = getSortOrderForKey(currentSortKey); const orderToggle = container.querySelector('.bytecore-order-toggle'); if (orderToggle) { orderToggle.textContent = currentSortOrder === "asc" ? "Asc" : "Desc"; } } else { currentSortKey = newSortKey; } performSort(container); } }); function performSort(container) { allListings = sortListings(allListings); const tableContainer = container.querySelector('.bytecore-table-container'); if (tableContainer) { renderTableRows(container); } } function addSettingsMenuItem() { const menu = document.querySelector('.settings-menu'); if (!menu || document.querySelector('.bytecore-settings-button')) return; const li = document.createElement('li'); li.className = 'link bytecore-settings-button'; const a = document.createElement('a'); a.href = '#'; const iconDiv = document.createElement('div'); iconDiv.className = 'icon-wrapper'; const svgIcon = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); svgIcon.setAttribute('class', 'default'); svgIcon.setAttribute('fill', '#fff'); svgIcon.setAttribute('stroke', 'transparent'); svgIcon.setAttribute('stroke-width', '0'); svgIcon.setAttribute('width', '16'); svgIcon.setAttribute('height', '16'); svgIcon.setAttribute('viewBox', '0 0 640 512'); const path = document.createElementNS('http://www.w3.org/2000/svg', 'path'); path.setAttribute('d', 'M36.8 192l566.3 0c20.3 0 36.8-16.5 36.8-36.8c0-7.3-2.2-14.4-6.2-20.4L558.2 21.4C549.3 8 534.4 0 518.3 0L121.7 0c-16 0-31 8-39.9 21.4L6.2 134.7c-4 6.1-6.2 13.2-6.2 20.4C0 175.5 16.5 192 36.8 192zM64 224l0 160 0 80c0 26.5 21.5 48 48 48l224 0c26.5 0 48-21.5 48-48l0-80 0-160-64 0 0 160-192 0 0-160-64 0zm448 0l0 256c0 17.7 14.3 32 32 32s32-14.3 32-32l0-256-64 0z'); const span = document.createElement('span'); span.textContent = 'Byte-Core Settings'; svgIcon.appendChild(path); iconDiv.appendChild(svgIcon); a.appendChild(iconDiv); a.appendChild(span); li.appendChild(a); a.addEventListener('click', e => { e.preventDefault(); document.body.click(); openSettingsModal(); }); const logoutButton = menu.querySelector('li.logout'); if (logoutButton) { menu.insertBefore(li, logoutButton); } else { menu.appendChild(li); } } function openSettingsModal() { const overlay = document.createElement("div"); overlay.className = "bytecore-modal-overlay"; const modal = document.createElement("div"); modal.className = "bytecore-settings-modal"; modal.innerHTML = ` <div class="bytecore-settings-title">⚙️ Byte Core Vault Configuration</div> <div class="bytecore-tabs"> <div class="bytecore-tab active" data-tab="settings">🔧 Configuration</div> </div> <div class="bytecore-tab-content active" id="tab-settings" style="max-height: 350px; overflow-y: auto;"> <div class="bytecore-settings-grid"> <div class="bytecore-settings-card"> <h3>🔑 API Authentication</h3> <div class="bytecore-input-group"> <input type="text" id="bytecore-api-key" value="${scriptSettings.apiKey || ''}" placeholder="Enter your Torn API key" class="bytecore-modern-input" style="flex-grow: 1;"> <button class="bytecore-refresh-btn" id="refresh-market-data">🔄 Sync</button> </div> <div id="refresh-status" style="margin-top: 5px; font-size: 12px; display: none;"></div> <p class="bytecore-help-text">Enable market comparison by providing your API key. Data remains private.</p> </div> <div class="bytecore-settings-card"> <h3>📊 Display Preferences</h3> <div class="bytecore-settings-row"> <label>Sort Method:</label> <select id="bytecore-default-sort" class="bytecore-modern-select"> <option value="price" ${scriptSettings.defaultSort === 'price' ? 'selected' : ''}>💰 Price</option> <option value="quantity" ${scriptSettings.defaultSort === 'quantity' ? 'selected' : ''}>📦 Quantity</option> <option value="profit" ${scriptSettings.defaultSort === 'profit' ? 'selected' : ''}>📈 Profit</option> <option value="updated" ${scriptSettings.defaultSort === 'updated' ? 'selected' : ''}>🕒 Updated</option> </select> </div> <div class="bytecore-settings-row"> <label>Sort Direction:</label> <select id="bytecore-default-order" class="bytecore-modern-select"> <option value="asc" ${scriptSettings.defaultOrder === 'asc' ? 'selected' : ''}>⬆️ Low to High</option> <option value="desc" ${scriptSettings.defaultOrder === 'desc' ? 'selected' : ''}>⬇️ High to Low</option> </select> </div> </div> <div class="bytecore-settings-card"> <h3>💼 Trading Settings</h3> <div class="bytecore-settings-row"> <label>Listing Fee (%):</label> <input type="number" id="bytecore-listing-fee" class="bytecore-modern-input" value="${scriptSettings.listingFee || 0}" min="0" max="100" step="1"> </div> <div class="bytecore-settings-row"> <label>Profit Display:</label> <select id="bytecore-default-display" class="bytecore-modern-select"> <option value="percentage" ${scriptSettings.defaultDisplayMode === 'percentage' ? 'selected' : ''}>📈 Percentage</option> <option value="profit" ${scriptSettings.defaultDisplayMode === 'profit' ? 'selected' : ''}>💵 Dollar Value</option> </select> </div> <div class="bytecore-settings-row"> <label>Link Behavior:</label> <select id="bytecore-link-behavior" class="bytecore-modern-select"> <option value="new_tab" ${scriptSettings.linkBehavior === 'new_tab' ? 'selected' : ''}>🔗 New Tab</option> <option value="new_window" ${scriptSettings.linkBehavior === 'new_window' ? 'selected' : ''}>🖼️ New Window</option> <option value="same_tab" ${scriptSettings.linkBehavior === 'same_tab' ? 'selected' : ''}>📄 Same Tab</option> </select> </div> </div> </div> </div> <div class="bytecore-settings-buttons"> <button class="bytecore-settings-save">Save Configuration</button> <button class="bytecore-settings-cancel">Cancel</button> </div> <div class="bytecore-settings-footer"> Developed by <a href="https://www.torn.com/profiles.php?XID=3255504" target="_blank">Mr_Awaken [3255504]</a> </div> `; overlay.appendChild(modal); const tabs = modal.querySelectorAll('.bytecore-tab'); tabs.forEach(tab => { tab.addEventListener('click', function () { tabs.forEach(t => t.classList.remove('active')); this.classList.add('active'); modal.querySelectorAll('.bytecore-tab-content').forEach(content => content.classList.remove('active')); document.getElementById(`tab-${this.getAttribute('data-tab')}`).classList.add('active'); }); }); modal.querySelector('.bytecore-settings-save').addEventListener('click', () => { saveSettingsFromModal(modal); overlay.remove(); }); modal.querySelector('.bytecore-settings-cancel').addEventListener('click', () => { overlay.remove(); }); overlay.addEventListener('click', e => { if (e.target === overlay) overlay.remove(); }); document.body.appendChild(overlay); } function saveSettingsFromModal(modal) { const oldLinkBehavior = scriptSettings.linkBehavior; scriptSettings.apiKey = modal.querySelector('#bytecore-api-key').value.trim(); scriptSettings.defaultSort = modal.querySelector('#bytecore-default-sort').value; scriptSettings.defaultOrder = modal.querySelector('#bytecore-default-order').value; scriptSettings.listingFee = Math.round(parseFloat(modal.querySelector('#bytecore-listing-fee').value) || 0); scriptSettings.defaultDisplayMode = modal.querySelector('#bytecore-default-display').value; scriptSettings.linkBehavior = modal.querySelector('#bytecore-link-behavior').value; if (scriptSettings.listingFee < 0) scriptSettings.listingFee = 0; if (scriptSettings.listingFee > 100) scriptSettings.listingFee = 100; currentSortKey = scriptSettings.defaultSort; currentSortOrder = scriptSettings.defaultOrder; displayMode = scriptSettings.defaultDisplayMode; saveSettings(); document.querySelectorAll('.bytecore-info-container').forEach(container => { const sortSelect = container.querySelector('.bytecore-sort-select'); if (sortSelect) sortSelect.value = currentSortKey; const orderToggle = container.querySelector('.bytecore-order-toggle'); if (orderToggle) orderToggle.textContent = currentSortOrder === "asc" ? "Asc" : "Desc"; const displayToggle = container.querySelector('.bytecore-display-toggle'); if (displayToggle) displayToggle.textContent = displayMode === "percentage" ? "%" : "$"; if (oldLinkBehavior !== scriptSettings.linkBehavior) { renderTableRows(container); } else { performSort(container); } }); if (scriptSettings.apiKey) { fetchTornItems(true); } } function fetchTornItems(forceRefresh = false) { const stored = GM_getValue("tornrpgItems"), lastUpdated = GM_getValue("tornrpgLastUpdate") || 0, now = Date.now(), oneDayMs = 24 * 60 * 60 * 1000, lastUTC = new Date(parseInt(lastUpdated)).toISOString().split('T')[0], todayUTC = new Date().toISOString().split('T')[0], lastHour = Math.floor(parseInt(lastUpdated) / (60 * 60 * 1000)), currentHour = Math.floor(now / (60 * 60 * 1000)); const needsRefresh = forceRefresh || lastUTC < todayUTC || (now - lastUpdated) >= oneDayMs || (lastHour < currentHour && (currentHour - lastHour) >= 1); if (scriptSettings.apiKey && (!stored || needsRefresh)) { const refreshStatus = document.getElementById('refresh-status'); if (refreshStatus) { refreshStatus.style.display = 'block'; refreshStatus.textContent = 'Fetching market values...'; refreshStatus.style.color = currentDarkMode ? '#aaa' : '#666'; } return fetch(`https://api.torn.com/torn/?key=${scriptSettings.apiKey}&selections=items&comment=wBazaars`) .then(r => r.json()) .then(data => { if (!data.items) { console.error("Failed to fetch Torn items. Check your API key or rate limit."); if (refreshStatus) { refreshStatus.textContent = data.error ? `Error: ${data.error.error}` : 'Failed to fetch market values. Check your API key.'; refreshStatus.style.color = '#cc0000'; setTimeout(() => { refreshStatus.style.display = 'none'; }, 5000); } return false; } cachedItemsData = null; const filtered = {}; for (let [id, item] of Object.entries(data.items)) { if (item.tradeable) { filtered[id] = { name: item.name, market_value: item.market_value }; } } GM_setValue("tornrpgItems", JSON.stringify(filtered)); GM_setValue("tornrpgLastUpdate", now.toString()); if (refreshStatus) { refreshStatus.textContent = `Market values updated successfully! (${todayUTC})`; refreshStatus.style.color = '#009900'; setTimeout(() => { refreshStatus.style.display = 'none'; }, 3000); } document.querySelectorAll('.bytecore-info-container').forEach(container => { if (container.isConnected) { renderTableRows(container); } }); return true; }) .catch(err => { console.error("Error fetching Torn items:", err); if (refreshStatus) { refreshStatus.textContent = `Error: ${err.message || 'Failed to fetch market values'}`; refreshStatus.style.color = '#cc0000'; setTimeout(() => { refreshStatus.style.display = 'none'; }, 5000); } return false; }); } return Promise.resolve(false); } document.body.addEventListener('click', event => { if (event.target.id === 'refresh-market-data' || event.target.closest('#refresh-market-data')) { event.preventDefault(); const apiKeyInput = document.getElementById('bytecore-api-key'); const refreshStatus = document.getElementById('refresh-status'); if (!apiKeyInput || !apiKeyInput.value.trim()) { if (refreshStatus) { refreshStatus.style.display = 'block'; refreshStatus.textContent = 'Please enter an API key first.'; refreshStatus.style.color = '#cc0000'; setTimeout(() => { refreshStatus.style.display = 'none'; }, 3000); } return; } scriptSettings.apiKey = apiKeyInput.value.trim(); fetchTornItems(true); } }); function observeUserMenu() { const menuObserver = new MutationObserver(mutations => { mutations.forEach(mutation => { if (mutation.addedNodes.length > 0) { for (const node of mutation.addedNodes) { if (node.nodeType === Node.ELEMENT_NODE && node.classList.contains('settings-menu')) { addSettingsMenuItem(); break; } } } }); }); menuObserver.observe(document.body, { childList: true, subtree: true }); if (document.querySelector('.settings-menu')) { addSettingsMenuItem(); } } observeUserMenu(); function getSortOrderForKey(key) { return key === "price" ? "asc" : "desc"; } function cleanupResources() { if (observer) { observer.disconnect(); } if (bodyObserver) { bodyObserver.disconnect(); } } window.addEventListener('beforeunload', cleanupResources); })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址