Skip to content

Instantly share code, notes, and snippets.

@htsign
Last active September 26, 2024 15:03
Show Gist options
  • Save htsign/455bd76d107be1f810c5caa4072c8275 to your computer and use it in GitHub Desktop.
Save htsign/455bd76d107be1f810c5caa4072c8275 to your computer and use it in GitHub Desktop.
remove tracking parameters from current url
// ==UserScript==
// @name TrackingParameterRemover
// @namespace https://htsign.hateblo.jp
// @version 20240926-rev0
// @description remove tracking parameters
// @author htsign
// @match *://*/*
// @run-at document-start
// @grant GM_registerMenuCommand
// @grant GM_getValue
// @grant GM_setValue
// @updateURL https://gist.github.com/htsign/455bd76d107be1f810c5caa4072c8275/raw/TrackingParameterRemover.user.js
// @downloadURL https://gist.github.com/htsign/455bd76d107be1f810c5caa4072c8275/raw/TrackingParameterRemover.user.js
// ==/UserScript==
const TRACKING_TAGS = [
'#?utm_campaign',
'#?utm_content',
'#?utm_int',
'#?utm_medium',
'#?utm_source',
'_hsmi',
'_openstat',
'action_object_map',
'action_ref_map',
'action_type_map',
'fb_action_ids',
'fb_action_types',
'fb_ref',
'fb_source',
'ga_campaign',
'ga_content',
'ga_medium',
'ga_place',
'ga_source',
'ga_term',
'gs_l',
'guccounter',
'guce_referrer',
'guce_referrer_sig',
'gws_rd',
'hmb_campaign',
'hmb_medium',
'hmb_source',
'ref_src',
'ref_url',
'utm_campaign',
'utm_cid',
'utm_content',
'utm_int',
'utm_language',
'utm_medium',
'utm_name',
'utm_place',
'utm_pubreferrer',
'utm_reader',
'utm_source',
'utm_swu',
'utm_term',
'utm_userid',
'utm_viz_id',
'yclid',
'cx_part@www.afpbb.com',
'_encoding@amazon.*',
'linkCode@amazon.*',
'linkId@amazon.*',
'pd_rd_*@amazon.*',
'psc@amazon.*',
'qid@amazon.*',
'sbo@amazon.*',
'sprefix@amazon.*',
'sr@amazon.*',
'tag@amazon.*',
'mkt_tok@www.amd.com',
'rm@digital.asahi.com',
'iref@www.asahi.com',
'ref@www.asahi.com',
'spJobID@www.asahi.com',
'spMailingID@www.asahi.com',
'spReportId@www.asahi.com',
'spUserID@www.asahi.com',
'callback@bilibili.com',
'cvid@bing.com',
'form@bing.com',
'pq@bing.com',
'qs@bing.com',
'sc@bing.com',
'sk@bing.com',
'sp@bing.com',
'algorithm@www.change.org',
'grid_position@www.change.org',
'algorithm@www.change.org',
'grid_position@www.change.org',
'j@www.change.org',
'jb@www.change.org',
'l@www.change.org',
'mid@www.change.org',
'original_footer_petition_id@www.change.org',
'placement@www.change.org',
'pt@www.change.org',
'sfmc_sub@www.change.org',
'source_location@www.change.org',
'u@www.change.org',
'bi?@google.*',
'client@google.*',
'dpr@google.*',
'ei@google.*',
'gws_rd@google.*',
'oq@google.*',
'sa@google.*',
'sei@google.*',
'source@google.*',
'ved@google.*',
'ncid@huffingtonpost.jp',
'fbclid@itmedia.co.jp',
'word_result@nhk.or.jp',
'n_cid@nikkeibp.co.jp',
'n_cid@nikkei.com',
'position@sourceforge.net',
'source@sourceforge.net',
's@x.com',
't@x.com',
'feature@youtube.com',
'gclid@youtube.com',
'kw@youtube.com',
];
const createStyle = params => {
const toKebab = s => s.replace(/(?<=[a-z])[A-Z]/g, m => `-${m.toLowerCase()}`);
return Object.entries(params)
.map(([key, val]) => `${toKebab(key)}: ${val};`)
.join('; ');
};
const createWrapper = () => {
const PADDING = 20;
const wrapper = Object.assign(
document.createElement('div'),
{
style: createStyle({
position: 'fixed',
width: 'fit-content',
maxHeight: 'fit-content',
left: `${PADDING}px`,
top: `${PADDING}px`,
zIndex: 2 ** 31 - 1,
}),
},
);
return document.body.appendChild(wrapper);
};
GM_registerMenuCommand('open settings', async ev => {
const wrapper = createWrapper();
const shadowRoot = wrapper.attachShadow({ mode: 'open' });
const css = `
:host {
background-color: white;
border: 2px solid #777;
max-width: 600px;
@media (prefers-color-scheme: dark) {
background-color: #333;
}
}
#list {
display: block;
white-space: pre;
width: 400px;
height: max(50vh, 600px);
}
#help {
font: 16px sans-serif;
}
#help_pre {
font: 14px monospace;
line-height: 1;
}
#controller {
display: flex;
justify-content: space-between;
}
#content_controller {
display: flex;
}
#reset_button {
background-color: rgb(64 0 0);
}
`;
/** @type {string[]} */
const trackingTags = GM_getValue('tracking_tags', TRACKING_TAGS);
const listArea = Object.assign(document.createElement('textarea'), {
id: 'list',
value: trackingTags.join('\n'),
});
const controllerBar = Object.assign(document.createElement('div'), { id: 'controller' });
const contentController = Object.assign(document.createElement('div'), { id: 'content_controller' });
const resetButton = Object.assign(document.createElement('button'), {
id: 'reset_button',
onclick() {
listArea.value = trackingTags.join('\n');
},
textContent: 'Reset',
});
const confirmButton = Object.assign(document.createElement('button'), {
id: 'confirm_button',
onclick() {
GM_setValue('tracking_tags', listArea.value.split('\n'));
},
textContent: 'Save',
});
const closeButton = Object.assign(document.createElement('button'), {
onclick() {
wrapper.remove();
},
textContent: 'Close',
});
const help = Object.assign(document.createElement('details'), { id: 'help' });
help.append(
Object.assign(document.createElement('summary'), { textContent: 'Help' }),
Object.assign(document.createElement('pre'), {
id: 'help_pre',
textContent: `
standard style:
- <parameter-name>[@<host>[/<path>]]
- e.g. "utm_source"
- e.g. "utm_source@twitter.com"
for hash keywords:
- #?<parameter-name>[@<host>[/<path>]]
others:
- blank lines are ignored
- placing "//" at the beginning of a line causes that line to be ignored
`.trim(),
}),
);
contentController.append(resetButton, confirmButton);
controllerBar.append(contentController, closeButton);
shadowRoot.append(listArea, help, controllerBar);
shadowRoot.adoptedStyleSheets = [await new CSSStyleSheet().replace(css)];
});
(function() {
'use strict';
/**
* @param {Location} loc
* @returns {{ url: string, locationChanged: boolean }}
*/
const removeTracking = loc => {
let locationChanged = false;
const escapables = Object.freeze({
'.': '\.',
});
const wildcardCharacters = Object.freeze({
'*': '.*',
'?': '.',
});
const wildcardKeys = Object.keys(wildcardCharacters);
/**
* @param {string} pattern
* @returns {RegExp}
*/
const toRegExp = pattern => {
/**
*
* @param {Record<string, string>} table
* @param {string} s
* @returns {string}
*/
const replace = (table, s) => Object.entries(table).reduce((acc, [f, t]) => acc.split(f).join(t), s);
const sanitized = replace(escapables, pattern);
const inner = replace(wildcardCharacters, sanitized);
return new RegExp('^' + inner + '$', 'i');
};
/**
*
* @param {string} pattern
* @param {string} s
* @returns {boolean}
*/
const match = (pattern, s) => {
if (wildcardKeys.some(c => pattern.includes(c))) {
return toRegExp(pattern).test(s);
}
return pattern === s;
};
/**
* @param {string} domain
* @param {URLSearchParams} params
* @param {string} pattern
* @returns {string}
*/
const deleteKeys = (domain, params, pattern) => {
if (!domain || loc.hostname.split('.').some((_, i, arr) => match(domain, arr.slice(i).join('.')))) {
for (const [key] of params) {
if (match(pattern, key)) {
params.delete(key);
locationChanged = true;
}
}
}
return params.toString().split('%25').join('%');
};
/**
* @param {string} search
* @param {() => boolean} condition
* @param {(arg: URLSearchParams) => any} callback
* @returns {void}
*/
const proc = (search, condition, callback) => {
const params = new URLSearchParams(search);
if (params.size === 0) return;
if (!condition()) return;
callback(params);
};
const url = new URL(loc);
url.search = url.search.split('%25').join('\0'); // avoid to escape of original '%25'
GM_getValue('tracking_tags', TRACKING_TAGS).forEach(tag => {
if (tag === '' || tag.startsWith('//')) return;
const [t, domain, pathname] = tag.split(/[@\/]/);
if (t.startsWith('#?')) {
proc(
url.hash.slice(1),
() => pathname == null || url.pathname === `/${pathname}`,
params => {
url.hash = deleteKeys(domain, params, t.slice(2));
},
);
}
else {
if (!url.search) return;
proc(
url.search.slice(1).replace(/%(?!25)/g, '%25'),
() => pathname == null || url.pathname === `/${pathname}`,
params => {
url.search = deleteKeys(domain, params, t);
},
);
}
});
url.search = url.search.split('%00').join('%25'); // restore original '%25'
return { url: url.href, locationChanged };
};
const { url, locationChanged } = removeTracking(location);
if (locationChanged) {
console.info(`TrackingParameterRemover: {${location.href}} => {${url}}`);
location.replace(url);
}
}());
@Korb
Copy link

Korb commented Feb 24, 2023

The script does not work with links pasted by the Augmented Steam add-on. Links like https://steamdb.info/app/.../?utm_source=Steam&utm_medium=Steam&utm_campaign=SteamDB%20Extension and https://pcgamingwiki.com/api/appid.php?appid=...&utm_source=Steam&utm_medium=Steam&utm_campaign=SteamDB%20Extension remain unchanged. Mozilla Firefox 111.0b5 (64-bit), Tampermonkey 4.18.1 (January 17, 2023).

@htsign
Copy link
Author

htsign commented Feb 24, 2023

@Korb Thanks for your report.
I tried that, but not working as you said. It seems not other userscripts with @include * don't work in this situation too, so probrems are on AugmentedSteam or TamperMonkey side, maybe.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment