// ==UserScript==
// @namespace jp.sceneq.rgtuwaaa
// @name remove google tracking UWAA
// @description remove google tracking
// @description:ja Google追跡UWAAを取り除きなさい
// @homepageURL https://github.com/sceneq/RemoveGoogleTracking
// @version 0.10
// @include https://www.google.*/*
// @grant none
// @run-at document-start
// @author sceneq
// @license MIT
// ==/UserScript==
'use strict';
console.time('init');
try {
window = window.unsafeWindow || window;
} catch (e) {}
const yesman = function() {
return true;
};
const tired = function() {};
/* Return the current Google search mode */
function getParam(parameter, name) {
var results = new RegExp('[?&]' + name + '=([^&#]*)').exec(parameter);
if (results === null) {
return null;
} else {
return results.pop() || 0;
}
}
/* return search mode */
function getSearchMode() {
const parameter = location.search + location.hash;
return getParam(parameter, 'tbm') || 'search';
}
// matching tracking paramaters
const badParametersNamesObj = {
base: [
'biw', // offsetWidth
'bih', // offsetHeight
'ei',
'sa',
'ved',
'source',
'prds',
'bvm',
'bav',
'psi',
'stick',
'dq',
'ech',
'gs_gbg',
'gs_rn',
'cp',
'ictx',
'cshid'
],
// image search
isch: [
'scroll',
'vet',
'yv',
'iact',
'forward',
'ndsp',
'csi',
'tbnid'
//'docid', // related images
//'imgdii', // related images
],
searchform: [
// search form
'pbx',
'dpr',
'pf',
'gs_rn',
'gs_mss',
'pq',
'cp',
'oq',
'sclient',
'gs_l',
'aqs'
//'gs_ri', // suggestions
//'gs_id', // suggestions
//'xhr', // suggestions at image search
//'tch', // js flag?
],
// Google maps
maps: ['psi']
};
// search option on first access
const GoogleOp = {
maps: Symbol(),
isch: Symbol(),
search: Symbol(),
unknown: Symbol()
};
const op = (() => {
if (location.pathname.startsWith('/maps')) {
return GoogleOp.maps;
}
if (getSearchMode() === 'isch') {
return GoogleOp.isch;
} else {
return GoogleOp.search;
}
})();
const badParametersNames = (() => {
switch (op) {
case GoogleOp.maps:
return badParametersNamesObj.maps;
case GoogleOp.isch:
return badParametersNamesObj.base.concat(
badParametersNamesObj.searchform,
badParametersNamesObj.isch
);
case GoogleOp.search:
return badParametersNamesObj.base.concat(
badParametersNamesObj.searchform
);
}
})();
const badAttrNamesObj = {
default: ['onmousedown', 'ping', 'oncontextmenu'],
search: ['onmousedown', 'ping', 'oncontextmenu'],
vid: ['onmousedown'],
nws: ['onmousedown'],
bks: [],
isch: [],
shop: []
};
// From the nodes selected here, delete parameters specified by badParametersNames
const dirtyLinkSelectors = [
// menu
'a.q.qs',
// doodle
'a.doodle',
// Upper left menu
'.gb_Z > a',
// Logo
'a#logo',
'div #logocont > a',
'div#qslc > a',
'header#hdr > div > a',
// search button?
'form#sf > a',
/// imagesearch
// colors
'div#sc-block > div > a',
// size
'a.hdtb-mitem'
];
const badPaths = ['imgevent', 'shopping\\/product\\/.*?\\/popout', 'async/ecr', 'async/bgasy'];
/* Compile */
// The first paramater is probably 'q' so '?' does not consider
const regBadParameters = new RegExp(
'&(?:' + badParametersNames.join('|') + ')=.*?(?=(&|$))',
'g'
);
const regBadPaths = new RegExp('^/(?:' + badPaths.join('|') + ')');
const dirtyLinkSelector = dirtyLinkSelectors
.map(s => s + ":not([href=''])")
.join(',');
/* Return parameter value */
function extractDirectLink(str, param) {
//(?<=q=)(.*)(?=&)/
const res = new RegExp(`[?&]${param}(=([^&#]*))`).exec(str);
if (!res || !res[2]) return '';
return decodeURIComponent(res[2]);
}
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
/* Return Promise when declared the variable name specified by argument */
function onDeclare(obj, propertyStr, interval = 80) {
return new Promise(async function(resolve, reject) {
const propertyNames = propertyStr.split('.');
let currObj = obj;
for (const propertyName of propertyNames) {
while (!(propertyName in currObj) || currObj[propertyName] === null) {
await sleep(interval);
}
currObj = currObj[propertyName];
}
resolve(currObj);
});
}
function rewriteProperties(prop) {
for (const table of prop) {
//const targetObject = typeof table[0] === 'function' ? table[0]() : table[0];
Object.defineProperty(table[0] || {}, table[1], {
value: table[2],
writable: false
});
}
}
function load() {
console.time('LOAD');
/* Overwrite disturbing functions */
rewriteProperties([[window, 'rwt', yesman], [window.gbar_, 'Rm', yesman]]);
// do not send referrer
const noreferrerMeta = document.createElement('meta');
noreferrerMeta.setAttribute('name', 'referrer');
noreferrerMeta.setAttribute('content', 'no-referrer');
document.querySelector('head').appendChild(noreferrerMeta);
/*
* Variables
*/
// Whether to use AJAX
const legacy = document.getElementById('cst') === null;
/* Nodes */
const nodeMain = document.getElementById('main');
const nodeCnt = document.getElementById('cnt');
const root = (() => {
if (legacy) {
return nodeCnt || nodeMain || window.document;
} else {
return nodeMain; // || nodeCnt;
}
})();
// Flag indicating whether the hard tab is loaded on 'DOMContentLoaded'
const lazy_hdtb = !legacy || root === nodeCnt;
// Define selector function
const $ = root.querySelector.bind(root);
const $$ = sel =>
Array.prototype.slice.call(root.querySelectorAll.call(root, [sel]));
// Selector pointing to anchors to purify
const dirtySelector = (() => {
if (root === window.document) {
return 'body a';
} else if (legacy) {
return `#${root.id} a`;
} else {
return '#rcnt a';
}
})();
// List of parameters to keep
const saveParamNames = [
'q',
'hl',
'num',
'tbm',
'tbs',
'lr',
'btnI',
'btnK',
'safe'
];
const obstacleInputsSelector =
'form[id*=sf] input' +
saveParamNames.map(s => ':not([name=' + s + '])').join('');
/*
* Functions
*/
function removeFormInputs() {
for (const node of document.querySelectorAll(obstacleInputsSelector)) {
node.parentNode.removeChild(node);
}
}
function removeBadParameters() {
for (const dirtyLink of document.querySelectorAll(dirtyLinkSelector)) {
dirtyLink.href = dirtyLink.href.replace(regBadParameters, '');
}
}
const specProcesses = {
shop: function() {
// Overwrite links(desktop version only)
//Object.values(google.pmc.smpo.r).map(s=>{return {title:s[14][0],link:s[28][8]}})
if (legacy) return;
onDeclare(google, 'pmc.spop.r').then(shopObj => {
const _tempAnchors = $$("div[class$='__content'] a[jsaction='spop.c']");
const [shopAnchors, shopThumbnailAnchors] = [0, 1].map(m =>
_tempAnchors.filter((_, i) => i % 2 === m)
);
const shopArrays = Object.values(shopObj);
const shopLinks = shopArrays.map(a => a[34][6]);
const zip = rows => rows[0].map((_, c) => rows.map(row => row[c]));
if (shopAnchors.length !== shopLinks.length) {
console.warn(
'length does not match',
shopAnchors.length,
shopLinks.length
);
return;
}
for (const detail of zip([
shopAnchors,
shopLinks,
shopThumbnailAnchors,
shopArrays
])) {
const [shopAnchor, shopLink, shopThumbnailAnchor, shopArray] = detail;
shopAnchor.href = shopThumbnailAnchor.href = shopLink;
// Disable click actions
//detail[0].__jsaction = null;
//detail[0].removeAttribute("jsaction");
// Overwrite variables used when link clicked
try {
shopArray[3][0][1] = shopLink;
shopArray[14][1] = shopLink;
shopArray[89][16] = shopLink;
shopArray[89][18][0] = shopLink;
shopArray[85][3] = shopLink;
} catch (e) {}
}
console.log('Links Rewrited');
});
}
};
function removeTracking() {
console.time('removeTracking');
const searchMode = getSearchMode();
const badAttrNames = badAttrNamesObj[searchMode]
? badAttrNamesObj[searchMode]
: badAttrNamesObj['default'];
const directLinkParamName = 'q';
// search result
for (const searchResult of $$(dirtySelector)) {
// remove attributes
for (const badAttrName of badAttrNames) {
searchResult.removeAttribute(badAttrName);
}
// hide referrer
searchResult.rel = 'noreferrer';
// remove google redirect link(legacy)
if (
searchResult.hasAttribute('href') &&
searchResult.getAttribute('href').startsWith('/url?')
) {
searchResult.href = extractDirectLink(
searchResult.href,
directLinkParamName
);
}
searchResult.href = searchResult.href.replace(regBadParameters, '');
}
removeBadParameters();
searchMode in specProcesses && specProcesses[searchMode]();
console.timeEnd('removeTracking');
}
const ObserveOp = {
LOADED: {
FORM: ['INPUT', 'name', /^oq$/],
IMAGE: ['DIV', 'class', /.*irc_bg.*/],
HDTB: ['DIV', 'class', /^hdtb-mn-cont$/]
},
UPDATE: {
HDTB: ['DIV', 'class', /^hdtb-mn-cont$/]
},
CHANGE: {
HDTB: ['DIV', 'id', /^cnt$/],
PAGE: ['DIV', 'data-set', /.+/]
}
};
const ObserveUntilLoadedList = Object.values(ObserveOp.LOADED);
function startObserve(targetElement, op, func, conf = { childList: true }) {
if (targetElement === null) {
console.warn('targetElement is null', op, func);
return;
}
//console.log(op, 'Register To', targetElement);
const targetElementName = op[0];
const targetAttrName = op[1];
const targetAttrValueReg = op[2];
const filterFunc = n => {
return (
n.nodeName === targetElementName &&
targetAttrValueReg.test(n.getAttribute(targetAttrName))
);
};
// if targetElement already appeared
if (
ObserveUntilLoadedList.includes(op) &&
Array.prototype.slice
.call(targetElement.querySelectorAll(targetElementName))
.filter(filterFunc).length >= 1
) {
//console.log(op, 'Register To', targetElement);
func();
return;
}
new MutationObserver((mutations, observer) => {
const nodes = Array.prototype.concat
.apply([], mutations.map(s => Array.prototype.slice.call(s.addedNodes)))
//.map((s)=>{console.log(s);return s})
.filter(filterFunc);
if (nodes.length >= 1) {
//console.log(targetElement, op, 'Fired', nodes[0], func);
func();
if (ObserveUntilLoadedList.includes(op)) {
observer.disconnect();
}
//return startObserve;
}
}).observe(targetElement, conf);
}
function pageInit() {
removeTracking();
startObserve($('#search'), ObserveOp.CHANGE.PAGE, removeTracking);
}
const confDeepObserve = { childList: true, subtree: true };
// Remove unnecessary input
startObserve(
document.querySelector('form'),
ObserveOp.LOADED.FORM,
removeFormInputs
);
const tsf = document.getElementById("tsf");
if(tsf === null){
console.warn("#tsf not found");
}else{
//tsf.addEventListener("submit", () => {
const _submit = tsf.submit.bind(tsf);
tsf.submit = () => {
const sel = "input " + badParametersNames.map(p => `[name=${p}]`).join(",");
for(const node of tsf.querySelectorAll(sel)){
node.parentNode.removeChild(node);
}
_submit();
};
}
// Wait for .hdtb-mn-cont appears in the first page access
if (lazy_hdtb && !legacy) {
startObserve(
root,
ObserveOp.LOADED.HDTB,
() => {
// hdtb loaded
switch (getSearchMode()) {
case 'isch': // Image Search
removeTracking();
// Remove unnecessary script from buttons
startObserve($('#isr_mc'), ObserveOp.LOADED.IMAGE, () => {
for (const node of $$('a[class*=irc_]')) {
node.__jsaction = null;
node.removeAttribute('jsaction');
}
});
// on search options updated
startObserve(
$('#top_nav'),
ObserveOp.UPDATE.HDTB,
removeBadParameters,
confDeepObserve
);
break;
default:
pageInit();
// Wait for #cnt inserted. In HDTB switching, since .hdtb-mn-cont does not appear
startObserve(root, ObserveOp.CHANGE.HDTB, pageInit);
break;
}
removeFormInputs();
},
confDeepObserve
);
} else if (legacy) {
removeTracking();
// Remove unnecessary parameters from hdtb
const hdtbRoot = $('#hdtbMenus');
if (hdtbRoot) {
startObserve(hdtbRoot, ObserveOp.LOADED.HDTB, removeBadParameters);
}
if (document.getElementById('hdr')) {
const stopAddParams = s => {
const src = s.srcElement;
if (src.nodeName === 'A' && src.href.match(/\/search.*[?&]tbm=isch/)) {
s.stopPropagation();
}
};
document.addEventListener('click', stopAddParams, true);
document.addEventListener('touchStart', stopAddParams, true);
}
// Remove unnecessary parameters from 'option'
for (const option of document.querySelectorAll('#mor > option')) {
option.value = option.value.replace(regBadParameters, '');
}
console.warn('legacy mode');
console.timeEnd('LOAD');
return;
}
console.timeEnd('LOAD');
}
function init() {
onDeclare(window, 'google', 20).then(() => {
rewriteProperties([
[google, 'log', yesman],
[google, 'logUrl', tired],
[google, 'getEI', yesman],
[google, 'getLEI', yesman],
[google, 'ctpacw', yesman],
[google, 'csiReport', yesman],
[google, 'report', yesman],
[google, 'aft', yesman],
[google, 'kEI', '0']
]);
});
// Reject Request by img tag
//maps:log204
const regBadImageSrc = /\/(?:(?:gen(?:erate)?|client|fp)_|log)204|(?:metric|csi)\.gstatic\.|(?:adservice)\.(google)/;
Object.defineProperty(window.Image.prototype, 'src', {
set: function(url) {
if (!regBadImageSrc.test(url)) {
this.setAttribute('src', url);
}
}
});
// Reject unknown parameters by script tag
Object.defineProperty(window.HTMLScriptElement.prototype, 'src', {
set: function(url) {
this.setAttribute('src', url.replace(regBadParameters, ''));
}
});
// hook XHR
const origOpen = XMLHttpRequest.prototype.open;
window.XMLHttpRequest.prototype.open = function(act, path) {
if (!regBadPaths.test(path)) {
origOpen.apply(this, [act, path.replace(regBadParameters, '')]);
}
};
// beacon
if ('navigator' in window) {
const origSendBeacon = navigator.sendBeacon.bind(navigator);
navigator.sendBeacon = (path, data) => {
if (!regBadImageSrc.test(path)) {
origSendBeacon(path, data);
}
};
}
}
/* Execute */
init();
console.timeEnd('init');
if (
location.pathname !== '/' &&
location.pathname !== '/search' &&
document.querySelector('html').getAttribute('itemtype') !==
'http://schema.org/SearchResultsPage'
) {
console.warn('');
return;
}
window.addEventListener('DOMContentLoaded', load);
// for older browser
if (document.getElementById('universal') !== null) {
load();
}