URL Tracking Parameter Cleaner

Automatically strips known privacy-invading tracking parameters from all URLs, links, and network requests

Você precisará instalar uma extensão como Tampermonkey, Greasemonkey ou Violentmonkey para instalar este script.

Você precisará instalar uma extensão como Tampermonkey para instalar este script.

Você precisará instalar uma extensão como Tampermonkey ou Violentmonkey para instalar este script.

Você precisará instalar uma extensão como Tampermonkey ou Userscripts para instalar este script.

Você precisará instalar uma extensão como o Tampermonkey para instalar este script.

Você precisará instalar um gerenciador de scripts de usuário para instalar este script.

(Eu já tenho um gerenciador de scripts de usuário, me deixe instalá-lo!)

Você precisará instalar uma extensão como o Stylus para instalar este estilo.

Você precisará instalar uma extensão como o Stylus para instalar este estilo.

Você precisará instalar uma extensão como o Stylus para instalar este estilo.

Você precisará instalar um gerenciador de estilos de usuário para instalar este estilo.

Você precisará instalar um gerenciador de estilos de usuário para instalar este estilo.

Você precisará instalar um gerenciador de estilos de usuário para instalar este estilo.

(Eu já possuo um gerenciador de estilos de usuário, me deixar fazer a instalação!)

// ==UserScript==
// @name         URL Tracking Parameter Cleaner
// @namespace    VVJMIFRyYWNraW5nIFBhcmFtZXRlciBDbGVhbmVy
// @version      1.0
// @description  Automatically strips known privacy-invading tracking parameters from all URLs, links, and network requests
// @author       smed79
// @license      GPLv3
// @icon         https://i25.servimg.com/u/f25/11/94/21/24/utpc10.png
// @match        *://*/*
// @run-at       document-start
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    // Tracking parameters - wildcard * supported
    const rawParams = `action_ref_*, action_type_*, hsa_*, itm_*, matomo_*, mkt_*, mtm_*, ns_*, piwik_*, pk_*, 
    sb_referer_*, sms_*, trk_*, tw_*, url_bnm_*, utm_*, zone_*, __s, _branch_match_id, _bta_c, 
    _bta_tid, _ga, _gac, _gl, _hsenc, _hsmi, _ke, _openstat, action_object_map, ad_id, 
    adgroupid, adjust_campaign, adjust_tracker, af_campaign, af_channel, af_keyword, af_medium, 
    af_source, awc, bclid, campaign_id, campid, cid, cj_event, cjevent, click_id, cmpid, 
    customid, dclid, dm_i, ef_id, elqTrack, epik, fb_action_ids, fb_action_types, fb_ref, 
    fb_source, fbclid, gbraid, gclid, gclsrc, gdffi, gdfms, gdftrk, hc_location, hc_ref, 
    hootPostID, hsCtaTracking, hubspotUtk, idzone, igshid, irclid, lptoken, mc_cid, mc_eid, 
    mkcid, mkevt, mkrid, mkwid, msclkid, ndclid, pcampaignid, pcrid, psid, pub_id, pubfeed, 
    publisherid, rdt, ref, ref_campaign, ref_source, s_kwcid, scm, scontext_r, si, srsltid, 
    toolid, ttclid, twclid, uclick, wbraid, wprov, wt_mc, WT.mc_id, WT.nav, yclid, zanpid, 
    zoneid`.split(',').map(p => p.trim().toLowerCase());

    // Pre-compile Regexes for performance
    const trackingRegexes = rawParams.map(pattern => {
        if (pattern.includes('*')) {
            const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, '\\$&');
            const regex = escaped.replace(/\*/g, '[^&=]*');
            return new RegExp(`^${regex}$`, 'i');
        }
        return new RegExp(`^${pattern}$`, 'i');
    });

    function isTrackingParam(paramName) {
        return trackingRegexes.some(regex => regex.test(paramName));
    }

    function cleanUrl(url) {
        if (!url || typeof url !== 'string' || url.startsWith('blob:') || url.startsWith('data:') || url.startsWith('javascript:')) return url; // Check to skip blob and data URLs
        try {
            const urlObj = new URL(url, window.location.origin);
            const params = new URLSearchParams(urlObj.search);

            // Safely iterate by converting keys to an array first
            const keysToDelete = Array.from(params.keys()).filter(isTrackingParam);
            
            if (keysToDelete.length === 0) return url; // No changes needed
            
            keysToDelete.forEach(key => params.delete(key));
            urlObj.search = params.toString();

            // Return relative path if original URL was relative (started with /)
            if (url.startsWith('/') && !url.startsWith('//')) {
                return urlObj.pathname + urlObj.search + urlObj.hash;
            }
            
            return urlObj.toString();
        } catch (e) {
            return url;
        }
    }

    function cleanElementAttribute(element, attribute) {
        if (element.hasAttribute(attribute)) {
            const url = element.getAttribute(attribute);
            // Relaxed the check to allow relative URLs like "/path?utm_..."
            if (url && !url.startsWith('javascript:') && !url.startsWith('mailto:')) {
                const cleaned = cleanUrl(url);
                if (cleaned !== url) {
                    element.setAttribute(attribute, cleaned);
                }
            }
        }
    }

    function cleanPage() {
        document.querySelectorAll('a[href]').forEach(el => cleanElementAttribute(el, 'href'));
        document.querySelectorAll('form[action]').forEach(el => cleanElementAttribute(el, 'action'));
        document.querySelectorAll('iframe[src], script[src], img[src]').forEach(el => cleanElementAttribute(el, 'src'));

        // Clean img/picture srcset
        document.querySelectorAll('[srcset]').forEach(el => {
            let srcset = el.getAttribute('srcset');
            if (srcset) {
                const cleanedSrcset = srcset.split(',').map(part => {
                    const [url, ...rest] = part.trim().split(' ');
                    return cleanUrl(url) + (rest.length ? ' ' + rest.join(' ') : '');
                }).join(',');
                if (cleanedSrcset !== srcset) el.setAttribute('srcset', cleanedSrcset);
            }
        });

        // Clean meta refresh
        document.querySelectorAll('meta[http-equiv="refresh" i]').forEach(meta => {
            const content = meta.getAttribute('content');
            if (content) {
                const match = content.match(/url=([^;]+)/i);
                if (match) {
                    const url = match[1].replace(/^['"]|['"]$/g, '');
                    const cleanedUrl = cleanUrl(url);
                    if (cleanedUrl !== url) {
                        meta.setAttribute('content', content.replace(url, cleanedUrl));
                    }
                }
            }
        });

        // Clean data attributes
        document.querySelectorAll('[data-url], [data-href], [data-src]').forEach(element => {
            ['data-url', 'data-href', 'data-src'].forEach(attr => cleanElementAttribute(element, attr));
        });
    }

    function injectPageScript() {
        // Passing our functions into the page context
        const pageScript = `
            (function() {
                const trackingRegexes = [${trackingRegexes.map(r => r.toString()).join(',')}];

                function isTrackingParam(paramName) {
                    return trackingRegexes.some(regex => regex.test(paramName));
                }

                function cleanUrl(url) {
                    if (!url || typeof url !== 'string' || url.startsWith('blob:') || url.startsWith('data:') || url.startsWith('javascript:')) return url; // Check to skip blob and data URLs
                    try {
                        const urlObj = new URL(url, window.location.origin);
                        const params = new URLSearchParams(urlObj.search);
                        const keysToDelete = Array.from(params.keys()).filter(isTrackingParam);
                        if (keysToDelete.length === 0) return url;
                        
                        keysToDelete.forEach(key => params.delete(key));
                        urlObj.search = params.toString();
                        
                        if (url.startsWith('/') && !url.startsWith('//')) {
                            return urlObj.pathname + urlObj.search + urlObj.hash;
                        }
                        return urlObj.toString();
                    } catch (e) {
                        return url;
                    }
                }

                // Intercept window.location
                const originalLocationAssign = window.location.assign;
                const originalLocationReplace = window.location.replace;
                
                try {
                    const originalHrefSetter = Object.getOwnPropertyDescriptor(Location.prototype, 'href').set;
                    Object.defineProperty(Location.prototype, 'href', {
                        set: function(url) {
                            originalHrefSetter.call(this, cleanUrl(url));
                        },
                        get: Object.getOwnPropertyDescriptor(Location.prototype, 'href').get
                    });
                } catch(e) {} // Fails safely in strict environments

                window.location.assign = function(url) { 
                    originalLocationAssign.call(window.location, cleanUrl(url)); 
                };
                window.location.replace = function(url) { 
                    originalLocationReplace.call(window.location, cleanUrl(url)); 
                };

                // Intercept XHR
                const originalXhrOpen = XMLHttpRequest.prototype.open;
                XMLHttpRequest.prototype.open = function(method, url, ...rest) {
                    const stringUrl = typeof url === 'string' ? url : url.toString();
                    const cleaned = cleanUrl(stringUrl);
                    // Only modify if the URL actually changed
                    if (cleaned !== stringUrl) {
                        return originalXhrOpen.call(this, method, cleaned, ...rest);
                    }
                    return originalXhrOpen.call(this, method, url, ...rest);
                };
            })();
        `;

        const script = document.createElement('script');
        script.textContent = pageScript;
        if (document.documentElement) document.documentElement.appendChild(script);
    }

    // Intercept fetch
    const originalFetch = window.fetch;
    window.fetch = function(...args) {
        if (args[0]) {
            if (typeof args[0] === 'string') {
                const cleaned = cleanUrl(args[0]);
                if (cleaned !== args[0]) {
                    args[0] = cleaned;
                }
            } else if (typeof args[0] === 'object' && args[0].url) {
                const cleaned = cleanUrl(args[0].url);
                // ONLY clone the Request object if the URL had tracking params removed
                if (cleaned !== args[0].url) {
                    args[0] = new Request(cleaned, args[0]);
                }
            }
        }
        return originalFetch.apply(this, args);
    };

    // Clean current page URL in address bar if tracking params exist
    const cleanedCurrentUrl = cleanUrl(window.location.href);
    if (cleanedCurrentUrl !== window.location.href) {
        window.history.replaceState(null, '', cleanedCurrentUrl);
    }

    // Debounce function to prevent lag from MutationObserver
    let timeout;
    const observer = new MutationObserver((mutations) => {
        const hasAdditions = mutations.some(m => m.addedNodes.length > 0);
        if (hasAdditions) {
            clearTimeout(timeout);
            timeout = setTimeout(() => cleanPage(), 300); // Wait 300ms after DOM settles
        }
    });

    injectPageScript();

    // Initial Run on DOMContentLoaded
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', cleanPage);
    } else {
        cleanPage();
    }

    observer.observe(document.documentElement || document.body, { childList: true, subtree: true });
})();