Fix Double-Encoded Characters in URLs

Fixes a Microsoft Edge bug where URLs with Chinese characters (漢字/中文字) become double-encoded. Resolves Wikipedia errors like `請求的頁面標題包含無效的字元` that can occur after clicking links from Google search results.

// ==UserScript==
// @name         Fix Double-Encoded Characters in URLs
// @namespace    https://github.com/fixicelo/userscripts
// @version      1.0.0
// @description  Fixes a Microsoft Edge bug where URLs with Chinese characters (漢字/中文字) become double-encoded. Resolves Wikipedia errors like `請求的頁面標題包含無效的字元` that can occur after clicking links from Google search results.
// @author       fixicelo
// @license      MIT
// @match        *://*/*%25*
// @run-at       document-start
// @grant        none
// ==/UserScript==

(function () {
  "use strict";
  /**
   * Repeatedly decodes a URL up to maxIterations or until no more %25 are found.
   * Prevents infinite loop on malformed or cyclic encodings.
   * @param {string} url - The URL string to decode.
   * @param {number} [maxIterations=5] - Maximum allowed decoding iterations.
   * @returns {string} - The fully decoded or best-effort decoded URL.
   */
  function fullyDecode(url, maxIterations = 5) {
    let prev = url;
    let curr = url;
    let i = 0;
    while (curr.includes("%25") && i < maxIterations) {
      try {
        curr = decodeURIComponent(curr);
      } catch (e) {
        // Malformed encoding - stop decoding
        break;
      }
      if (curr === prev) break; // No further decoding
      prev = curr;
      i++;
    }
    return curr;
  }

  /**
   * Fixes the current page's URL if it's double-encoded.
   */
  function fixCurrentPageURL() {
    const currentURL = window.location.href;
    if (!currentURL.includes("%25")) return;

    const decodedURL = fullyDecode(currentURL);
    if (decodedURL && decodedURL !== currentURL) {
      // Try to preserve hash and search if not part of the double-encoding
      // Replace only pathname and search, not protocol, host, or hash
      const urlObj = new URL(currentURL);
      const fixedObj = new URL(decodedURL);

      // Only replace if the host and protocol match (avoid redirect to unrelated site)
      if (urlObj.origin === fixedObj.origin) {
        if (
          urlObj.pathname !== fixedObj.pathname ||
          urlObj.search !== fixedObj.search
        ) {
          urlObj.pathname = fixedObj.pathname;
          urlObj.search = fixedObj.search;
          // Preserve hash from original URL if present
          window.location.replace(urlObj.toString());
        }
      } else {
        // Fallback: If origins do not match, use the fully decoded URL
        window.location.replace(decodedURL);
      }
    }
  }

  // Run as soon as possible
  fixCurrentPageURL();
})();