Zed.city — Multiply All Numbers by 1000

Multiplies every visible number on zed.city and its subdomains by 1000

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         Zed.city — Multiply All Numbers by 1000
// @namespace    zed.city.tools
// @version      1.0
// @description  Multiplies every visible number on zed.city and its subdomains by 1000
// @match        http*://zed.city/*
// @match        http*://*.zed.city/*
// @run-at       document-idle
// @grant        none
// ==/UserScript==

(function () {
  'use strict';

  // Tags we never touch
  const SKIP_TAGS = new Set([
    'SCRIPT','STYLE','NOSCRIPT','IFRAME','OBJECT',
    'TEXTAREA','INPUT','SELECT','BUTTON','SVG','CANVAS','CODE','PRE'
  ]);

  // Mark text nodes we've processed so we don't multiply again
  const DONE = Symbol('zed-multiplied');
  const processed = new WeakSet();

  // Basic helpers
  function isEditable(node) {
    if (!node) return false;
    if (node.nodeType === Node.TEXT_NODE) node = node.parentNode;
    if (!(node instanceof Element)) return false;
    if (node.closest('[contenteditable=""], [contenteditable="true"]')) return true;
    if (node.closest('input, textarea')) return true;
    return false;
  }

  function shouldSkipNode(node) {
    if (node.nodeType !== Node.TEXT_NODE) return true;
    const p = node.parentNode;
    if (!(p instanceof Element)) return true;
    if (SKIP_TAGS.has(p.tagName)) return true;
    if (isEditable(p)) return true;
    // allow opt-out via attribute on any ancestor: data-no-thousand
    if (p.closest('[data-no-thousand]')) return true;
    return false;
  }

  // Detect simple number forms and preserve formatting
  // Handles:
  //  - 123
  //  - 1,234  or 1,234.56
  //  - 123.45
  //  - 1 234 (thin space / space grouped) — we normalize/keep spaces
  // EU-style (1.234,56) is handled best-effort.
  const numberPattern = /(?<![\w.-])(?:\d{1,3}(?:[,\u00A0\u202F ]\d{3})+|\d+)(?:[.,]\d+)?(?![\w.-])/g;

  // Insert thousands separators with given group char (comma by default)
  function formatThousands(xStr, groupChar) {
    const parts = xStr.split('.');
    parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, groupChar);
    return parts.join('.');
  }

  // Best-effort formatter preserving original separators/decimals
  function formatLike(original, value) {
    // Normalize: work with absolute string & sign separately
    const isNeg = value < 0;
    let abs = Math.abs(value);

    // Decide decimal separator in original
    // If original contains both '.' and ',', assume the rightmost is decimal
    const lastDot = original.lastIndexOf('.');
    const lastComma = original.lastIndexOf(',');
    let decSep = null;
    if (lastDot === -1 && lastComma === -1) {
      decSep = null;
    } else if (lastDot > lastComma) {
      decSep = '.';
    } else {
      decSep = ',';
    }

    // Count decimal digits in original (if any)
    let origDecimals = 0;
    if (decSep) {
      const idx = original.lastIndexOf(decSep);
      if (idx !== -1) {
        origDecimals = original.length - idx - 1;
      }
    }

    // Grouping character from original (prefer comma or narrow space if seen)
    let groupChar = ',';
    const hasSpGroup = /(?:\d[\u00A0\u202F ]\d{3})/.test(original);
    if (hasSpGroup) groupChar = original.match(/[\u00A0\u202F ]/)?.[0] || ' ';
    else if (/,/.test(original) && decSep !== ',') groupChar = ',';
    else if (/\./.test(original) && decSep !== '.') groupChar = '.';

    // Build numeric with desired decimals
    let numStr = abs.toFixed(origDecimals);

    // If EU-style decimal (comma), convert '.' decimal to ','
    if (decSep === ',') {
      numStr = numStr.replace('.', ',');
      // For grouping, temporarily swap to '.' to insert separators easily
      const tmp = numStr.replace(',', '.');
      numStr = formatThousands(tmp, '.').replace('.', ',');
      // Reinsert grouping (.) already applied
      return (isNeg ? '-' : '') + numStr;
    }

    // Default: '.' decimal, group with groupChar
    numStr = formatThousands(numStr, groupChar);
    return (isNeg ? '-' : '') + numStr;
  }

  function multiplyInText(text) {
    return text.replace(numberPattern, (match) => {
      // Normalize the matched number to parse
      let normalized = match.replace(/\u00A0|\u202F| /g, '');
      // If it's EU-style like 1.234,56 => replace thousands '.' then decimal ',' to '.'
      if (/[.,]/.test(normalized)) {
        const lastDot = normalized.lastIndexOf('.');
        const lastComma = normalized.lastIndexOf(',');
        if (lastComma > lastDot) {
          // Assume comma is decimal
          normalized = normalized.replace(/\./g, '').replace(',', '.');
        } else {
          // Assume dot is decimal; strip commas as group
          normalized = normalized.replace(/,/g, '');
        }
      } else {
        // No decimal separators, just strip spaces/nbspaces
        normalized = normalized.replace(/,/g, '');
      }

      let n = Number(normalized);
      if (!isFinite(n)) return match;
      n = n * 1000;

      return formatLike(match, n);
    });
  }

  function processTextNode(node) {
    if (processed.has(node)) return;
    if (shouldSkipNode(node)) return;

    const before = node.nodeValue;
    if (!before || !/\d/.test(before)) return;

    const after = multiplyInText(before);
    if (after !== before) {
      node.nodeValue = after;
      processed.add(node);
    }
  }

  function walk(root) {
    const walker = document.createTreeWalker(
      root,
      NodeFilter.SHOW_TEXT,
      {
        acceptNode(n) {
          // Fast path: skip empty or digitless nodes
          return (n.nodeValue && /\d/.test(n.nodeValue)) ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_REJECT;
        }
      }
    );
    let node;
    while ((node = walker.nextNode())) {
      processTextNode(node);
    }
  }

  // Initial pass
  walk(document.body);

  // Observe changes for dynamically loaded content
  const mo = new MutationObserver((mutations) => {
    for (const m of mutations) {
      if (m.type === 'characterData') {
        processTextNode(m.target);
      } else {
        for (const added of m.addedNodes) {
          if (added.nodeType === Node.TEXT_NODE) {
            processTextNode(added);
          } else if (added.nodeType === Node.ELEMENT_NODE) {
            if (!SKIP_TAGS.has(added.tagName)) walk(added);
          }
        }
      }
    }
  });

  mo.observe(document.documentElement || document.body, {
    childList: true,
    characterData: true,
    subtree: true
  });

  // Optional: expose a small toggle and a per-element opt-out attribute.
  // To prevent conversion in a specific region, add: data-no-thousand to that element.
})();