// ==UserScript==
// @license MIT
// @name CleanURLs and SkipRedirects
// @version 7.0
// @description Supprime les paramètres de suivi des URLs et passe les redirections
// @author LeDimiScript
// @match *://*/*
// @grant none
// @run-at document-start
// @namespace https://greasyfork.org/users/1291639
// @downloadURL none
// ==/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();
}
})();