remove google tracking UWAA

remove google tracking

当前为 2018-12-02 提交的版本,查看 最新版本

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==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'
	],
	// 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();
}