feat: add Clean URL Tracking Params userscript
Strips fbclid/gclid/utm_*/msclkid/ttclid and other known ad-tracking query params from the URL bar via history.replaceState -- no reload, no network requests. Handles SPA navigation too (pushState/replaceState wrapping, popstate/hashchange), since client-side routers often re-add tracking params on route changes. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -12,6 +12,10 @@ unless a script's own README explicitly says otherwise.
|
||||
page's live DOM and shows them on-screen as selectable text. Inspired
|
||||
by [uidrop.site](https://uidrop.site)'s extraction categories, without
|
||||
its AI hand-off — nothing is ever sent anywhere.
|
||||
- [**Clean URL Tracking Params**](clean-url-tracking/) — strips known
|
||||
ad/tracking query parameters (`fbclid`, `gclid`, `utm_*`, `msclkid`,
|
||||
`ttclid`, and more) from the URL bar on load and on SPA navigation,
|
||||
via `history.replaceState` — no reload, no network requests.
|
||||
|
||||
## Installing a script
|
||||
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
# Clean URL Tracking Params
|
||||
|
||||
Strips known ad/tracking query parameters (`fbclid`, `gclid`, `utm_*`,
|
||||
`msclkid`, `ttclid`, and more — see the full list in the script) from the
|
||||
URL bar on page load and on single-page-app navigation, using
|
||||
`history.replaceState`. The page never reloads and nothing is ever sent
|
||||
anywhere — this only rewrites the URL already sitting in your own
|
||||
address bar.
|
||||
|
||||
Only well-known ad-network/analytics-specific parameter names are
|
||||
stripped. Generic-sounding params like `ref`, `source`, or `id` are
|
||||
deliberately left alone, since real sites often depend on those for
|
||||
actual navigation — this script only touches names that are
|
||||
unambiguously tracking-only.
|
||||
|
||||
**Scope note**: this is a cosmetic/privacy URL cleanup, not a
|
||||
network-level tracker blocker. If a tracking pixel already read the
|
||||
param before this script ran, cleaning the URL afterward doesn't
|
||||
un-fire it. Pair with a real request-blocking extension (uBlock Origin,
|
||||
etc.) if that's the goal — this script just keeps the URL itself clean
|
||||
to look at, copy, and share.
|
||||
|
||||
## Installing
|
||||
|
||||
See the [repo README](../README.md#installing-a-script).
|
||||
@@ -0,0 +1,134 @@
|
||||
// ==UserScript==
|
||||
// @name Clean URL Tracking Params
|
||||
// @namespace https://devops.cloudhost.es/Malin/userscripts
|
||||
// @version 1.0.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',
|
||||
]);
|
||||
|
||||
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);
|
||||
})();
|
||||
Reference in New Issue
Block a user