您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Show RW bonus name and percentage directly on each Item Market listing (no hover required)
// ==UserScript== // @name Torn Item Market — RW Bonus Badges // @namespace smitty.torn.com // @version 1.0 // @description Show RW bonus name and percentage directly on each Item Market listing (no hover required) // @author Smitty // @match https://www.torn.com/page.php?sid=ItemMarket* // @match https://pda.torn.com/page.php?sid=ItemMarket* // @run-at document-idle // @grant none // @license MIT // ==/UserScript== (function () { "use strict"; // Only run on Item Market pages (SPA friendly) const isItemMarket = () => { const u = new URL(location.href); return u.searchParams.get("sid") === "ItemMarket" || document.querySelector("#item-market-root"); }; const STYLE_ID = "rw-bonus-badge-style"; function injectStyles() { if (document.getElementById(STYLE_ID)) return; const css = ` .rw-badge { position: absolute; left: 6px; bottom: 6px; z-index: 5; font: 600 11px/1.2 system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif; background: rgba(20, 20, 20, 0.85); color: #fff; padding: 4px 6px; border-radius: 4px; box-shadow: 0 1px 2px rgba(0,0,0,.25); pointer-events: none; max-width: 92%; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } .rw-badge .rw-bonus { margin-right: 6px; opacity: 0.9; } .rw-badge .rw-bonus:last-child { margin-right: 0; } .rw-img-wrap-relative { position: relative !important; } `; const style = document.createElement("style"); style.id = STYLE_ID; style.textContent = css; document.head.appendChild(style); } // Parse bonus display text from an <i class="bonus-attachment-..."> node function parseBonusIcon(iEl) { if (!iEl) return null; const name = iEl.getAttribute("data-bonus-attachment-title") || iEl.getAttribute("aria-label") || ""; const desc = iEl.getAttribute("data-bonus-attachment-description") || ""; // Grab the first percentage like "12%" or "7.5%" const m = desc.match(/(\d+(?:\.\d+)?)\s*%/); const pct = m ? m[1] : null; // Fallback: if no percentage present, just surface the description const label = pct ? `${name} ${pct}%` : (name || desc || "").trim(); return label || null; } // Build a badge label for one tile function buildBadgeText(tile) { // Any RW bonus icons live inside a container with a class containing "bonuses" const iconContainer = tile.querySelector("div[class*='bonuses']"); if (!iconContainer) return null; const icons = iconContainer.querySelectorAll("i[class^='bonus-attachment-'], i[class*=' bonus-attachment-']"); if (!icons.length) return null; const labels = []; icons.forEach(i => { const label = parseBonusIcon(i); if (label) labels.push(label); }); if (!labels.length) return null; // Merge duplicates, keep order const seen = new Set(); const unique = labels.filter(t => (seen.has(t) ? false : seen.add(t))); return unique; } function ensureImageWrapperRelative(tile) { // Prefer the image wrapper to anchor the badge let imgWrap = tile.querySelector("div[class*='imageWrapper']"); if (!imgWrap) { // fall back to the main tile node imgWrap = tile; } if (!imgWrap.classList.contains("rw-img-wrap-relative")) { imgWrap.classList.add("rw-img-wrap-relative"); } return imgWrap; } function applyBadge(tile) { if (!tile || tile.dataset.rwBadgeApplied === "1") return; const labels = buildBadgeText(tile); // Only add a badge when there is at least one RW icon with parsed text if (!labels || labels.length === 0) { tile.dataset.rwBadgeApplied = "1"; return; } const imgWrap = ensureImageWrapperRelative(tile); // Remove any old badge we created const existing = imgWrap.querySelector(":scope > .rw-badge"); if (existing) existing.remove(); // Create badge const badge = document.createElement("div"); badge.className = "rw-badge"; badge.title = labels.join(" • "); // Cap how many labels we render visually to avoid overflow // Always show the first; if more exist, append “+N” if (labels.length === 1) { badge.textContent = labels[0]; } else { const first = document.createElement("span"); first.className = "rw-bonus"; first.textContent = labels[0]; badge.appendChild(first); const more = document.createElement("span"); more.textContent = `+${labels.length - 1}`; badge.appendChild(more); } imgWrap.appendChild(badge); tile.dataset.rwBadgeApplied = "1"; } function scanOnce(root = document) { // Each listing tile has a class beginning with "itemTile" const tiles = root.querySelectorAll("div[class^='itemTile'], div[class*=' itemTile']"); tiles.forEach(applyBadge); } // Observe dynamic content (infinite scroll, filters, SPA route changes) function observe() { const root = document.getElementById("item-market-root") || document.body; const mo = new MutationObserver(muts => { let needsScan = false; for (const m of muts) { if (m.addedNodes && m.addedNodes.length) { needsScan = true; break; } } if (needsScan) scanOnce(root); }); mo.observe(root, { childList: true, subtree: true }); } function boot() { if (!isItemMarket()) return; injectStyles(); scanOnce(); observe(); } // Handle SPA navigations let lastHref = location.href; new MutationObserver(() => { if (location.href !== lastHref) { lastHref = location.href; setTimeout(boot, 50); } }).observe(document, { subtree: true, childList: true }); // Initial start if (document.readyState === "complete" || document.readyState === "interactive") { boot(); } else { window.addEventListener("DOMContentLoaded", boot, { once: true }); } })(); (function () { "use strict"; // Only run on Item Market pages (SPA friendly) const isItemMarket = () => { const u = new URL(location.href); return u.searchParams.get("sid") === "ItemMarket" || document.querySelector("#item-market-root"); }; const STYLE_ID = "rw-bonus-badge-style"; function injectStyles() { if (document.getElementById(STYLE_ID)) return; const css = ` .rw-badge { position: absolute; left: 6px; bottom: 6px; z-index: 5; font: 600 11px/1.2 system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif; background: rgba(20, 20, 20, 0.85); color: #fff; padding: 4px 6px; border-radius: 4px; box-shadow: 0 1px 2px rgba(0,0,0,.25); pointer-events: none; max-width: 92%; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } .rw-badge .rw-bonus { margin-right: 6px; opacity: 0.9; } .rw-badge .rw-bonus:last-child { margin-right: 0; } .rw-img-wrap-relative { position: relative !important; } `; const style = document.createElement("style"); style.id = STYLE_ID; style.textContent = css; document.head.appendChild(style); } // Parse bonus display text from an <i class="bonus-attachment-..."> node function parseBonusIcon(iEl) { if (!iEl) return null; const name = iEl.getAttribute("data-bonus-attachment-title") || iEl.getAttribute("aria-label") || ""; const desc = iEl.getAttribute("data-bonus-attachment-description") || ""; // Grab the first percentage like "12%" or "7.5%" const m = desc.match(/(\d+(?:\.\d+)?)\s*%/); const pct = m ? m[1] : null; // Fallback: if no percentage present, just surface the description const label = pct ? `${name} ${pct}%` : (name || desc || "").trim(); return label || null; } // Build a badge label for one tile function buildBadgeText(tile) { // Any RW bonus icons live inside a container with a class containing "bonuses" const iconContainer = tile.querySelector("div[class*='bonuses']"); if (!iconContainer) return null; const icons = iconContainer.querySelectorAll("i[class^='bonus-attachment-'], i[class*=' bonus-attachment-']"); if (!icons.length) return null; const labels = []; icons.forEach(i => { const label = parseBonusIcon(i); if (label) labels.push(label); }); if (!labels.length) return null; // Merge duplicates, keep order const seen = new Set(); const unique = labels.filter(t => (seen.has(t) ? false : seen.add(t))); return unique; } function ensureImageWrapperRelative(tile) { // Prefer the image wrapper to anchor the badge let imgWrap = tile.querySelector("div[class*='imageWrapper']"); if (!imgWrap) { // fall back to the main tile node imgWrap = tile; } if (!imgWrap.classList.contains("rw-img-wrap-relative")) { imgWrap.classList.add("rw-img-wrap-relative"); } return imgWrap; } function applyBadge(tile) { if (!tile || tile.dataset.rwBadgeApplied === "1") return; const labels = buildBadgeText(tile); // Only add a badge when there is at least one RW icon with parsed text if (!labels || labels.length === 0) { tile.dataset.rwBadgeApplied = "1"; return; } const imgWrap = ensureImageWrapperRelative(tile); // Remove any old badge we created const existing = imgWrap.querySelector(":scope > .rw-badge"); if (existing) existing.remove(); // Create badge const badge = document.createElement("div"); badge.className = "rw-badge"; badge.title = labels.join(" • "); // Cap how many labels we render visually to avoid overflow // Always show the first; if more exist, append “+N” if (labels.length === 1) { badge.textContent = labels[0]; } else { const first = document.createElement("span"); first.className = "rw-bonus"; first.textContent = labels[0]; badge.appendChild(first); const more = document.createElement("span"); more.textContent = `+${labels.length - 1}`; badge.appendChild(more); } imgWrap.appendChild(badge); tile.dataset.rwBadgeApplied = "1"; } function scanOnce(root = document) { // Each listing tile has a class beginning with "itemTile" const tiles = root.querySelectorAll("div[class^='itemTile'], div[class*=' itemTile']"); tiles.forEach(applyBadge); } // Observe dynamic content (infinite scroll, filters, SPA route changes) function observe() { const root = document.getElementById("item-market-root") || document.body; const mo = new MutationObserver(muts => { let needsScan = false; for (const m of muts) { if (m.addedNodes && m.addedNodes.length) { needsScan = true; break; } } if (needsScan) scanOnce(root); }); mo.observe(root, { childList: true, subtree: true }); } function boot() { if (!isItemMarket()) return; injectStyles(); scanOnce(); observe(); } // Handle SPA navigations let lastHref = location.href; new MutationObserver(() => { if (location.href !== lastHref) { lastHref = location.href; setTimeout(boot, 50); } }).observe(document, { subtree: true, childList: true }); // Initial start if (document.readyState === "complete" || document.readyState === "interactive") { boot(); } else { window.addEventListener("DOMContentLoaded", boot, { once: true }); } })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址