您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Show USD price next to ETH amounts on death.fun
// ==UserScript== // @name death.fun ETH → USD overlay // @namespace http://tampermonkey.net/ // @version 0.1.0 // @description Show USD price next to ETH amounts on death.fun // @author Koi // @match https://death.fun/ // @run-at document-idle // @grant none // @license MIT // ==/UserScript== (function () { 'use strict'; const ETH_REGEX = /([-+]?[\d.,]+)\s*ETH\b/i; const NUMERIC_REGEX = /^[-+]?[\d.,]+$/; const MAX_TEXT_LENGTH = 90; const conversions = new WeakMap(); const inputDisplays = new WeakMap(); const trackedInputs = new Set(); const usdFormatterLarge = new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD', minimumFractionDigits: 2, maximumFractionDigits: 2, }); const usdFormatterSmall = new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD', minimumFractionDigits: 4, maximumFractionDigits: 4, }); let ethPriceUsd = null; function formatUsd(value, forcePlus) { const abs = Math.abs(value); const formatter = abs < 1 ? usdFormatterSmall : usdFormatterLarge; let formatted = formatter.format(abs); if (value < 0) { formatted = '-' + formatted; } else if (forcePlus) { formatted = '+' + formatted; } return formatted; } function updateSpan(span) { const ethAmount = parseFloat(span.dataset.ethAmount); if (!Number.isFinite(ethAmount)) { return; } const sign = span.dataset.ethSign || ''; if (ethPriceUsd == null) { span.textContent = ' (USD …)'; span.title = 'USD conversion in progress…'; return; } const usdValue = ethAmount * ethPriceUsd; const formatted = formatUsd(usdValue, sign === '+' && usdValue >= 0); span.textContent = ' (' + formatted + ' USD)'; span.title = 'Based on 1 ETH = ' + formatUsd(ethPriceUsd, false) + ' USD'; } function deriveSign(matchText, parent, node) { const trimmed = matchText.trim(); if (trimmed.startsWith('+')) { return '+'; } if (trimmed.startsWith('-')) { return '-'; } const parentText = (parent?.textContent || '').trim(); const parentMatch = parentText.match(ETH_REGEX); if (parentMatch) { const parentTrimmed = parentMatch[1].trim(); if (parentTrimmed.startsWith('+')) { return '+'; } if (parentTrimmed.startsWith('-')) { return '-'; } } const prev = node.previousSibling; if (prev && prev.nodeType === Node.TEXT_NODE) { const prevTrim = prev.textContent?.trim(); if (prevTrim === '+') { return '+'; } if (prevTrim === '-') { return '-'; } } return ''; } function elementHasEthIcon(element) { if (!element || element.nodeType !== Node.ELEMENT_NODE) { return false; } return element.querySelector?.('svg[viewBox="0 0 9 15"]') != null; } function processTextNode(node) { if (node.nodeType !== Node.TEXT_NODE) { return; } const parent = node.parentNode; if (!parent) { return; } if (parent instanceof HTMLElement && parent.classList.contains('deathusd-conversion')) { return; } const parentTag = parent.nodeName; if (parentTag === 'SCRIPT' || parentTag === 'STYLE' || parentTag === 'NOSCRIPT') { return; } const text = node.textContent || ''; const trimmed = text.trim(); if (!trimmed || trimmed.length > MAX_TEXT_LENGTH) { return; } function isLikelyEthContext() { const elementParent = node.parentElement; if (elementHasEthIcon(elementParent)) { return true; } const grandParent = elementParent?.parentElement; if (elementHasEthIcon(grandParent)) { return true; } const labels = [elementParent, grandParent] .map(el => (el?.getAttribute?.('aria-label') || el?.getAttribute?.('title') || '')?.toLowerCase?.()) .filter(Boolean); if (labels.some(label => label.includes('eth'))) { return true; } return false; } let match = text.match(ETH_REGEX); let numberPart; let sign; let ethAmount; if (match) { numberPart = match[1].replace(/,/g, ''); ethAmount = parseFloat(numberPart); if (!Number.isFinite(ethAmount)) { return; } sign = deriveSign(match[1], parent, node); } else if (NUMERIC_REGEX.test(trimmed) && isLikelyEthContext()) { numberPart = trimmed.replace(/,/g, ''); ethAmount = parseFloat(numberPart); if (!Number.isFinite(ethAmount)) { return; } sign = deriveSign(trimmed, parent, node); } else { return; } if (sign === '-' && ethAmount > 0) { ethAmount = -ethAmount; } let span = conversions.get(node); if (!span || !span.isConnected || span.parentNode !== parent) { span = document.createElement('span'); span.className = 'deathusd-conversion'; span.style.marginLeft = '0.35em'; span.style.fontSize = '0.85em'; span.style.opacity = '0.75'; parent.insertBefore(span, node.nextSibling); conversions.set(node, span); } span.dataset.ethAmount = String(ethAmount); span.dataset.ethSign = sign; updateSpan(span); } function isLikelyEthInput(input) { if (!(input instanceof HTMLInputElement)) { return false; } const type = (input.getAttribute('type') || 'text').toLowerCase(); if (!['', 'text', 'number'].includes(type)) { return false; } const numericMode = input.getAttribute('inputmode'); if (numericMode && !['decimal', 'numeric'].includes(numericMode)) { return false; } if (input.dataset.deathusdInput === 'yes') { return true; } const attrHints = [input.getAttribute('aria-label'), input.getAttribute('placeholder'), input.name] .filter(Boolean) .map(value => value.toLowerCase()); if (attrHints.some(value => value.includes('usd'))) { return false; } if (attrHints.some(value => value.includes('eth'))) { input.dataset.deathusdInput = 'yes'; return true; } const containers = [input.parentElement, input.parentElement?.parentElement, input.parentElement?.parentElement?.parentElement, input.closest('[role="group"]')]; for (const container of containers) { if (!container) { continue; } if (elementHasEthIcon(container)) { input.dataset.deathusdInput = 'yes'; return true; } const text = container.textContent || ''; if (/\b(eth|bet)\b/i.test(text)) { input.dataset.deathusdInput = 'yes'; return true; } } let ancestor = input.parentElement; for (let depth = 0; ancestor && depth < 5; depth += 1) { if (elementHasEthIcon(ancestor)) { input.dataset.deathusdInput = 'yes'; return true; } const text = ancestor.textContent || ''; if (/\beth\b/i.test(text)) { input.dataset.deathusdInput = 'yes'; return true; } ancestor = ancestor.parentElement; } return false; } function updateInputDisplay(input) { const span = inputDisplays.get(input); if (!span) { return; } const rawValue = (input.value || '').trim(); if (!rawValue) { span.textContent = ''; span.style.display = 'none'; return; } const normalized = rawValue.replace(/\s+/g, '').replace(/,/g, ''); const amount = parseFloat(normalized); if (!Number.isFinite(amount)) { span.textContent = ''; span.style.display = 'none'; return; } span.style.display = 'block'; if (ethPriceUsd == null) { span.textContent = '≈ USD …'; span.title = 'USD conversion in progress…'; return; } const usdValue = amount * ethPriceUsd; span.textContent = '≈ ' + formatUsd(usdValue, false) + ' USD'; span.title = 'Based on 1 ETH = ' + formatUsd(ethPriceUsd, false) + ' USD'; } function ensureInputAugmented(input) { if (!isLikelyEthInput(input)) { return; } let span = inputDisplays.get(input); if (!span || !span.isConnected) { span = document.createElement('div'); span.className = 'deathusd-input'; span.style.marginTop = '0.35em'; span.style.fontSize = '0.85em'; span.style.opacity = '0.75'; span.style.textAlign = 'right'; span.style.fontFamily = 'inherit'; span.style.display = 'none'; input.insertAdjacentElement('afterend', span); inputDisplays.set(input, span); trackedInputs.add(input); const handler = () => updateInputDisplay(input); input.addEventListener('input', handler); input.addEventListener('change', handler); } updateInputDisplay(input); } function processElement(element) { if (element instanceof HTMLInputElement) { ensureInputAugmented(element); } } function scanNode(node) { if (node.nodeType === Node.TEXT_NODE) { processTextNode(node); } else if (node.nodeType === Node.ELEMENT_NODE) { processElement(node); node.childNodes.forEach(scanNode); } } function refreshAllSpans() { document.querySelectorAll('.deathusd-conversion').forEach(updateSpan); } function refreshAllInputs() { for (const input of trackedInputs) { if (!document.contains(input)) { trackedInputs.delete(input); continue; } ensureInputAugmented(input); } } async function fetchPrice() { try { const response = await fetch('https://min-api.cryptocompare.com/data/price?fsym=ETH&tsyms=USD', { cache: 'no-cache', headers: { Accept: 'application/json' }, }); if (!response.ok) { throw new Error('HTTP ' + response.status); } const data = await response.json(); const price = Number(data?.USD); if (Number.isFinite(price) && price > 0) { ethPriceUsd = price; refreshAllSpans(); refreshAllInputs(); } } catch (error) { console.warn('[deathusd] Unable to fetch ETH price', error); } } function init() { if (!document.body) { return; } scanNode(document.body); const observer = new MutationObserver(mutations => { for (const mutation of mutations) { if (mutation.type === 'childList') { mutation.addedNodes.forEach(scanNode); } else if (mutation.type === 'characterData') { scanNode(mutation.target); } else if (mutation.type === 'attributes' && mutation.target instanceof HTMLInputElement) { ensureInputAugmented(mutation.target); updateInputDisplay(mutation.target); } } }); observer.observe(document.body, { childList: true, subtree: true, characterData: true, attributes: true, attributeFilter: ['value'], }); fetchPrice(); setInterval(fetchPrice, 60000); } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init, { once: true }); } else { init(); } })();