您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Highlight Torn listings based on manually saved prices (Item Market + Bazaar), using both ID and Name
当前为
// ==UserScript== // @name Bezas Bazaar // @namespace http://tampermonkey.net/ // @version 3.0 // @description Highlight Torn listings based on manually saved prices (Item Market + Bazaar), using both ID and Name // @match https://www.torn.com/page.php?sid=ItemMarket* // @match https://www.torn.com/bazaar.php* // @grant GM_getValue // @grant GM_setValue // @grant GM_addStyle // @license MIT // ==/UserScript== (function () { 'use strict'; const normalize = str => str?.toLowerCase().replace(/\s+/g, '_'); function getSavedPrice(id, name = null) { const idKey = `manual_price_${id}`; const nameKey = name ? `manual_price_${normalizeKey(name)}` : null; const byId = GM_getValue(idKey); const byName = nameKey ? GM_getValue(nameKey) : undefined; return byId !== undefined ? byId : byName; } function savePrice(id, name, value) { if (id) GM_setValue(`manual_price_${id}`, value); if (name) GM_setValue(`manual_price_${normalizeKey(name)}`, value); } function normalizeKey(key) { return key?.toLowerCase().trim().replace(/\s+/g, '_'); } GM_addStyle(` .manual-highlight-good { background-color: #004d00 !important; color: white !important; } .manual-highlight-warning { background-color: #ffa500 !important; color: black !important; } .manual-highlight-bad { background-color: #8b0000 !important; color: white !important; } .manual-highlight-missing { background-color: #9370DB !important; color: white !important; } .manual-price-diff { margin-left: 6px; font-size: 12px; font-weight: bold; color: black; background-color: rgba(255,255,255,0.6); padding: 1px 4px; border-radius: 4px; } #manual-price-modal { position: fixed; top: 30%; left: 50%; transform: translate(-50%, -30%); background: #1e1e1e; color: white; padding: 20px; border: 2px solid #888; border-radius: 10px; z-index: 9999; display: none; } #manual-price-modal input { width: 100px; padding: 5px; font-size: 14px; } #manual-price-modal button { margin-left: 10px; padding: 5px 10px; background: #444; color: white; border: none; border-radius: 5px; cursor: pointer; } .manual-overlay { position: absolute; top: 0; left: 0; width: 100%; height: 100%; z-index: 0; border-radius: 8px; opacity: 0.6; pointer-events: none; } .itemTile___cbw7w, .item___GYCYJ { position: relative; } #manual-price-drag-handle { cursor: move; user-select: none; } `); // Modal const modal = document.createElement("div"); modal.id = "manual-price-modal"; modal.innerHTML = ` <div id="manual-price-drag-handle" style="margin-bottom: 10px; font-weight: bold; cursor: move;">⇅ Drag to move</div> <label>Manual price: $<input type="number" id="manual-price-input" /></label> <button id="manual-price-save">Save</button> <button id="manual-price-cancel">Cancel</button> `; document.body.appendChild(modal); makeModalDraggable(); let currentItemId = null; let currentItemName = null; document.getElementById("manual-price-save").onclick = () => { const val = parseInt(document.getElementById("manual-price-input").value); if (val > 0) { savePrice(currentItemId, currentItemName, val); modal.style.display = "none"; highlightAll(); } }; document.getElementById("manual-price-cancel").onclick = () => modal.style.display = "none"; function showModal(id, name) { currentItemId = id; currentItemName = name; document.getElementById("manual-price-input").value = getSavedPrice(id, name) || ''; modal.style.display = "block"; } function makeModalDraggable() { const modal = document.getElementById("manual-price-modal"); const handle = document.getElementById("manual-price-drag-handle"); let offsetX = 0, offsetY = 0, isDragging = false; handle.addEventListener("mousedown", (e) => { isDragging = true; offsetX = e.clientX - modal.offsetLeft; offsetY = e.clientY - modal.offsetTop; document.body.style.userSelect = "none"; }); document.addEventListener("mouseup", () => { isDragging = false; document.body.style.userSelect = "auto"; }); document.addEventListener("mousemove", (e) => { if (!isDragging) return; modal.style.left = `${e.clientX - offsetX}px`; modal.style.top = `${e.clientY - offsetY}px`; modal.style.right = "auto"; modal.style.bottom = "auto"; modal.style.transform = "none"; }); } function addDiff(el, diff) { if (el.querySelector('.manual-price-diff')) return; const span = document.createElement('span'); span.className = 'manual-price-diff'; span.textContent = `(${diff > 0 ? '+' : ''}${diff.toFixed(1)}%)`; el.appendChild(span); } function applyHighlight(el, listed, saved, priceEl) { el.classList.remove('manual-highlight-good', 'manual-highlight-warning', 'manual-highlight-bad', 'manual-highlight-missing'); el.style.setProperty('background-color', '', 'important'); el.style.setProperty('color', '', 'important'); if (saved === undefined) { el.classList.add('manual-highlight-missing'); el.style.setProperty('background-color', '#9370DB', 'important'); el.style.setProperty('color', '#fff', 'important'); } else { const diff = ((listed - saved) / saved) * 100; if (diff < -5) { el.classList.add('manual-highlight-good'); el.style.setProperty('background-color', '#004d00', 'important'); el.style.setProperty('color', '#fff', 'important'); } else if (diff >= -5 && diff <= 0) { el.classList.add('manual-highlight-warning'); el.style.setProperty('background-color', '#ffa500', 'important'); el.style.setProperty('color', '#000', 'important'); } else { el.classList.add('manual-highlight-bad'); el.style.setProperty('background-color', '#8b0000', 'important'); el.style.setProperty('color', '#fff', 'important'); } if (priceEl) addDiff(priceEl, diff); } } function highlightItemTiles() { document.querySelectorAll('.itemTile___cbw7w').forEach(tile => { const img = tile.querySelector('img.torn-item'); const priceSpan = tile.querySelector('.priceAndTotal___eEVS7 span'); if (!img || !priceSpan) return; const idMatch = img.src.match(/\/items\/(\d+)\//); const itemId = idMatch ? idMatch[1] : null; if (!itemId) return; // Use regex to extract the first $amount (ignoring anything in percent) const fullText = priceSpan.textContent || ''; const match = fullText.match(/\$([\d,]+)/); const listed = match ? parseInt(match[1].replace(/,/g, '')) : NaN; const saved = getSavedPrice(itemId); console.log(`[Tile DEBUG] ID=${itemId}, Raw="${fullText}", Listed=${listed}, Saved=${saved}`); if (!isNaN(listed)) { applyHighlight(tile, listed, saved, priceSpan); } }); } function highlightSellerRows(itemId) { const rows = document.querySelectorAll('li[class*="rowWrapper___"]'); const saved = getSavedPrice(itemId, null); rows.forEach(row => { const priceEl = row.querySelector('div[class*="price___"]'); const match = priceEl?.textContent.match(/\$([\d,]+)/); if (!priceEl || !match) return; const price = parseInt(match[1].replace(/,/g, '')); applyHighlight(row, price, saved, priceEl); }); } function watchSellerRows(itemId) { const observer = new MutationObserver(() => { highlightSellerRows(itemId); }); observer.observe(document.body, { childList: true, subtree: true }); } function setupBuyButtons() { document.querySelectorAll('.itemTile___cbw7w .actionButton___pb_Da').forEach(btn => { if (btn.dataset.bound === "true") return; btn.dataset.bound = "true"; btn.addEventListener('click', () => { const container = btn.closest('.itemTile___cbw7w'); const img = container?.querySelector('img.torn-item'); const nameEl = container?.querySelector('.itemName___3tW7n'); const match = img?.src?.match(/\/items\/(\d+)\//); const id = match ? match[1] : null; const name = nameEl?.textContent?.trim() || null; showModal(id, name); highlightSellerRows(id); watchSellerRows(id); }); }); } function highlightFullListings() { const table = document.querySelector('#fullListingsView table'); if (!table || (!currentItemId && !currentItemName)) return; const saved = getSavedPrice(currentItemId, currentItemName); table.querySelectorAll('tr').forEach(row => { const priceEl = row.querySelector('td:first-child'); const match = priceEl?.textContent.match(/\$([\d,]+)/); if (!match) return; const listed = parseInt(match[1].replace(/,/g, '')); applyHighlight(row, listed, saved, priceEl); }); } function highlightBazaarPage() { document.querySelectorAll('.itemsContainner___tVzIR .item___GYCYJ').forEach(item => { const nameEl = item.querySelector('.description___Y2Nrl .name___B0RW3'); const priceEl = item.querySelector('.description___Y2Nrl .price___dJqda'); if (!nameEl || !priceEl) return; const itemName = nameEl.textContent.trim(); const rawPrice = priceEl.textContent.match(/\$[\d,]+/); const listed = rawPrice ? parseInt(rawPrice[0].replace(/[^\d]/g, '')) : NaN; const saved = getSavedPrice(null, itemName); console.log(`[Bazaar DEBUG] Name=${itemName}, Raw="${rawPrice}", Listed=${listed}, Saved=${saved}`); if (!isNaN(listed)) { applyHighlight(item, listed, saved, priceEl); console.log(`[Bazaar APPLY] ${itemName} → Listed=${listed}, Saved=${saved}, Diff=${saved ? (((listed - saved) / saved) * 100).toFixed(2) + '%' : 'N/A'}`); } const btn = item.querySelector('button[aria-label^="Buy:"]'); if (btn && !btn.dataset.bound) { btn.dataset.bound = "true"; btn.addEventListener('click', () => showModal(null, itemName)); } }); } function highlightAll() { if (location.href.includes("ItemMarket")) { highlightItemTiles(); setupBuyButtons(); highlightFullListings(); } else if (location.href.includes("bazaar.php")) { highlightBazaarPage(); } } new MutationObserver(highlightAll).observe(document.body, { childList: true, subtree: true }); window.addEventListener('keydown', e => { if (e.key === "Escape") modal.style.display = "none"; }); highlightAll(); })();