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>
138 lines
5.0 KiB
JavaScript
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);
|
|
})();
|