Files
MalinandClaude Sonnet 5 e267365def feat: strip Yahoo/Verizon Media GUCE consent-redirect params
guccounter/guce_referrer/guce_referrer_sig -- confirmed live on a
yahoo.com news article reached via Google News. Not caught by the
existing tracker list; these are Yahoo's own GUCE consent-flow
redirect params, distinct from Yandex's yclid already covered.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-18 07:01:32 +02:00

138 lines
5.0 KiB
JavaScript

// ==UserScript==
// @name Clean URL Tracking Params
// @namespace https://devops.cloudhost.es/Malin/userscripts
// @version 1.1.0
// @description Strips known ad/tracking query parameters (fbclid, gclid, utm_*, msclkid, ttclid, etc.) from the URL bar on load and on SPA navigation, without reloading the page. No network requests of any kind.
// @author Malin
// @match *://*/*
// @grant none
// @run-at document-start
// ==/UserScript==
/*
* Clean URL Tracking Params
* --------------------------
* Removes known tracking/analytics query parameters from the current
* page's URL via history.replaceState -- the page never reloads, nothing
* is sent anywhere, this only rewrites what's already in your own
* address bar. Runs once at document-start (before most page scripts
* get a chance to read the dirty URL) and again on every SPA navigation
* (history.pushState/replaceState calls, popstate, hashchange), since
* single-page apps often re-add tracking params on client-side route
* changes without a real page load.
*
* This is a cosmetic/privacy URL-bar cleanup, not a network-level
* tracker blocker -- a tracking pixel that already fired before this
* script ran (e.g. from a query param read by inline page JS) isn't
* un-fired by cleaning the URL afterward. Pair with an actual
* request-blocking extension (uBlock Origin etc.) if that's the goal;
* this script only keeps the URL itself clean to look at, copy, and share.
*/
(function () {
'use strict';
// Case-sensitive tracking param names to strip. Deliberately excludes
// generic-sounding params like "ref"/"source"/"id" that legitimate
// sites often depend on for real navigation -- only well-known
// ad-network/analytics-specific param names are listed here, to avoid
// ever breaking a page's actual functionality.
const TRACKING_PARAMS = new Set([
// Google Ads / Analytics
'gclid', 'gclsrc', 'dclid', 'wbraid', 'gbraid', '_gl',
'utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',
'utm_id', 'utm_source_platform', 'utm_creative_format', 'utm_marketing_tactic',
// Meta / Facebook / Instagram
'fbclid', 'fb_action_ids', 'fb_action_types', 'fb_ref', 'fb_source',
'igshid', 'igsh',
// Microsoft Ads
'msclkid',
// Twitter / X
'twclid',
// TikTok
'ttclid',
// Yahoo / Yandex
'yclid', '_openstat',
// Pinterest
'epik',
// Snapchat
'sc_cid',
// Mailchimp
'mc_eid', 'mc_cid',
// HubSpot
'_hsenc', '_hsmi', 'hsCtaTracking',
// Marketo
'mkt_tok',
// Vero
'vero_id', 'vero_conv',
// Piwik / Matomo
'pk_campaign', 'pk_kwd', 'pk_source', 'pk_medium', 'pk_content',
// Omeda
'oly_enc_id', 'oly_anon_id',
// Generic share-tracking token used by YouTube, Spotify, and others
'si',
// Yahoo / Verizon Media (GUCE consent-redirect flow -- confirmed live
// on yahoo.com news articles reached via Google News, 2026-08-18)
'guccounter', 'guce_referrer', 'guce_referrer_sig',
]);
function cleanUrl(rawUrl) {
let url;
try {
url = new URL(rawUrl, location.href);
} catch (e) {
return null; // not a parseable URL -- leave it alone
}
if (!url.search) return null;
let changed = false;
for (const key of [...url.searchParams.keys()]) {
if (TRACKING_PARAMS.has(key)) {
url.searchParams.delete(key);
changed = true;
}
}
if (!changed) return null;
// Drop a bare trailing "?" left over once every param is gone.
let clean = url.pathname + (url.search ? url.search : '') + url.hash;
if (url.origin !== location.origin) {
clean = url.origin + clean;
}
return clean;
}
function cleanCurrentUrl() {
const clean = cleanUrl(location.href);
if (clean !== null) {
history.replaceState(history.state, '', clean);
}
}
// Initial clean -- as early as document-start allows. Some params
// live in location.href before any DOM exists yet, so this is safe
// to run immediately.
cleanCurrentUrl();
// Re-run once the document is actually interactive too, in case a
// very-early redirect changed the URL again before we got here.
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', cleanCurrentUrl, { once: true });
}
// SPA navigation: history.pushState/replaceState are the standard way
// client-side routers change the URL without a real page load. Wrap
// both so a newly-set URL gets cleaned right after the app sets it,
// not fought over (we clean AFTER the app's own call completes, so we
// always see the final URL it intended).
for (const method of ['pushState', 'replaceState']) {
const original = history[method];
history[method] = function (...args) {
const result = original.apply(this, args);
cleanCurrentUrl();
return result;
};
}
window.addEventListener('popstate', cleanCurrentUrl);
window.addEventListener('hashchange', cleanCurrentUrl);
})();