您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
remove google tracking
// ==UserScript== // @namespace jp.sceneq.rgtuwaaa // @name remove google tracking UWAA // @description remove google tracking // @homepageURL https://github.com/sceneq/RemoveGoogleTracking // @version 0.21 // @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 = (arg) => { for (const [obj, prop, value] of arg) { if (!obj) continue; Object.defineProperty(obj, prop, { value, writable: false, }); } }; const waitUntilDeclare = (obj, property, option = { interval: 100 }) => { console.debug('waitUntilDeclare Start', 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(option.interval); } currObj = currObj[propertyName]; } console.debug('waitUntilDeclare Done', obj.toString(), property); resolve(currObj); }); }; const waitUntilNode = (sel, option = { interval: 100 }) => { console.debug('waitUntilNode Start', sel); return new Promise(async (resolve, reject) => { let d = null; while (d === null) { d = $(sel); await sleep(option.interval); } console.debug('waitUntilNode Done', sel); resolve(d); }); }; const untrackBuilder = (arg) => { const badParams = arg.badParams; return (a) => { const href = a?.href; if (!href) return; const url = new URL(href); if (a.getAttribute('href') === '/url') { a.href = url.searchParams.get('url'); // todo q? } else { a.removeAttribute('ping'); a.href = delParams(href, badParams); } }; }; const delParams = (sUrl, params) => { if (sUrl.startsWith('/')) { sUrl = location.origin + sUrl; } const url = new URL(sUrl); const keys = [...url.searchParams.keys()]; for (const k of keys) { if (!params.includes(k)) continue; url.searchParams.delete(k); } return url.toString(); }; 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] = [ 'vet', 'pbx', 'dpr', 'pf', 'gs_rn', 'gs_mss', 'pq', 'cp', 'oq', 'sclient', 'gs_l', 'aqs', 'sxsrf', ]; o[Types.isch] = [ 'vet', 'scroll', '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 overwrite = (arg) => { const badImageSrcRegex = /\/(?:(?:gen(?:erate)?|client|fp)_|log)204|(?:metric|csi)\.gstatic\.|(?:adservice)\.(google)/; if(arg.pageType.ty !== Types.maps){ Object.defineProperty(window.Image.prototype, 'src', { set: function (url) { //console.debug('img send', url); if (badImageSrcRegex.test(url)) return; this.setAttribute('src', url); }, }); } Object.defineProperty(window.HTMLScriptElement.prototype, 'src', { set: function (url) { //console.debug('script send', url); if(typeof(url) === "string"){ this.setAttribute('src', delParams(url, arg.badParams)); } else { this.setAttribute('src', url); } }, }); const blockOnOpenPaths = [ '/imgevent', '/async/ecr', '/async/bgasy', '/shopping/product/.+?/popout', '/_/VisualFrontendUi/browserinfo', '/_/VisualFrontendUi/jserror', ]; // todo fakeXHR? const blockOnSendPaths = ['/log']; const regBlockOnOpenPaths = new RegExp( '^(?:' + blockOnOpenPaths.join('|') + ')' ); const regBlockOnSendPaths = new RegExp( '^(?:' + blockOnSendPaths.join('|') + ')' ); const origOpen = window.XMLHttpRequest.prototype.open; window.XMLHttpRequest.prototype.open = function (act, path) { try { this.__path = path; this.__url = null; if (path === undefined) return; if (path.startsWith('https://')) { const url = new URL(path); this.__url = url; if (this.__url.origin === 'aa.google.com') return; if (regBlockOnOpenPaths.test(this.__url.pathname)) return; } else if (regBlockOnOpenPaths.test(this.__path)) { return; } const new_path = delParams(path, arg.badParams); } catch (e) { console.error(e); return; } //console.debug('xhr open', this.__path); return origOpen.apply(this, [act, this.__path]); }; const origSend = window.XMLHttpRequest.prototype.send; window.XMLHttpRequest.prototype.send = function (body) { try { if (this.__url !== null) { if (regBlockOnSendPaths.test(this.__url.pathname)) return; } else if (regBlockOnOpenPaths.test(this.__path)) { return; } } catch (e) { console.error(e); return; } //console.debug('xhr send', this.__path); return origSend.apply(this, [body]); }; if ('navigator' in window) { const origSendBeacon = navigator.sendBeacon.bind(navigator); navigator.sendBeacon = (path, data) => { if (path === undefined) return; if (path.startsWith('https://play.google.com/log')) return; if (badImageSrcRegex.test(path)) return; //console.debug('nav send', path); origSendBeacon(path, data); }; } }; const searchFormUriuri = async (arg) => { // TODO mobile, mobileOld let form = null; if (arg.pageType.mobileOld) { form = $('#sf'); } else if (arg.pageType.ty === Types.isch) { form = await waitUntilDeclare(window, 'sf'); } else { form = await waitUntilDeclare(window, 'tsf'); } 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) => { let property = null; if (arg.pageType.mobile) { property = 'topstuff'; } else if (arg.pageType.mobileOld) { property = 'main'; // 'rmenu'; } else if (arg.pageType.ty === Types.search) { property = 'hdtb-msb'; } else if (arg.pageType.ty === Types.bks) { property = 'lr_'; } else if (arg.pageType.ty === Types.vid) { property = 'hdtb-msb'; } else if (arg.pageType.ty === Types.nws) { property = 'hdtb-msb'; } else if (arg.pageType.ty === Types.isch) { property = 'i4'; } else { property = 'search'; } return waitUntilDeclare(window, property).then((_) => { for (const a of $$('a')) { untrack(a); } }); }; const gcommon = async (arg) => { const untrack = untrackBuilder(arg); const p1 = waitUntilDeclare(window, 'google').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 = mutations.flatMap((d) => [...d.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 = {}; 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 mobile, mobileOld const untrack = untrackBuilder(arg); const p1 = untrackAnchors(untrack, arg); const p2 = searchFormUriuri(arg); const p3 = waitUntilDeclare(window, 'islrg').then((islrg) => { const imagesWrapper = islrg.children[0]; const uuuu = (div) => { if (div.children.length !== 2) return; const a = div.children[1]; untrack(a); a.removeAttribute('jsaction'); }; for (const div of $$(':scope > div', imagesWrapper)) { uuuu(div); } new MutationObserver((mutations) => { for (const div of mutations.flatMap((m) => [...m.addedNodes])) { uuuu(div); } }).observe(imagesWrapper, { childList: true }); }); /* const p4 = waitUntilNode('.ZsbmCf').then(_ => { for (const a of $$('.ZsbmCf, .Beeb4e')) { const ee = e => untrack(a); a.addEventListener('click', ee); a.addEventListener('contextmenu', ee); } }); */ /* const safeurl = a => { const s = a.j ?? a; if (s.startsWith('/imgres?')) { return delParams(location.origin + s, [ 'vet', 'w', 'h', 'ved', // q imgurl imgrefurl tbnid docid ]); } if (s.startsWith('/')) return s; return new URLSearchParams(s).get('url') ?? s; }; const p4 = waitUntilDeclare(window, 'default_VisualFrontendUi').then(_ => { rewriteProperties([[_, 'zd', safeurl]]); }); */ await Promise.all([p1, p2, p3]); }; fun[Types.shop] = async (arg) => { // TODO desktop, mobile, mobileOld const untrack = untrackBuilder(arg); const p1 = untrackAnchors(untrack, arg); const p2 = searchFormUriuri(arg); const p3 = waitUntilDeclare(window, 'google.pmc.spop.r', { interval: 30, }).then((shopObj) => { for (const result of $$('.sh-dlr__list-result')) { const shop = shopObj[result.dataset.docid]; const link = shop[34][6]; result.querySelector("a[class$='__merchant-name']").href = link; result.querySelector('a.translate-content').href = link; result.querySelector('div.sh-dlr__thumbnail > a').href = link; shop[3][0][1] = link; shop[14][1] = link; shop[89][16] = link; shop[89][18][0] = link; if (shop[85] !== null) { shop[85][3] = link; } } }); await Promise.all([p1, p2, p3]); }; fun[Types.maps] = async (arg) => { // TODO desktop, mobile, mobileOld const untrack = (a) => { a.addEventListener('click', (e) => e.stopPropagation()); for (const n of [...a.attributes] .map((at) => at.name) .filter((n) => ['href', 'class'].indexOf(n) === -1)) { a.removeAttribute(n); } }; const main = await waitUntilNode('div[role=main]', { interval: 30 }); new MutationObserver((mutations) => { console.log(mutations); }).observe(main.children[1].children[0], { childList: true }); }; (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', location.href); return; } const badParams = (() => { return [...BadParamsBase, ...BadParams[ty]]; })(); 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, }; console.debug('arg', arg); overwrite(arg); await fun[ty](arg); } else { console.warn(`key not found in fun: ${ty.toString()}`); } console.timeEnd('rgt'); })();