// ==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 https://greasyfork.org/users/1291639 // @downloadURL https://update.greasyfork.icu/scripts/553472/CleanURLs%20and%20SkipRedirects.user.js // @updateURL https://update.greasyfork.icu/scripts/553472/CleanURLs%20and%20SkipRedirects.meta.js // ==/UserScript== // // ┌────────────────────────────────────────────┐ // │ SOMMAIRE │ // ├────────────────────────────────────────────┤ // │ [1] Configuration │ // │ [2] Cas spéciaux │ // │ [3] Outils de décodage │ // │ [4] Nettoyage des URLs │ // │ [5] Barre d’adresse │ // │ [6] Balises │ // │ [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 // ================================= // ---------- 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(); } })();