Amazon.co.jp URL Cleaner 🔗🧹

Amazon.co.jp 用URLクリーナー。パス中の /ref= や一般的なトラッキング用パラメータを削除します。商品ページは /dp/ASIN に正規化し、それ以外は既知の追跡要素のみ除去して他のパラメータは保持(フィルタ等を維持)。History/Location・クリック・SPA遷移をフックし、URLを常に読みやすくプライバシー配慮に保ちます。

スクリプトをインストールするには、Tampermonkey, GreasemonkeyViolentmonkey のような拡張機能のインストールが必要です。

You will need to install an extension such as Tampermonkey to install this script.

スクリプトをインストールするには、TampermonkeyViolentmonkey のような拡張機能のインストールが必要です。

スクリプトをインストールするには、TampermonkeyUserscripts のような拡張機能のインストールが必要です。

このスクリプトをインストールするには、Tampermonkeyなどの拡張機能をインストールする必要があります。

このスクリプトをインストールするには、ユーザースクリプト管理ツールの拡張機能をインストールする必要があります。

(ユーザースクリプト管理ツールは設定済みなのでインストール!)

このスタイルをインストールするには、Stylusなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus などの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus tなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

(ユーザースタイル管理ツールは設定済みなのでインストール!)

作者のサイトでサポートを受ける。または、このスクリプトの質問や評価の投稿はこちら通報はこちらへお寄せください
// ==UserScript==
// @name         Amazon.co.jp URL Cleaner 🔗🧹
// @name:ja      Amazon.co.jp URLクリーナー 🔗🧹
// @name:en      Amazon.co.jp URL Cleaner 🔗🧹
// @name:zh-CN   Amazon.co.jp 链接清理器 🔗🧹
// @name:zh-TW   Amazon.co.jp 連結清理器 🔗🧹
// @name:ko      Amazon.co.jp URL 클리너 🔗🧹
// @name:fr      Nettoyeur d’URL Amazon.co.jp 🔗🧹
// @name:es      Limpiador de URL de Amazon.co.jp 🔗🧹
// @name:de      Amazon.co.jp-URL-Cleaner 🔗🧹
// @name:pt-BR   Limpador de URL da Amazon.co.jp 🔗🧹
// @name:ru      Очистка URL Amazon.co.jp 🔗🧹
// @namespace    https://github.com/koyasi777/amazon-jp-url-cleaner
// @version      2.0.2
// @description  Amazon.co.jp 用URLクリーナー。パス中の /ref= や一般的なトラッキング用パラメータを削除します。商品ページは /dp/ASIN に正規化し、それ以外は既知の追跡要素のみ除去して他のパラメータは保持(フィルタ等を維持)。History/Location・クリック・SPA遷移をフックし、URLを常に読みやすくプライバシー配慮に保ちます。
// @description:ja   Amazon.co.jp 用URLクリーナー。パス中の /ref= や一般的なトラッキング用パラメータを削除します。商品ページは /dp/ASIN に正規化し、それ以外は既知の追跡要素のみ除去して他のパラメータは保持(フィルタ等を維持)。History/Location・クリック・SPA遷移をフックし、URLを常に読みやすくプライバシー配慮に保ちます。
// @description:en   Amazon.co.jp URL cleaner userscript. Removes /ref= path segments and common tracking params. Product pages normalize to /dp/ASIN; other pages remove known tracking while keeping other params so filters work. Hooks History/Location, link clicks, and SPA navigation to keep URLs readable and privacy-friendly.
// @description:zh-CN  Amazon.co.jp URL 清理脚本:移除路径中的 /ref= 片段与常见跟踪参数。商品页规范为 /dp/ASIN;非商品页仅删已知跟踪参数并保留其它参数以确保筛选等功能可用。拦截 History/Location、链接点击与 SPA 路由,让 URL 始终更易读、更注重隐私。
// @description:zh-TW  Amazon.co.jp URL 清理腳本:移除路徑中的 /ref= 段落與常見追蹤參數。商品頁正規化為 /dp/ASIN;非商品頁僅刪已知追蹤參數並保留其它參數以維持篩選等功能。攔截 History/Location、連結點擊與 SPA 導覽,讓 URL 一直更易讀、更重視隱私。
// @description:ko    Amazon.co.jp URL 클리너 유저스크립트. 경로의 /ref= 구간과 일반적인 추적 파라미터를 제거합니다. 상품 페이지는 /dp/ASIN으로 정리하고, 비상품 페이지는 알려진 추적만 제거하며 나머지는 유지해 필터 등이 동작하게 합니다. History/Location·클릭·SPA 내비게이션을 후킹해 URL을 항상 읽기 쉽고 프라이버시 친화적으로 유지합니다.
// @description:fr    Userscript de nettoyage d’URL Amazon.co.jp. Supprime /ref= (chemin) et les paramètres de suivi courants. Pages produit → /dp/ASIN ; autres pages : retire le tracking connu en gardant les autres paramètres (filtres OK). Intercepte History/Location, clics et navigation SPA pour garder des URL lisibles et respectueuses de la vie privée.
// @description:es    Userscript limpiador de URL para Amazon.co.jp. Elimina /ref= (ruta) y parámetros de seguimiento comunes. Páginas de producto → /dp/ASIN; otras páginas: quita tracking conocido y conserva el resto (filtros OK). Engancha History/Location, clics y navegación SPA para mantener URLs legibles y más privadas.
// @description:de    Amazon.co.jp-URL-Cleaner-Userscript. Entfernt /ref= im Pfad und gängige Tracking-Parameter. Produktseiten → /dp/ASIN; andere Seiten: entfernt nur bekanntes Tracking und behält übrige Parameter (Filter ok). Hookt History/Location, Link-Klicks und SPA-Navigation, damit URLs lesbar und datenschutzfreundlich bleiben.
// @description:pt-BR Userscript limpador de URL para Amazon.co.jp. Remove /ref= no caminho e parâmetros comuns de rastreamento. Páginas de produto → /dp/ASIN; demais páginas: remove tracking conhecido e mantém o restante (filtros OK). Intercepta History/Location, cliques e navegação SPA para manter URLs legíveis e mais privadas.
// @description:ru    Скрипт очистки URL для Amazon.co.jp. Удаляет /ref= в пути и типичные трекинговые параметры. Товарные страницы → /dp/ASIN; остальные: убирает известный трекинг, сохраняя прочие параметры (фильтры работают). Перехватывает History/Location, клики и SPA-навигацию, чтобы URL оставались читаемыми и более приватными.
// @author       koyasi777
// @license      MIT
// @homepageURL  https://github.com/koyasi777/amazon-jp-url-cleaner
// @supportURL   https://github.com/koyasi777/amazon-jp-url-cleaner/issues
// @icon         https://www.amazon.co.jp/favicon.ico
// @match        https://www.amazon.co.jp/*
// @grant        none
// @run-at       document-start
// @noframes
// ==/UserScript==

(function () {
  'use strict';

  // --- Configuration -----------------------------------------

  // 【Mode A: 商品ページ用】許可するクエリキー(ホワイトリスト)
  // 必要なパラメータ(アフィリエイトのtagや言語設定など)のみをここに定義。
  // 空配列 [] なら、/dp/ASIN だけの最もクリーンなURLになります。
  const PRODUCT_ALLOW_KEYS = [];

  // 【Mode B: その他ページ用】削除対象パラメータ(ブラックリスト)
  // 検索結果やトップページ等で「機能は壊さずにトラッキングだけ消す」ためのリスト。
  const TRACKING_BLACKLIST = new Set([
    'ref', 'ref_', 'pf_rd_r', 'pf_rd_p', 'pf_rd_m', 'pf_rd_s', 'pf_rd_t', 'pf_rd_i',
    'pd_rd_r', 'pd_rd_w', 'pd_rd_wg', 'qid', 'sr', 'keywords', // keywordsは検索維持のため例外判定あり
    'dchild', 'crid', 'sprefix', 'field-keywords', 'hvpos', 'hvexid', 'hvnetw',
    'hvrand', 'hvqmt', 'hvbmt', 'hvdev', 'hvdvcmdl', 'hvlocint', 'hvlocphy',
    'hvtargid', 'hydadcr', '_encoding','ie',
  ]);

  // 上記ブラックリストに含まれていても、検索結果ページ等で機能維持のために残すべきキー
  const FUNCTIONAL_KEYS_ON_SEARCH = new Set(['keywords', 'k', 'rh', 'page', 'sort', 'node']);

  // --- Helpers ---------------------------------------------------------------

  /**
   * 安全なURLオブジェクト生成
   * URL.canParse が使えない環境への配慮も含め、パース不可なら例外を投げるかnullを返す
   */
  function toURL(input) {
    const s = String(input);
    // ベースURL解決を厳密に行う
    try {
      return new URL(s, location.href);
    } catch {
      throw new TypeError(`Unparsable URL: ${s}`);
    }
  }

  /**
   * パスからASINを抽出
   * /dp/, /gp/product/, /gp/aw/d/ に対応
   */
  function extractASIN(pathname) {
    const m = pathname.match(/\/(?:dp|gp\/product|gp\/aw\/d)\/([A-Z0-9]{10})(?:[/?]|$)/i);
    return m ? m[1].toUpperCase() : null;
  }

  // --- Core Logic: The Dual-Mode Canonicalizer -------------------------------

  function canonicalize(input) {
    let url;
    try {
      url = toURL(input);
    } catch {
      // パース不能な文字列は触らず返す(安全性優先)
      return String(input);
    }

    // Safety check: amazon.co.jp 以外は絶対に触らない
    if (!/\.amazon\.co\.jp$/i.test(url.hostname)) return url.href;

    const asin = extractASIN(url.pathname);

    // -------------------------------------------------------------------------
    // Strategy A: 商品ページ (Strict Reconstruction)
    // -------------------------------------------------------------------------
    if (asin) {
      // 言語プレフィックス(/-/en/ や /-/es/ 等)があれば保持
      const langPrefixMatch = url.pathname.match(/^\/-\/[^/]+\//);
      const prefix = langPrefixMatch ? langPrefixMatch[0].slice(0, -1) : '';

      // 許可されたクエリだけをホワイトリスト方式で再構築
      const keptParams = new URLSearchParams();
      if (PRODUCT_ALLOW_KEYS.length) {
        const allow = new Set(PRODUCT_ALLOW_KEYS.map(k => k.toLowerCase()));
        for (const [k, v] of url.searchParams) {
          if (allow.has(k.toLowerCase())) keptParams.append(k, v);
        }
      }
      const qs = keptParams.toString();

      // フラグメント(#)は維持しつつ、正規化URLを返す
      return `${url.origin}${prefix}/dp/${asin}${qs ? `?${qs}` : ''}${url.hash}`;
    }

    // -------------------------------------------------------------------------
    // Strategy B: その他ページ (General Cleaning)
    // -------------------------------------------------------------------------
    // 検索結果、トップページ、アカウントサービス等は、ブラックリストにあるゴミだけを除去する。

    // 1. Path Cleaning: URLパス内の /ref=... をカット
    if (url.pathname.includes('/ref=')) {
      url.pathname = url.pathname.split('/ref=')[0];
      if (url.pathname === '') url.pathname = '/';
    }

    // 2. Query Cleaning: トラッキングパラメータの除去
    const isSearchPage = url.pathname.startsWith('/s');

    // /stores/ 系だけ lp_asin を落とす(流入元ASINの文脈/計測用途になりがち)
    const isStorePage = url.pathname.startsWith('/stores/');
    if (isStorePage) {
      url.searchParams.delete('lp_asin');
    }

    // 検索ページの旧パラメータを正規形へ寄せる(検索語が消えるのを防ぐ)
    if (isSearchPage) {
      // field-keywords を k に正規化(k が無いときだけ)
      if (!url.searchParams.has('k') && url.searchParams.has('field-keywords')) {
        url.searchParams.set('k', url.searchParams.get('field-keywords') || '');
        url.searchParams.delete('field-keywords');
      }
    }

    const keys = Array.from(url.searchParams.keys());

    for (const key of keys) {
      const lowerKey = key.toLowerCase();

      // ref=... は全ページで無条件削除
      if (lowerKey === 'ref' || lowerKey.startsWith('ref_')) {
        url.searchParams.delete(key);
        continue;
      }

      // ブラックリスト判定
      if (
        lowerKey.startsWith('pf_rd_') ||
        lowerKey.startsWith('pd_rd_') ||
        TRACKING_BLACKLIST.has(lowerKey)
      ) {
        // 例外: 検索ページで機能的に必要なキーなら維持 (例: keywords)
        if (isSearchPage && FUNCTIONAL_KEYS_ON_SEARCH.has(lowerKey)) continue;

        url.searchParams.delete(key);
      }
    }

    return url.href;
  }

  // --- Execution & Hooks: Robustness ---------------------------------

  function normalizeHere() {
    const target = canonicalize(location.href);
    if (target !== location.href) {
      try {
        // history.state を維持しつつ URL のみ置換
        history.replaceState(history.state, document.title, target);
      } catch {
        // フォールバック
        history.replaceState(null, '', target);
      }
      return true; // 変更あり
    }
    return false; // 変更なし
  }

  // 1) 初期実行(最速タイミング)
  normalizeHere();

  // 2) History API Hook
  (function hookHistory() {
    const _push = history.pushState;
    const _replace = history.replaceState;

    history.pushState = function (state, title, url) {
      if (url !== undefined && url !== null) {
        try { url = canonicalize(url); } catch {}
        return _push.call(this, state, title, url);
      }
      return _push.call(this, state, title);
    };

    history.replaceState = function (state, title, url) {
      if (url !== undefined && url !== null) {
        try { url = canonicalize(url); } catch {}
        return _replace.call(this, state, title, url);
      } else {
        // 引数なしreplaceでも現在地を浄化
        const target = canonicalize(location.href);
        if (target !== location.href) {
          return _replace.call(this, state, title, target);
        }
        return _replace.call(this, state, title);
      }
    };

    // SPAバック/フォワード時の再正規化
    window.addEventListener('popstate', normalizeHere, { capture: true });
  })();

  // 3) Location API Hook
  (function hookLocation() {
    // ユーティリティ: 同一URLならリロードを防ぐ
    function safeCallAssignReplace(fn, urlLike) {
      try {
        const c = canonicalize(urlLike);
        if (c === location.href) return; // no-op
        return fn.call(this, c);
      } catch {
        return fn.call(this, urlLike);
      }
    }

    try {
      const L = Location.prototype;
      // assignフック
      try {
        const descA = Object.getOwnPropertyDescriptor(L, 'assign');
        if (!descA || descA.writable) {
          const _assign = L.assign;
          L.assign = function (url) {
            return safeCallAssignReplace.call(this, _assign, url);
          };
        }
      } catch {}

      // replaceフック
      try {
        const descR = Object.getOwnPropertyDescriptor(L, 'replace');
        if (!descR || descR.writable) {
          const _replace = L.replace;
          L.replace = function (url) {
            return safeCallAssignReplace.call(this, _replace, url);
          };
        }
      } catch {}

    } catch {
      // prototype操作がブロックされた場合のインスタンス直接書き換え
      try {
        const loc = window.location;
        const _assign2 = loc.assign.bind(loc);
        const _replace2 = loc.replace.bind(loc);
        Object.defineProperty(loc, 'assign', {
          value: (url) => safeCallAssignReplace.call(loc, _assign2, url)
        });
        Object.defineProperty(loc, 'replace', {
          value: (url) => safeCallAssignReplace.call(loc, _replace2, url)
        });
      } catch {}
    }
  })();

  // 3.5) Click Event Hook
  (function preNormalizeAnchorClicks() {
    document.addEventListener('click', (e) => {
      if (e.defaultPrevented) return;
      if (e.button !== 0) return; // 左クリックのみ
      if (e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) return;

      const a = e.target && e.target.closest && e.target.closest('a[href]');
      if (!a) return;

      // 除外: ダウンロード、別タブ、外部リンク属性
      if (a.hasAttribute('download')) return;
      if (a.target === '_blank') return;
      if (/\bexternal\b/i.test(a.rel || '')) return;

      try {
        const u = new URL(a.href, location.href);
        // http/https 以外は無視
        if (!/^https?:$/i.test(u.protocol)) return;
        // Amazon以外は無視
        if (!/\.amazon\.co\.jp$/i.test(u.hostname)) return;

        const c = canonicalize(u);
        // クリーンなURLに書き換えてから遷移させる
        if (c !== a.href) a.href = c;
      } catch {}
    }, { capture: true });
  })();

  // 4) Watchdog
  // Amazonの遅延ロードによるURL再汚染に対抗するため、変更が止まるまで監視する。
  (function watchdog() {
    let ticks = 0;
    let stable = 0; // 連続して変更がなかった回数

    const id = setInterval(() => {
      const changed = normalizeHere();
      // 変更があったらstableカウントをリセット、なければ加算
      stable = changed ? 0 : (stable + 1);

      // 終了条件:
      // 1. 8回連続(約2秒)変更がない = 安定したとみなす
      // 2. または合計40回(約10秒)経過しても終わらない = 強制終了
      if (stable >= 8 || (++ticks > 40 && !changed)) {
        clearInterval(id);
      }
    }, 250);

    // 補助トリガー
    document.addEventListener('DOMContentLoaded', normalizeHere, { once: true });
    window.addEventListener('load', normalizeHere, { once: true });
    document.addEventListener('visibilitychange', () => {
      if (document.visibilityState === 'visible') normalizeHere();
    });
  })();

})();