Greasy Fork

Greasy Fork is available in English.

CleanURLs and SkipRedirects

Supprime les paramètres de suivi des URLs et passe les redirections

// ==UserScript==
// @license         MIT
// @name            CleanURLs and SkipRedirects
// @name:en         CleanURLs and SkipRedirects
// @version         7.3
// @description     Supprime les paramètres de suivi des URLs et passe les redirections
// @description:en  Removes tracking parameters from URLs and skips redirects
// @author          LeDimiScript
// @match           *://*/*
// @grant           none
// @run-at          document-start
// @icon            data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iaXNvLTg4NTktMSI/Pg0KPCEtLSBVcGxvYWRlZCB0bzogU1ZHIFJlcG8sIHd3dy5zdmdyZXBvLmNvbSwgR2VuZXJhdG9yOiBTVkcgUmVwbyBNaXhlciBUb29scyAtLT4NCjxzdmcgaGVpZ2h0PSI4MDBweCIgd2lkdGg9IjgwMHB4IiB2ZXJzaW9uPSIxLjEiIGlkPSJMYXllcl8xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiANCgkgdmlld0JveD0iMCAwIDUxMS45OTUgNTExLjk5NSIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+DQo8Y2lyY2xlIHN0eWxlPSJmaWxsOiMwMDY3NzU7IiBjeD0iMjU1Ljk5NyIgY3k9IjI1NS45OTciIHI9IjI1NS45OTciLz4NCjxwYXRoIHN0eWxlPSJmaWxsOiMwNTU2NjE7IiBkPSJNNTExLjc4OSwyNzAuMTYzYy02Ljk3NiwxMzAuMjItMTExLjE5NCwyMzQuNDM3LTI0MS40MTQsMjQxLjYyNUwxNTcuMDY3LDM5OC40OGwxMDguMjM1LTEwOS43MTUNCglsLTIwLjcxNy0yMS4zNTFsMjIuODMxLTIyLjQwOGwyMC41MDYsMjAuOTI4TDM5Ni43OSwxNTUuNTg2bDExNC43ODgsMTE0Ljc4OEw1MTEuNzg5LDI3MC4xNjN6Ii8+DQo8cGF0aCBzdHlsZT0iZmlsbDojRkZENjMwOyIgZD0iTTIxNi4yNTgsMjI1Ljc3bC01MC45NDYsMi41MzdDMTY0Ljg4OCwyMjguMzA3LDE5NS41NDEsMTkwLjY3OCwyMTYuMjU4LDIyNS43N3oiLz4NCjxnPg0KCTxwYXRoIHN0eWxlPSJmaWxsOiNGNUJFMTg7IiBkPSJNMjI5Ljc4NywyMTQuMzU1bC0zMC4yMy04Mi4yMzNDMjEwLjEyNywxNDIuMjcsMjc3LjEzOSwxNzEuODY1LDIyOS43ODcsMjE0LjM1NXoiLz4NCgk8cGF0aCBzdHlsZT0iZmlsbDojRjVCRTE4OyIgZD0iTTIxNi4yNTgsMjI1Ljc3di0wLjIxMWwtMC44NDYtMS4yNjlsLTQ5LjI1NSwyLjk2bC0wLjg0NiwxLjA1Nw0KCQlDMTY0LjQ2NiwyMjguMzA3LDE5Ny44NjYsMjYwLjg2MiwyMTYuMjU4LDIyNS43N3oiLz4NCjwvZz4NCjxwYXRoIHN0eWxlPSJmaWxsOiNGRkQ2MzA7IiBkPSJNMjI5Ljc4NywyMTQuMzU1bDEuNDgtMS4yNjlsLTI4Ljc1LTc4LjAwNWwtMi45Ni0yLjk2DQoJQzE5OC45MjMsMTMzLjgxMywxNjkuNzUsMjA3LjM3OSwyMjkuNzg3LDIxNC4zNTV6Ii8+DQo8Zz4NCgk8cGF0aCBzdHlsZT0iZmlsbDojMDBDQzk2OyIgZD0iTTExNS40MjIsMzI2LjYwNmw4MC4xMTktNzguNjM5YzguNDU2LTguMjQ0LDIxLjk4Ni04LjAzMywzMC4yMywwLjIxMWwyLjc0OCwyLjk2bDMxLjkyMSwzMi41NTUNCgkJbDIuNzQ4LDIuOTZjOC4yNDQsOC40NTYsOC4wMzMsMjEuOTg2LTAuMjExLDMwLjIzbC04MC4xMTksNzguNjM5Yy04LjQ1Niw4LjI0NC0yMS45ODYsOC4wMzMtMzAuMjMtMC4yMTFsLTM3LjQxNy0zOC4yNjMNCgkJYy04LjI0NC04LjQ1Ni04LjAzMy0yMS45ODYsMC4yMTEtMzAuMjN2LTAuMjEySDExNS40MjJ6IE0yMTQuNTY2LDI2NC44NzlsLTIuNzQ4LTIuOTZjLTAuNjM0LTAuNjM0LTEuNjkxLTAuNjM0LTIuMzI1LDANCgkJbC04MC4xMTksNzguNjM5Yy0wLjYzNCwwLjYzNC0wLjYzNCwxLjY5MSwwLDIuMzI1bDM3LjQxNywzOC4yNjNjMC42MzQsMC42MzQsMS42OTEsMC42MzQsMi4zMjUsMGw4MC4xMTktNzguNjM5DQoJCWMwLjYzNC0wLjYzNCwwLjYzNC0xLjY5MSwwLTIuMzI1bC0yLjc0OC0yLjk2bC0zMS45MjEtMzIuNTU1TDIxNC41NjYsMjY0Ljg3OUwyMTQuNTY2LDI2NC44Nzl6Ii8+DQoJPHBhdGggc3R5bGU9ImZpbGw6IzAwQ0M5NjsiIGQ9Ik0yNDkuMDI0LDE5NS41NDFsODAuMTE5LTc4LjYzOWM4LjQ1Ni04LjI0NCwyMS45ODYtOC4wMzMsMzAuMjMsMC4yMTFsMzcuNDE3LDM4LjI2Mw0KCQljOC4yNDQsOC40NTYsOC4wMzMsMjEuOTg2LTAuMjExLDMwLjIzbC04MC4xMTksNzguNjM5Yy04LjQ1Niw4LjI0NC0yMS45ODYsOC4wMzMtMzAuMjMtMC4yMTFsLTIuNzQ4LTIuOTZsLTMxLjcxLTMyLjU1NQ0KCQlsLTAuMjExLDAuMjExbC0yLjc0OC0yLjk2QzI0MC41NjgsMjE3LjMxNSwyNDAuNzc5LDIwMy43ODUsMjQ5LjAyNCwxOTUuNTQxeiBNMjk3LjQzMywyNDcuMzMzbDIuNzQ4LDIuOTYNCgkJYzAuNjM0LDAuNjM0LDEuNjkxLDAuNjM0LDIuMzI1LDBsODAuMTE5LTc4LjYzOWMwLjYzNC0wLjYzNCwwLjYzNC0xLjY5MSwwLTIuMzI1bC0zNy40MTctMzguMjYzYy0wLjYzNC0wLjYzNC0xLjY5MS0wLjYzNC0yLjMyNSwwDQoJCWwtODAuMTE5LDc4LjYzOWMtMC42MzQsMC42MzQtMC42MzQsMS42OTEsMCwyLjMyNWwyLjc0OCwyLjk2bDMxLjkyMSwzMi41NTV2LTAuMjEySDI5Ny40MzN6Ii8+DQo8L2c+DQo8cGF0aCBzdHlsZT0iZmlsbDojRkZENjMwOyIgZD0iTTIwOC42NDcsMjg1LjE3Mmw3NC40MTEtNzIuOTMyYzQuODYyLTQuNjUxLDEyLjY4NC00LjY1MSwxNy4zMzUsMC4yMTFsMCwwDQoJYzQuNjUxLDQuODYyLDQuNjUxLDEyLjY4NC0wLjIxMSwxNy4zMzVsLTc0LjQxMSw3Mi45MzJjLTQuODYyLDQuNjUxLTEyLjY4NCw0LjY1MS0xNy4zMzUtMC4yMTFsMCwwDQoJQzIwMy43ODUsMjk3LjY0NSwyMDMuNzg1LDI4OS44MjMsMjA4LjY0NywyODUuMTcyeiIvPg0KPGc+DQoJPHBhdGggc3R5bGU9ImZpbGw6IzA3QjU4NzsiIGQ9Ik0yNTMuMjUyLDI3Ni4wODNsNy4xODcsNy4zOTlsMi43NDgsMi45NmM4LjI0NCw4LjQ1Niw4LjAzMywyMS45ODYtMC4yMTEsMzAuMjNsLTgwLjExOSw3OC42MzkNCgkJYy04LjQ1Niw4LjI0NC0yMS45ODYsOC4wMzMtMzAuMjMtMC4yMTFsLTE4LjgxNS0xOS4wMjZsMTQuMTY0LTEzLjk1M2wxOC44MTUsMTkuMDI2YzAuNjM0LDAuNjM0LDEuNjkxLDAuNjM0LDIuMzI1LDANCgkJbDgwLjExOS03OC42MzljMC42MzQtMC42MzQsMC42MzQtMS42OTEsMC0yLjMyNWwtMi43NDgtMi45NmwtNy4xODctNy4zOTlsMTQuMTY0LTEzLjk1M0wyNTMuMjUyLDI3Ni4wODN6Ii8+DQoJPHBhdGggc3R5bGU9ImZpbGw6IzA3QjU4NzsiIGQ9Ik0zNzguMTg3LDEzNi4xMzlsMTguODE1LDE5LjAyNmM4LjI0NCw4LjQ1Niw4LjAzMywyMS45ODYtMC4yMTEsMzAuMjNsLTgwLjExOSw3OC42MzkNCgkJYy04LjQ1Niw4LjI0NC0yMS45ODYsOC4wMzMtMzAuMjMtMC4yMTFsLTIuNzQ4LTIuOTZsLTcuMTg3LTcuMzk5bDE0LjE2NC0xMy43NGw3LjE4Nyw3LjM5OWwyLjc0OCwyLjk2DQoJCWMwLjYzNCwwLjYzNCwxLjY5MSwwLjYzNCwyLjMyNSwwbDgwLjExOS03OC42MzljMC42MzQtMC42MzQsMC42MzQtMS42OTEsMC0yLjMyNWwtMTguODE1LTE5LjAyNmwxNC4xNjQtMTMuOTUzSDM3OC4xODd6Ii8+DQo8L2c+DQo8cGF0aCBzdHlsZT0iZmlsbDojRjVCRTE4OyIgZD0iTTMwMC42MDQsMjEyLjQ1M0wzMDAuNjA0LDIxMi40NTNjNC42NTEsNC44NjIsNC42NTEsMTIuNjg0LTAuMjExLDE3LjMzNWwtNzQuNDExLDcyLjkzMg0KCWMtNC44NjIsNC42NTEtMTIuNjg0LDQuNjUxLTE3LjMzNS0wLjIxMWwwLDBsOTEuOTU3LTkwLjI2NlYyMTIuNDUzeiIvPg0KPC9zdmc+
// @namespace       http://greasyfork.icu/users/1291639
// ==/UserScript==
//
// ┌────────────────────────────────────────────┐
// │                  SOMMAIRE                  │
// ├────────────────────────────────────────────┤
// │ [1] Configuration                          │
// │ [2] Cas spéciaux                           │
// │ [3] Outils de décodage                     │
// │ [4] Nettoyage des URLs                     │
// │ [5] Barre d’adresse                        │
// │ [6] Balises <a>                            │
// │ [7] Observateurs DOM & URL                 │
// │ [8] Initialisation                         │
// └────────────────────────────────────────────┘

(function() {
    'use strict';

    // Quitter si le document n'est pas HTML
    if (document.doctype == null) return;



    // ===============[1]===============
    //           CONFIGURATION
    // =================================


    // ---------- Liste des protocoles pris en charge ----------

    const validProtocols = [
      'finger:',
      'ftp://',
      'ftps://',
      'freenet:',
      'gemini:',
      'gopher:',
      'http://',
      'https://',
      'ipfs:',
      'mailto:',
      'magnet:',
      'wap:',
      'xmpp:'
    ];


    // ---------- Liste des paramètres d'URL à nettoyer ----------

    const blacklist = [
      'a',
      'abcId',
      'accountId',
      'ad',
      'adgroupid',
      'adgrp',
      'adgrpid',
      'ad-location',
      'ad_medium',
      'ad_name',
      'ad_pvid',
      'ad_sub',
      'ad_tags',
      'adt_ei',
      'ad_type',
      'advertising-id',
      'aem_p4p_detail',
      'af',
      'aff',
      'aff_fcid',
      'aff_fsk',
      'affiliate',
      'aff_platform',
      'aff_trace_key',
      'affparams',
      'afSmartRedirect',
      'afftrack',
      'aid',
      'algo_exp_id',
      'algo_pvid',
      'ap_id',
      'appver',
      'ar',
      'ascsubtag',
      'asc_contentid',
      'asgtbndr',
      'atc',
      'at_campaign',
      'at_campaign_group',
      'at_creation',
      'at_medium',
      'at_network',
      'at_platform',
      'ats',
      'at_term',
      'at_variant',
      'autostart',
      'bb',
      'bbn',
      'bdmtchtyp ',
      'bdmtchtyp+',
      'bizType',
      'block',
      'bp',
      '_bsa_req',
      'bta',
      'businessType',
      'caifrq',
      'campaign',
      'campaignId',
      'campaignid',
      'campaign_id',
      'campid',
      'cd',
      '__cf_chl_rt_tk',
      '__cf_chl_tk',
      '__cf_chl_f_tk',
      '__cf_chl_jschl_tk__',
      '__cf_chl_captcha_tk__',
      '__cf_chl_managed_tk__',
      '__cf_chl_rt_tk__',
      'cha',
      'chb',
      'chbr',
      'chf',
      'chm',
      'chmd',
      'chn',
      'chnl',
      'chp',
      'chv',
      'cid',
      'ck',
      'clickid',
      'client_id',
      'cm_mmc',
      'cm_ven',
      'cmd',
      'cmpgn',
      'cnvs',
      'cod',
      'content-id',
      'country',
      'creative',
      'crid',
      'cs',
      'cst',
      'cti',
      'cts',
      'curPageLogUid',
      'customid',
      'dc',
      'dchild',
      'dclid',
      'deals-widget',
      'device',
      'dgcid',
      'dib',
      'dib_tag',
      'dicbo',
      'dim1',
      'dim2',
      'discounts-widget',
      'docid',
      'ds',
      'dt',
      'dTag',
      'dv',
      'e9s',
      'eclog',
      'edd',
      'edm_click_module',
      'ei',
      'embed',
      'email',
      '_encoding',
      'eventSource',
      'exp_price',
      'fbclid',
      'fdl',
      'feature',
      'febuild',
      'field',
      'field-lbr_brands_browse-bin',
      'fn',
      'forced_click',
      'fr',
      'freq',
      'from',
      'frs',
      '_ga',
      'ga_order',
      'ga_search_query',
      'ga_search_type',
      'ga_view_type',
      'gadid',
      'gatewayAdapt',
      'gbv',
      'gclid',
      'gclsrc',
      'gg_dev',
      'gh_jid',
      'goods_id',
      'googleloc',
      'gps-id',
      'gsAttrs',
      'gs_lcp',
      'gs_lp',
      'gt',
      'guccounter',
      'hdtime',
      'helpid',
      'hosted_button_id',
      'hvadid',
      'hvbmt',
      'hvdev',
      'hvlocphy',
      'hvnetw',
      'hvqmt',
      'hvtargid',
      'hydadcr',
      'i',
      'ICID',
      'ico',
      'idOffre',
      'idzone',
      'ie',
      'iflsig',
      'ig_rid',
      'im',
      'index',
      'intake',
      'intcmp',
      'irclickid',
      'irgwc',
      'irpid',
      'is_from_webapp',
      'itemId',
      'itemid',
      'itid',
      'itok',
      'ix',
      'kard',
      'katds_labels',
      'kb',
      'keyno',
      'keywords',
      'KwdID',
      'l10n',
      'landed',
      'ld',
      'linkCode',
      'loc_physical_ms',
      'ls',
      'mark',
      'mc',
      'mc_cid',
      'mcid',
      'md',
      'md5',
      'media',
      'merchantid',
      'mid',
      'mkcid',
      '__mk_de_DE',
      'mkevt',
      'mkgroupid',
      'mkrid',
      'mkscid',
      'mortyurl',
      'mp',
      'msclkid',
      'mtchtyp',
      'nats',
      'nci',
      'nojs',
      'norover',
      'nsdOptOutParam',
      'ntwrk',
      'obOrigUrl',
      'offerid',
      'offer_id',
      'opened-from',
      'optout',
      'oq',
      'organic_search_click',
      'origin',
      'os',
      'p',
      'p1',
      'p2',
      'p3',
      'pa',
      'Partner',
      'partner',
      'partner_id',
      'partner_ID',
      'pb',
      'pcampaignid',
      'pd_rd_i',
      'pd_rd_r',
      'pd_rd_w',
      'pd_rd_wg',
      'pdp_npi',
      'pf',
      'pf_rd_i',
      'pf_rd_m',
      'pf_rd_p',
      'pf_rd_r',
      'pf_rd_s',
      'pf_rd_t',
      'pg',
      'pgId',
      'PHPSESSID',
      'pk_campaign',
      'pdp_ext_f',
      'pkey',
      'Platform',
      'platform',
      'plkey',
      'pload',
      'plu',
      'pp',
      'pqr',
      'pr',
      'pro',
      'prod',
      'product_id',
      'product_partition_id',
      'prom',
      'promo',
      'promocode',
      'promoid',
      'provider',
      'psc',
      'psp',
      'psprogram',
      'psr',
      'pvid',
      'qid',
      'Query',
      'r',
      'rb_css',
      'rb_geo',
      'rb_itemId',
      'rb_pgeo',
      'rb_plang',
      'realDomain',
      'recruiter_id',
      'ref',
      'ref_',
      'ref_src',
      'refcode',
      'referral',
      'referrer',
      'refinements',
      'reftag',
      'related_post_from',
      'retailer',
      'rf',
      'rlp',
      'rlid',
      'rlsatarget',
      'rnid',
      'rowan_id1',
      'rowan_msg_id',
      'rs',
      'ru',
      'rss',
      'sbo',
      'sCh',
      'sca_esv',
      'scene',
      'sclient',
      'scm',
      'scm_id',
      'scm-url',
      'sd',
      'searchText',
      'sei',
      'sender_device',
      'serviceIdserviceId',
      'sh',
      'shareId',
      'shownav',
      'showVariations',
      'si',
      'sid',
      '___SID',
      'site',
      'site_id',
      'sk',
      'smid',
      'social_params',
      'source',
      'sourceId',
      'sp',
      'sp_csd',
      'spLa',
      'spm',
      'spreadType',
      'sprefix',
      'sr',
      'src',
      '_src',
      'src_cmp',
      'src_player',
      'src_src',
      'srcSns',
      'start_radio',
      'su',
      'supplier',
      'sxin_0_pb',
      'syslcid',
      '_t',
      'tag',
      'targetid',
      'tcampaign',
      'td',
      'terminal_id',
      'test',
      'text',
      'tgt',
      'th',
      'tl',
      'token',
      'toolid',
      'tracelog',
      'trafficChannelId',
      'traffic_id',
      'traffic_source',
      'traffic_type',
      'tt',
      'tuid',
      'tz',
      'uact',
      'ug_edm_item_id',
      'ui',
      'uilcid',
      '_ul',
      'url_from',
      'userId',
      'utm',
      'utm1',
      'utm2',
      'utm3',
      'utm4',
      'utm5',
      'utm6',
      'utm7',
      'utm8',
      'utm9',
      'utm_campaign',
      'utm_content',
      'utm_feed',
      'utm_hash',
      'utm_id',
      'utm_medium',
      'utm_productid',
      'utm_source',
      'utm_term',
      'uuid',
      'utype',
      'var',
      'variant_id',
      'vcn',
      'vcv',
      've',
      'ved',
      'wcks',
      'wgl',
      'wprov',
      'x',
      '_xiid',
      'xpid',
      'y',
      'zone',
      'zoneid'
    ];


    // ---------- Liste des paramètres de hash à nettoyer ----------

    const hash = [
      '!psicash',
      'back-url',
      'back_url',
      'dealsGridLinkAnchor',
      'ebo',
      'int',
      'intcid',
      'mpos',
      'niche-',
      'searchinput',
      'src',
      'xtor'
    ];


    // ---------- Liste des paramètres de redirection ----------

    const redirectParams = [
      'ds_dest_url',
      'kaRdt',
      'lp',
      'redirect',
      'redirect_uri',
      'target',
      'tURL',
      'u',
      'url'
    ];



    // ===============[2]===============
    //           CAS SPÉCIAUX
    // =================================

    /**
     * Réécrit les URL pour certains sites spécifiques :
     * - Amazon
     * - Wikimedia
     */
    function rewriteSpecialCases(url) {


        // ---------- Cas Amazon ----------

        // Domaines Amazon
        const amazonDomains = [
          'amazon.com',
          'amazon.be',
          'amazon.ae',
          'amazon.ca',
          'amazon.co.uk',
          'amazon.com.au',
          'amazon.com.br',
          'amazon.com.mx',
          'amazon.com.tr',
          'amazon.de',
          'amazon.es',
          'amazon.fr',
          'amazon.in',
          'amazon.it',
          'amazon.nl',
          'amazon.pl',
          'amazon.sa',
          'amazon.se',
          'amazon.sg'
        ];
        const isAmazon = amazonDomains.some(domain => url.hostname.endsWith(domain));
        if (isAmazon && url.pathname.match(/^\/s/) && url.searchParams.has('keywords')) {
            // Unifie le chemin
            url.pathname = '/s';
            // Remplace 'keywords' par 'k'
            const keywords = url.searchParams.get('keywords');
            url.searchParams.delete('keywords');
            url.searchParams.set('k', keywords);
        }


        // ---------- Cas Wikimedia ----------

        // Domaines Wikimedia
        const wikimediaDomains = [
          'mediawiki.org',
          'wikibooks.org',
          'wikidata.org',
          'wikimedia.org',
          'wikinews.org',
          'wikipedia.org',
          'wikiquote.org',
          'wikisource.org',
          'wikiversity.org',
          'wikivoyage.org',
          'wiktionary.org'
        ];
        const isWikimedia = wikimediaDomains.some(domain => url.hostname.endsWith(domain));
        if (isWikimedia && url.pathname.match(/^\/w\/index.php/) && url.searchParams.has('title')) {
            // Récupère la valeur du paramètre 'title'
            const title = url.searchParams.get('title');
            // Supprime le paramètre 'title' de l'URL
            url.searchParams.delete('title');
            // Modifie le chemin en fonction de la valeur de 'title'
            url.pathname = `/wiki/${encodeURIComponent(title)}`;
        }
    }



    // ===============[3]===============
    //        OUTILS DE DÉCODAGE
    // =================================


    // ---------- Décodage en URL ----------

    function tryUrlDecode(value) {
        if (!value) return null;
        try {
            let decoded = decodeURIComponent(value);
            return decoded;
        } catch (e) {
            return null; // Cas de chaîne mal formée
        }
    }


    // ---------- Décodage en base64 ----------

    function tryBase64Decode(value) {
        if (!value) return null;
        try {
            // Corrige le format base64url → base64 standard
            let base64 = value.replace(/-/g, '+').replace(/_/g, '/');
            while (base64.length % 4 !== 0) base64 += '=';
            const decoded = atob(base64);
            return decoded;
        } catch (e) {
            return null;
        }
    }

    /**
     * Tente de décoder une URL de redirection encodée :
     * - en URL
     * - en base64
     * Retourne l'url décodée ou null
     */
    function tryDecodeRedirectUrl(value) {
        if (!value) return null;

        // 1. Essayer un décodage URL
        const urlDecoded = tryUrlDecode(value);
        for (const protocol of validProtocols) {
            if (urlDecoded && urlDecoded.startsWith(protocol)) {
                return urlDecoded;
            }
        }

        // 2. Essayer un décodage base64, puis décodage URL
        const base64Decoded = tryBase64Decode(value);
        if (base64Decoded) {
            const finalDecoded = tryUrlDecode(base64Decoded);
            for (const protocol of validProtocols) {
                if (finalDecoded && finalDecoded.startsWith(protocol)) {
                    return finalDecoded;
                }
            }
        }

        // 3. Aucun résultat valide : ce n'était pas une URL de redirection
        return null;
    }



    // ===============[4]===============
    //        NETTOYAGE DES URLS
    // =================================


    /**
     * Suit les redirections et nettoie l'URL en plusieurs étapes :
     * - traitement du chemin
     * - nettoyage des paramètres
     * - nettoyage du hash
     * S'assure qu'il n'y a pas de paramètres encodés en URL.
     */
    function cleanUrl(url) {
        if (typeof url === 'string') url = new URL(url);


       // ---------- Passage des redirections ----------

        const urlString = url.href;
        const originalOrigin = url.origin;
        let lastValidRedirect = url;

        // 1. Traitement des urls mal encodées
        for (const param of redirectParams) {
          let startIndex = 0;
          const pattern = `${param}=`;
          while (true) {
            const index = urlString.indexOf(pattern, startIndex);
            if (index === -1) break;
            const rawValue = urlString.slice(index + pattern.length);
            for (const protocol of validProtocols) {
              if (rawValue.startsWith(protocol)) {
                try {
                  const nextUrl = new URL(rawValue);
                  if (nextUrl.origin !== originalOrigin) {
                    return cleanUrl(nextUrl);
                  } else {
                    lastValidRedirect = cleanUrl(nextUrl);
                  }
                } catch {
                  return null;
                }
                break;
              }
            }
            startIndex = index + pattern.length;
          }
        }

        // 2. Traitement classique
        for (const param of redirectParams) {
          const values = url.searchParams.getAll(param);
          for (const rawValue of values) {
            const decodedRedirect = tryDecodeRedirectUrl(rawValue);
            if (decodedRedirect) {
              const decodedUrl = new URL(decodedRedirect);
              if (decodedUrl.origin !== originalOrigin) {
                return cleanUrl(decodedUrl);
              } else {
                lastValidRedirect = cleanUrl(decodedUrl);
              }
            } else {
              lastValidRedirect.searchParams.delete(param);
            }
          }
        }

        // 3. Retourne l'url de destination
        url = lastValidRedirect;


        // ---------- Nettoyage de l'URL ----------

        // Appliquer les réécriture spécifiques
        rewriteSpecialCases(url);
        let href = url.href;

        // 1. Supprimer les paramètres dans le chemin
        const path = url.pathname;
        const pathParts = path.split('/').filter(part => {
            if (!part.includes('=')) return true;
            const [key, ...rest] = part.split('=');
            const value = rest.join('=');
            // Supprime si la clé est blacklistée ou la valeur est vide
            return !!value && !blacklist.includes(key);
        });
        // Reconstruire le chemin avec ce qu'il reste
        url.pathname = pathParts.join('/');

        // 2. Supprime les paramètres vides et tous les paramètres blacklistés
        if (url.search.includes('=')) {
            const newParams = new URLSearchParams();
            for (const [key, value] of url.searchParams.entries()) {
                  if (!!value && !blacklist.includes(key)) {
                      newParams.append(key, value);
                  }
            }
            url.search = newParams.toString();
        }

        // 3. Supprime les hash vides et tous les paramètres blacklistés dans hash
        if (url.hash) {
          // Enlève le '#'
          let hashContent = url.hash.substring(1);
          if (hashContent.includes('=')) {
              // Parser comme une liste de paramètres
              const hashParams = new URLSearchParams(hashContent);
              // Nettoyage
              const newHashParams = new URLSearchParams();
              for (const [key, value] of hashParams.entries()) {
                  if (!!value && !hash.includes(key)) {
                      newHashParams.append(key, value);
                  }
              }
              // Reconstruire le hash avec ce qu'il reste
              url.hash = newHashParams.toString() ? '#' + newHashParams.toString() : '';
            }
        }


        // ---------- Décodage des paramètres restants ----------

        let finalURL = url.href;
        const newParams = new URLSearchParams();

        // 1. Parcourir tous les paramètres de l'URL
        for (const [key, value] of url.searchParams.entries()) {
            // Décoder la valeur du paramètre
            let decodedValue = tryUrlDecode(value);
            // On cherche si la valeur est une suite de paramètres encodés
            if (decodedValue && decodedValue.includes('=')) {
                const innerParams = new URLSearchParams(decodedValue);

                // Si la valeur décodée ressemble à une série de paramètres, les ajouter comme nouveaux paramètres en remplaçant la clef par sa valeur décodée
                for (const [innerKey, innerValue] of innerParams.entries()) {
                    newParams.append(innerKey, innerValue);  // Ajouter chaque paramètre comme s'il était indépendant
                }
                // On ne garde pas le param original, car il est remplacé par ses composants décodés
            } else {
                // Si ce n'est pas une suite de paramètres, ajouter le paramètre normalement
                newParams.append(key, decodedValue);
            }
        }

        // 2. Remplacer les paramètres dans l'URL nettoyée avec les nouveaux paramètres décodés
        url.search = newParams.toString();

        // 3. Si l'URL a changé suite à ce décodage des paramètres, rappeler cleanUrl
        if (url.href !== finalURL) {
            url = cleanUrl(url);
        }

        return url;  // Retourner l'URL nettoyée avec les paramètres réécrits
    }



    // ===============[5]===============
    //         BARRE D'ADRESSE
    // =================================

    // Modifie l'URL actuelle dans la barre d'adresse pour supprimer les paramètres de suivi
    function modifyURL() {
        const url = new URL(window.location.href);
        const cleanedUrl = cleanUrl(url);
        const newURL = cleanedUrl.href;
        if (window.location.href !== newURL) {
            window.history.replaceState(null, '', newURL);
        }
    }



    // ===============[6]===============
    //           BALISES <a>
    // =================================


    // ---------- Redéfinir la propriété `href` de HTMLAnchorElement ----------

    (function redefineHrefSetter() {
        const descriptor = Object.getOwnPropertyDescriptor(HTMLAnchorElement.prototype, 'href');
        Object.defineProperty(HTMLAnchorElement.prototype, 'href', {
            get: descriptor.get,
            set(value) {
                try {
                    const cleaned = validateAndCleanUrl(value);
                    descriptor.set.call(this, cleaned ?? value);
                } catch (e) {
                    descriptor.set.call(this, value);
                }
            }
        });
    })();


    // ---------- Nettoyer une URL et éventuellement suivre la redirection ----------

    /**
     * Valide et nettoie une URL.
     * Retourne une URL absolue nettoyée ou null si invalide.
     */
    function validateAndCleanUrl(href) {
        if (!href) return null; // Ignore les valeurs vides
        try {
            // Transforme les href en URL absolue
            const url = new URL(href, window.location.href);
            // Nettoie l'URL
            let cleaned = cleanUrl(url);
            // Retourne l'URL nettoyée (toujours absolue)
            return cleaned.href;
        } catch (e) {
            return null;
        }
    }


    // ---------- Intercepter les modifications de href via setAttribute() ----------

    (function redefineSetAttribute() {
        const original = Element.prototype.setAttribute;
        Element.prototype.setAttribute = function(name, value) {
            if (this.tagName === 'A' && name.toLowerCase() === 'href') {
                try {
                    const cleaned = validateAndCleanUrl(value);
                    if (cleaned) {
                        return original.call(this, name, cleaned);
                    }
                } catch (e) {}
            }
            return original.call(this, name, value);
        };
    })();


    // ---------- Traiter tous les liens dans un conteneur ----------

    /**
     * Parcourir tous les liens du conteneur,
     * nettoyer leur attribut href si nécessaire,
     * et remplacer par l’URL nettoyée.
     */
    function handleLinks(container = document) {
        const links = container.getElementsByTagName('a');
        for (const link of links) {
            try {
                // On récupère l'attribut brut, sans résolution automatique en URL absolue
                if (!link.hasAttribute('href')) continue;
                const originalHref = link.getAttribute('href');
                const cleanedHref = validateAndCleanUrl(originalHref);
                // Si une URL nettoyée valide existe et qu'elle est différente, on remplace
                if (cleanedHref && cleanedHref !== originalHref) {
                    link.setAttribute('href', cleanedHref);
                }
            } catch (e) {}
        }
    }



    // ===============[7]===============
    //      OBSERVATEURS DOM & URL
    // =================================


    // ---------- Configure un MutationObserver pour nettoyer dynamiquement les URLs ----------

    function setupMutationObserver() {
        const observer = new MutationObserver(function(mutations) {
            mutations.forEach(function(mutation) {
                for (const node of mutation.addedNodes) {
                    if (node.nodeType === Node.ELEMENT_NODE) {
                        handleLinks(node);
                    }
                }
            });
        });
        function tryObserve() {
            if (document.body) {
                observer.observe(document.body, { childList: true, subtree: true });
            } else {
                requestAnimationFrame(tryObserve);
            }
        }
        tryObserve();
    }


    // ---------- Observation des changement dynamique de l'URL ----------

    function observeURLChanges() {
        let lastUrl = window.location.href;
        const observer = new MutationObserver(() => {
            const currentUrl = window.location.href;
            if (currentUrl !== lastUrl) {
                modifyURL();
                lastUrl = currentUrl;
            }
        });
        observer.observe(document, { subtree: true, childList: true });
        // Observe les ajouts de hash
        window.addEventListener('hashchange', () => {
            const currentUrl = window.location.href;
            if (currentUrl !== lastUrl) {
                modifyURL();
                lastUrl = currentUrl;
            }
        });
    }



    // ===============[8]===============
    //          INITIALISATION
    // =================================


    // ---------- Nettoyage initial ----------

    modifyURL();


    // ---------- Nettoyage une fois la page chargée ----------

    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', function() {
            handleLinks();
            setupMutationObserver();
            observeURLChanges();
        });
    } else {
        handleLinks();
        setupMutationObserver();
        observeURLChanges();
    }
})();