// ==UserScript==
// @namespace jp.sceneq.rgtuwaaa
// @name remove google tracking UWAA
// @description remove google tracking
// @homepageURL https://github.com/sceneq/RemoveGoogleTracking
// @version 0.20
// @include https://www.google.*/*
// @grant none
// @run-at document-body
// @author sceneq
// @license MIT
// ==/UserScript==
const yes = () => true;
const doNothing = () => {};
const $ = (s, n = document) => n.querySelector(s);
const $$ = (s, n = document) => [...n.querySelectorAll(s)];
const sleep = millis => new Promise(resolve => setTimeout(resolve, millis));
const zip = rows => rows[0].map((_, c) => rows.map(row => row[c]));
const rewriteProperties = props => {
for (const p of props) {
Object.defineProperty(p[0] || {}, p[1], {
value: p[2],
writable: false,
});
}
};
const waitUntilDeclare = ({ obj, property, interval }) => {
console.debug('waitUntilDeclare', obj.toString(), property);
return new Promise(async (resolve, reject) => {
const propertyNames = property.split('.');
let currObj = obj;
for (const propertyName of propertyNames) {
while (!(propertyName in currObj) || currObj[propertyName] === null) {
await sleep(interval);
}
currObj = currObj[propertyName];
}
console.debug('waitUntilDeclare', obj.toString(), property, 'Done');
resolve(currObj);
});
};
const untrackBuilder = arg => {
const r = arg.badParamsRegex;
return a => {
const href = a.getAttribute('href');
if(href === null) return;
if (href.startsWith('/url?')) {
a.href = new URLSearchParams(href.slice(5)).get('q');
} else {
a.removeAttribute('ping');
a.href = a.href.replace(r, '');
}
};
};
const Types = {
search: Symbol('search'),
isch: Symbol('image'),
shop: Symbol('shop'),
nws: Symbol('news'),
vid: Symbol('video'),
bks: Symbol('book'),
maps: Symbol('maps'),
fin: Symbol('finance'),
toppage: Symbol('toppage'),
// flights: Symbol('flights'),
};
const tbmToType = tbm => {
if (tbm === null) {
return Types.search;
}
const t = {
isch: Types.isch,
shop: Types.shop,
nws: Types.nws,
vid: Types.vid,
bks: Types.bks,
fin: Types.fin,
}[tbm];
if (t === undefined) {
return null;
} else {
return t;
}
};
const BadParamsBase = [
'biw', // offsetWidth
'bih', // offsetHeight
'ei',
'sa',
'ved',
'source',
'prds',
'bvm',
'bav',
'psi',
'stick',
'dq',
'ech',
'gs_gbg',
'gs_rn',
'cp',
'ictx',
'cshid',
'gs_lcp',
];
const BadParams = (() => {
const o = {};
o[Types.search] = [
'pbx',
'dpr',
'pf',
'gs_rn',
'gs_mss',
'pq',
'cp',
'oq',
'sclient',
'gs_l',
'aqs',
];
o[Types.isch] = [
'scroll',
'vet',
'yv',
'iact',
'forward',
'ndsp',
'csi',
'tbnid',
'sclient',
'oq',
];
o[Types.shop] = ['oq'];
o[Types.nws] = ['oq'];
o[Types.vid] = ['oq'];
o[Types.bks] = ['oq'];
o[Types.fin] = ['oq'];
o[Types.toppage] = ['oq', 'sclient', 'uact'];
o[Types.maps] = ['psi'];
return o;
})();
const searchFormUriuri = async arg => {
let form = null;
if (arg.pageType.mobileOld) {
form = $('#sf');
} else if (arg.pageType.ty === Types.isch) {
form = await waitUntilDeclare({
obj: window,
property: 'sf',
interval: 30,
});
} else {
form = await waitUntilDeclare({
obj: window,
property: 'tsf',
interval: 30,
});
}
if (form === null) {
console.warn('form === null');
return;
}
for (const i of form) {
if (i.tagName !== 'INPUT') continue;
if (arg.badParams.includes(i.name)) {
i.parentElement.removeChild(i);
}
}
const orig = form.appendChild.bind(form);
form.appendChild = e => {
if (!arg.badParams.includes(e.name)) {
orig(e);
}
};
};
const untrackAnchors = (untrack, arg) => {
return waitUntilDeclare({
obj: window,
property: arg.pageType.mobile
? 'topstuff'
: arg.pageType.mobileOld
? 'rmenu'
: 'search',
interval: 30,
}).then(_ => {
for (const a of $$('a')) {
untrack(a);
}
});
};
const gcommon = async arg => {
const untrack = untrackBuilder(arg);
const p1 = waitUntilDeclare({
obj: window,
property: 'google',
interval: 30,
}).then(google => {
rewriteProperties([
[google, 'log', yes],
[google, 'logUrl', doNothing],
[google, 'getLEI', yes],
[google, 'ctpacw', yes],
[google, 'csiReport', yes],
[google, 'report', yes],
[google, 'aft', yes],
//[google, 'kEI', '0'],
//[google, 'getEI', yes], or ()=>"0"
]);
});
rewriteProperties([
[window, 'rwt', doNothing],
[window.gbar_, 'Rm', doNothing],
]);
const p2 = untrackAnchors(untrack, arg);
const p3 = searchFormUriuri(arg);
await Promise.all([p1, p2, p3]);
if (arg.pageType.mobile) {
const sel =
arg.pageType.ty === Types.search
? '#bres + h1 + div > div + div'
: '#bres + div > div + div';
new MutationObserver(mutations => {
const nodes = [];
for (const m of mutations) {
nodes.push(...m.addedNodes);
}
for (const n of nodes) {
new MutationObserver((_, obs) => {
console.debug('untrack', n);
for (const a of $$('a', n)) {
untrack(a);
}
obs.disconnect();
}).observe(n, { childList: true });
}
}).observe($(sel), {
childList: true,
});
}
};
const fun = {};
// TODO mobile, mobileOld
fun[Types.toppage] = searchFormUriuri;
fun[Types.search] = gcommon;
fun[Types.vid] = gcommon;
fun[Types.nws] = gcommon;
fun[Types.bks] = gcommon;
fun[Types.fin] = gcommon;
fun[Types.isch] = async arg => {
// TODO desktop, mobile, mobileOld
const untrack = untrackBuilder(arg);
const p1 = waitUntilDeclare({
obj: window,
property: 'islmp',
interval: 30,
}).then(() => {
for (const a of $$('a')) {
untrack(a);
}
});
const p2 = searchFormUriuri(arg);
await Promise.all([p1, p2]);
};
fun[Types.shop] = async arg => {
// TODO mobile, mobileOld
const untrack = untrackBuilder(arg);
const p1 = waitUntilDeclare({
obj: window,
property: 'google.pmc.spop.r',
interval: 30,
}).then(shopObj => {
// Rewrite to original link
const tmp = $$("div[class$='__content'] a[jsaction='spop.c']");
const [anchors, thumbs] = [0, 1].map(m =>
tmp.filter((_, i) => i % 2 === m)
);
const shops = Object.values(shopObj);
const links = shops.map(a => a[34][6]);
if (anchors.length === links.length) {
for (const [anchor, link, thumb, shop] of zip([
anchors,
links,
thumbs,
shops,
])) {
anchors.href = thumb.href = link;
shop[3][0][1] = link;
shop[14][1] = link;
shop[89][16] = link;
shop[89][18][0] = link;
shop[85][3] = link;
}
} else {
console.warn('length does not match', anchors.length, links.length);
}
});
const p2 = untrackAnchors(untrack, arg);
const p3 = searchFormUriuri(arg);
await Promise.all([p1, p2, p3]);
};
// TODO fun[Types.maps] = async arg => {}
(async () => {
'use strict';
console.debug('rgt: init');
console.time('rgt');
const ty = (() => {
if (location.pathname.startsWith('/maps')) {
return Types.maps;
}
if (location.pathname === '/' || location.pathname === '/webhp') {
return Types.toppage;
}
const tbm = new URLSearchParams(location.search).get('tbm');
if (location.pathname === '/search') {
return tbmToType(tbm);
}
return null;
})();
if (ty === null) {
console.debug('ty === null');
return;
}
const badParams = (() => {
return [...BadParamsBase, ...BadParams[ty]];
})();
const badParamsRegex = new RegExp(
'&(?:' + badParams.join('|') + ')=.*?(?=(&|$))',
'g'
);
const badImageSrcRegex = /\/(?:(?:gen(?:erate)?|client|fp)_|log)204|(?:metric|csi)\.gstatic\.|(?:adservice)\.(google)/;
Object.defineProperty(window.Image.prototype, 'src', {
set: function(url) {
if (!badImageSrcRegex.test(url)) {
this.setAttribute('src', url);
}
},
});
Object.defineProperty(window.HTMLScriptElement.prototype, 'src', {
set: function(url) {
this.setAttribute('src', url.replace(badParamsRegex, ''));
},
});
const badPaths = [
'imgevent',
'shopping\\/product\\/.*?\\/popout',
'async/ecr',
'async/bgasy',
];
const regBadPaths = new RegExp('^/(?:' + badPaths.join('|') + ')');
const origOpen = XMLHttpRequest.prototype.open;
window.XMLHttpRequest.prototype.open = function(act, path) {
if (path === undefined) return;
if (path.startsWith('https://aa.google.com/')) return;
if (path.startsWith('https://play.google.com/log')) return;
if (path.startsWith('https://www.google.com/log')) return;
if (regBadPaths.test(path)) return;
origOpen.apply(this, [act, path.replace(badParamsRegex, '')]);
};
if ('navigator' in window) {
const origSendBeacon = navigator.sendBeacon.bind(navigator);
navigator.sendBeacon = (path, data) => {
if (path.startsWith('https://play.google.com/log')) return;
if (badImageSrcRegex.test(path)) return;
origSendBeacon(path, data);
};
}
if (ty in fun) {
const mobileOld = $("html[itemtype]") === null; // &liteui=2
const mobile = !mobileOld && $('meta[name=viewport]') !== null;
const arg = {
pageType: {
ty,
mobile,
mobileOld,
},
badParams,
badParamsRegex,
};
console.debug('arg', arg);
await fun[ty](arg);
} else {
console.warn(`key not found in fun: ${ty.toString()}`);
}
console.timeEnd('rgt');
})();