remove google tracking UWAA

remove google tracking

当前为 2017-07-21 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @namespace jp.sceneq.rgtuwaaa
  3.  
  4. // @name remove google tracking UWAA
  5.  
  6. // @description remove google tracking
  7. // @description:ja Google追跡UWAAを取り除きなさい
  8.  
  9. // @homepageURL https://github.com/sceneq/RemoveGoogleTracking
  10.  
  11. // @version 0.8
  12. // @include https://www.google.*/*
  13. // @grant none
  14. // @run-at document-start
  15.  
  16. // @author sceneq
  17. // @license MIT
  18. // ==/UserScript==
  19.  
  20. 'use strict';
  21. console.time('init');
  22.  
  23. try {
  24. window = window.unsafeWindow || window;
  25. } catch (e) {}
  26.  
  27. const yesman = function() {
  28. return true;
  29. };
  30. const tired = function() {};
  31.  
  32. /* Return the current Google search mode */
  33. function getParam(parameter, name) {
  34. var results = new RegExp('[?&]' + name + '=([^&#]*)').exec(parameter);
  35. if (results === null) {
  36. return null;
  37. } else {
  38. return results.pop() || 0;
  39. }
  40. }
  41.  
  42. /* return search mode */
  43. function getSearchMode() {
  44. const parameter = location.search + location.hash;
  45. return getParam(parameter, 'tbm') || 'search';
  46. }
  47.  
  48. // matching tracking paramaters
  49. const badParametersNamesObj = {
  50. base: [
  51. 'biw', // offsetWidth
  52. 'bih', // offsetHeight
  53. 'ei',
  54. 'sa',
  55. 'ved',
  56. 'source',
  57. 'prmd',
  58. 'bvm',
  59. 'bav',
  60. 'psi',
  61. 'stick',
  62. 'dq',
  63. 'ech',
  64. 'gs_gbg',
  65. 'gs_rn',
  66. 'cp'
  67. ],
  68. // image search
  69. isch: [
  70. 'scroll',
  71. 'vet',
  72. 'yv',
  73. 'ijn',
  74. 'iact',
  75. 'forward',
  76. 'ndsp',
  77. 'csi',
  78. 'tbnid'
  79. //'docid', // related images
  80. //'imgdii', // related images
  81. ],
  82. searchform: [
  83. // search form
  84. 'pbx',
  85. 'dpr',
  86. 'pf',
  87. 'gs_rn',
  88. 'gs_mss',
  89. 'pq',
  90. 'cp',
  91. 'oq',
  92. 'sclient',
  93. 'gs_l',
  94. 'aqs'
  95. //'gs_ri', // suggestions
  96. //'gs_id', // suggestions
  97. //'xhr', // suggestions at image search
  98. //'tch', // js flag?
  99. ],
  100. // Google maps
  101. maps: ['psi']
  102. };
  103.  
  104. // search option on first access
  105. const GoogleOp = {
  106. maps: Symbol(),
  107. isch: Symbol(),
  108. search: Symbol(),
  109. unknown: Symbol()
  110. };
  111.  
  112. const op = (() => {
  113. if (location.pathname.startsWith('/maps')) {
  114. return GoogleOp.maps;
  115. }
  116. if (getSearchMode() === 'isch') {
  117. return GoogleOp.isch;
  118. } else {
  119. return GoogleOp.search;
  120. }
  121. })();
  122.  
  123. const badParametersNames = (() => {
  124. switch (op) {
  125. case GoogleOp.maps:
  126. return badParametersNamesObj.maps;
  127. case GoogleOp.isch:
  128. return badParametersNamesObj.base.concat(
  129. badParametersNamesObj.searchform,
  130. badParametersNamesObj.isch
  131. );
  132. case GoogleOp.search:
  133. return badParametersNamesObj.base.concat(
  134. badParametersNamesObj.searchform
  135. );
  136. }
  137. })();
  138.  
  139. const badAttrNamesObj = {
  140. default: ['onmousedown', 'ping', 'oncontextmenu'],
  141. search: ['onmousedown', 'ping', 'oncontextmenu'],
  142. vid: ['onmousedown'],
  143. nws: ['onmousedown'],
  144. bks: [],
  145. isch: [],
  146. shop: []
  147. };
  148.  
  149. // From the nodes selected here, delete parameters specified by badParametersNames
  150. const dirtyLinkSelectors = [
  151. // menu
  152. 'a.q.qs',
  153.  
  154. // doodle
  155. 'a.doodle',
  156.  
  157. // Upper left menu
  158. '.gb_Z > a',
  159.  
  160. // Logo
  161. 'a#logo',
  162. 'div#qslc > a',
  163. 'header#hdr > div > a',
  164.  
  165. // search button?
  166. 'form#sf > a',
  167.  
  168. /// imagesearch
  169. // colors
  170. 'div#sc-block > div > a',
  171. // size
  172. 'a.hdtb-mitem'
  173. ];
  174.  
  175. const badPaths = ['imgevent'];
  176.  
  177. /* Compile */
  178. // The first paramater is probably 'q' so '?' does not consider
  179. const regBadParameters = new RegExp(
  180. '&(?:' + badParametersNames.join('|') + ')=.*?(?=(&|$))',
  181. 'g'
  182. );
  183. const regBadPaths = new RegExp('^/(?:' + badPaths.join('|') + ')');
  184. const dirtyLinkSelector = dirtyLinkSelectors
  185. .map(s => s + ":not([href=''])")
  186. .join(',');
  187.  
  188. /* Return parameter value */
  189. function extractDirectLink(str, param) {
  190. //(?<=q=)(.*)(?=&)/
  191. const res = new RegExp(`[?&]${param}(=([^&#]*))`).exec(str);
  192. if (!res || !res[2]) return '';
  193. return decodeURIComponent(res[2]);
  194. }
  195.  
  196. function sleep(ms) {
  197. return new Promise(resolve => setTimeout(resolve, ms));
  198. }
  199.  
  200. /* Return Promise when declared the variable name specified by argument */
  201. async function onDeclare(obj, propertyStr, interval = 80) {
  202. return new Promise(async function(resolve, reject) {
  203. const propertyNames = propertyStr.split('.');
  204. let currObj = obj;
  205. for (const propertyName of propertyNames) {
  206. while (!(propertyName in currObj) || currObj[propertyName] === null) {
  207. await sleep(interval);
  208. }
  209. currObj = currObj[propertyName];
  210. }
  211. resolve(currObj);
  212. });
  213. }
  214.  
  215. function rewriteProperties(prop) {
  216. for (const table of prop) {
  217. //const targetObject = typeof table[0] === 'function' ? table[0]() : table[0];
  218. Object.defineProperty(table[0] || {}, table[1], {
  219. value: table[2],
  220. writable: false
  221. });
  222. }
  223. }
  224.  
  225. function load() {
  226. console.time('LOAD');
  227.  
  228. /* Overwrite disturbing functions */
  229. rewriteProperties([[window, 'rwt', yesman], [window.gbar_, 'Rm', yesman]]);
  230.  
  231. // do not send referrer
  232. const noreferrerMeta = document.createElement('meta');
  233. noreferrerMeta.setAttribute('name', 'referrer');
  234. noreferrerMeta.setAttribute('content', 'no-referrer');
  235. document.querySelector('head').appendChild(noreferrerMeta);
  236.  
  237. /*
  238. * Variables
  239. */
  240. // Whether to use AJAX
  241. const legacy = document.getElementById('cst') === null;
  242.  
  243. /* Nodes */
  244. const nodeMain = document.getElementById('main');
  245. const nodeCnt = document.getElementById('cnt');
  246. const root = (() => {
  247. if (legacy) {
  248. return nodeCnt || nodeMain || window.document;
  249. } else {
  250. return nodeMain; // || nodeCnt;
  251. }
  252. })();
  253.  
  254. // Flag indicating whether the hard tab is loaded on 'DOMContentLoaded'
  255. const lazy_hdtb = !legacy || root === nodeCnt;
  256.  
  257. // Define selector function
  258. const $ = root.querySelector.bind(root);
  259. const $$ = sel =>
  260. Array.prototype.slice.call(root.querySelectorAll.call(root, [sel]));
  261.  
  262. // Selector pointing to anchors to purify
  263. const dirtySelector = (() => {
  264. if (root === window.document) {
  265. return 'body a';
  266. } else if (legacy) {
  267. return `#${root.id} a`;
  268. } else {
  269. return '#rcnt a';
  270. }
  271. })();
  272.  
  273. // List of parameters to keep
  274. const saveParamNames = ['q', 'hl', 'num', 'tbm'];
  275. const obstacleInputsSelector =
  276. 'form[id*=sf] input' +
  277. saveParamNames.map(s => ':not([name=' + s + '])').join('');
  278.  
  279. /*
  280. * Functions
  281. */
  282. function removeFormInputs() {
  283. for (const node of document.querySelectorAll(obstacleInputsSelector)) {
  284. node.parentNode.removeChild(node);
  285. }
  286. }
  287.  
  288. function removeBadParameters() {
  289. for (const dirtyLink of document.querySelectorAll(dirtyLinkSelector)) {
  290. dirtyLink.href = dirtyLink.href.replace(regBadParameters, '');
  291. }
  292. }
  293.  
  294. const specProcesses = {
  295. shop: function() {
  296. // Overwrite links(desktop version only)
  297. //Object.values(google.pmc.smpo.r).map(s=>{return {title:s[14][0],link:s[28][8]}})
  298. if (legacy) return;
  299. onDeclare(google, 'pmc.spop.r').then(shopObj => {
  300. const shopElements = $$('#rso a.pstl');
  301. const shopImgElements = $$('#rso a.psliimg');
  302. const shopArrays = Object.values(shopObj);
  303. const shopLinks = shopArrays.map(a => a[34][6]);
  304. const zip = rows => rows[0].map((_, c) => rows.map(row => row[c]));
  305.  
  306. if (shopElements.length !== shopLinks.length) {
  307. console.warn(
  308. 'length does not match',
  309. shopElements.length,
  310. shopLinks.length
  311. );
  312. return;
  313. }
  314.  
  315. for (const detail of zip([
  316. shopElements,
  317. shopLinks,
  318. shopImgElements,
  319. shopArrays
  320. ])) {
  321. // for (shopElements, shopLinks, shopImgElements, shopArrays) in zip(~)
  322. const shopElement = detail[0];
  323. const shopLink = detail[1];
  324. const shopImgElement = detail[2];
  325. const shopArray = detail[3];
  326.  
  327. // Overwrite link
  328. shopElement.href = shopImgElement.href = shopLink;
  329.  
  330. // Disable click actions
  331. //detail[0].__jsaction = null;
  332. //detail[0].removeAttribute("jsaction");
  333.  
  334. // Overwrite variables used when link clicked
  335. try {
  336. shopArray[3][0][1] = shopLink;
  337. shopArray[14][1] = shopLink;
  338. shopArray[89][16] = shopLink;
  339. shopArray[89][18][0] = shopLink;
  340. shopArray[85][3] = shopLink;
  341. } catch (e) {}
  342. }
  343. console.log('Links Rewrited');
  344. });
  345. }
  346. };
  347.  
  348. function removeTracking() {
  349. console.time('removeTracking');
  350. const searchMode = getSearchMode();
  351. const badAttrNames = badAttrNamesObj[searchMode]
  352. ? badAttrNamesObj[searchMode]
  353. : badAttrNamesObj['default'];
  354. const directLinkParamName = 'q';
  355.  
  356. // search result
  357. for (const searchResult of $$(dirtySelector)) {
  358. // remove attributes
  359. for (const badAttrName of badAttrNames) {
  360. searchResult.removeAttribute(badAttrName);
  361. }
  362.  
  363. // hide referrer
  364. searchResult.rel = 'noreferrer';
  365.  
  366. // remove google redirect link(legacy)
  367. if (
  368. searchResult.hasAttribute('href') &&
  369. searchResult.getAttribute('href').startsWith('/url?')
  370. ) {
  371. searchResult.href = extractDirectLink(
  372. searchResult.href,
  373. directLinkParamName
  374. );
  375. }
  376. searchResult.href = searchResult.href.replace(regBadParameters, '');
  377. }
  378.  
  379. removeBadParameters();
  380.  
  381. searchMode in specProcesses && specProcesses[searchMode]();
  382.  
  383. console.timeEnd('removeTracking');
  384. }
  385.  
  386. const ObserveOp = {
  387. LOADED: {
  388. FORM: ['INPUT', 'name', /^oq$/],
  389. IMAGE: ['DIV', 'class', /.*irc_bg.*/],
  390. HDTB: ['DIV', 'class', /^hdtb-mn-cont$/]
  391. },
  392. UPDATE: {
  393. HDTB: ['DIV', 'class', /^hdtb-mn-cont$/]
  394. },
  395. CHANGE: {
  396. HDTB: ['DIV', 'id', /^cnt$/],
  397. PAGE: ['DIV', 'data-set', /.+/]
  398. }
  399. };
  400.  
  401. const ObserveUntilLoadedList = Object.values(ObserveOp.LOADED);
  402.  
  403. function startObserve(targetElement, op, func, conf = { childList: true }) {
  404. if (targetElement === null) {
  405. console.warn('targetElement is null', op, func);
  406. return;
  407. }
  408. //console.log(op, 'Register To', targetElement);
  409. const targetElementName = op[0];
  410. const targetAttrName = op[1];
  411. const targetAttrValueReg = op[2];
  412. const filterFunc = n => {
  413. return (
  414. n.nodeName === targetElementName &&
  415. targetAttrValueReg.test(n.getAttribute(targetAttrName))
  416. );
  417. };
  418.  
  419. // if targetElement already appeared
  420. if (
  421. ObserveUntilLoadedList.includes(op) &&
  422. Array.prototype.slice
  423. .call(targetElement.querySelectorAll(targetElementName))
  424. .filter(filterFunc).length >= 1
  425. ) {
  426. //console.log(op, 'Register To', targetElement);
  427. func();
  428. return;
  429. }
  430.  
  431. new MutationObserver((mutations, observer) => {
  432. const nodes = Array.prototype.concat
  433. .apply([], mutations.map(s => Array.prototype.slice.call(s.addedNodes)))
  434. //.map((s)=>{console.log(s);return s})
  435. .filter(filterFunc);
  436.  
  437. if (nodes.length >= 1) {
  438. //console.log(targetElement, op, 'Fired', nodes[0], func);
  439. func();
  440. if (ObserveUntilLoadedList.includes(op)) {
  441. observer.disconnect();
  442. }
  443. //return startObserve;
  444. }
  445. }).observe(targetElement, conf);
  446. }
  447.  
  448. function pageInit() {
  449. removeTracking();
  450. startObserve($('#search'), ObserveOp.CHANGE.PAGE, removeTracking);
  451. }
  452.  
  453. const confDeepObserve = { childList: true, subtree: true };
  454.  
  455. // Wait for .hdtb-mn-cont appears in the first page access
  456. if (lazy_hdtb && !legacy) {
  457. startObserve(
  458. root,
  459. ObserveOp.LOADED.HDTB,
  460. () => {
  461. // hdtb loaded
  462. switch (getSearchMode()) {
  463. case 'isch': // Image Search
  464. removeTracking();
  465.  
  466. // Remove unnecessary script from buttons
  467. startObserve($('#isr_mc'), ObserveOp.LOADED.IMAGE, () => {
  468. for (const node of $$(
  469. 'a.irc_tas, a.irc_mil, a.irc_hol, a.irc_but'
  470. )) {
  471. node.__jsaction = null;
  472. node.removeAttribute('jsaction');
  473. }
  474. });
  475.  
  476. // on search options updated
  477. startObserve(
  478. $('#top_nav'),
  479. ObserveOp.UPDATE.HDTB,
  480. removeBadParameters,
  481. confDeepObserve
  482. );
  483. break;
  484. default:
  485. pageInit();
  486. // Wait for #cnt inserted. In HDTB switching, since .hdtb-mn-cont does not appear
  487. startObserve(root, ObserveOp.CHANGE.HDTB, pageInit);
  488. break;
  489. }
  490. removeFormInputs();
  491. },
  492. confDeepObserve
  493. );
  494. } else if (legacy) {
  495. removeTracking();
  496.  
  497. // Remove unnecessary input
  498. startObserve(
  499. document.querySelector('form'),
  500. ObserveOp.LOADED.FORM,
  501. removeFormInputs
  502. );
  503.  
  504. // Remove unnecessary parameters from hdtb
  505. const hdtbRoot = $('#hdtbMenus');
  506. if (hdtbRoot) {
  507. startObserve(hdtbRoot, ObserveOp.LOADED.HDTB, removeBadParameters);
  508. }
  509.  
  510. if (document.getElementById('hdr')) {
  511. const stopAddParams = s => {
  512. const src = s.srcElement;
  513. if (src.nodeName === 'A' && src.href.match(/\/search.*[?&]tbm=isch/)) {
  514. s.stopPropagation();
  515. }
  516. };
  517. document.addEventListener('click', stopAddParams, true);
  518. document.addEventListener('touchStart', stopAddParams, true);
  519. }
  520.  
  521. // Remove unnecessary parameters from 'option'
  522. for (const option of document.querySelectorAll('#mor > option')) {
  523. option.value = option.value.replace(regBadParameters, '');
  524. }
  525.  
  526. console.warn('legacy mode');
  527. console.timeEnd('LOAD');
  528. return;
  529. }
  530.  
  531. console.timeEnd('LOAD');
  532. }
  533.  
  534. function init() {
  535. onDeclare(window, 'google', 20).then(() => {
  536. rewriteProperties([
  537. [google, 'log', yesman],
  538. [google, 'rll', yesman],
  539. [google, 'logUrl', tired],
  540. [google, 'getEI', yesman],
  541. [google, 'getLEI', yesman],
  542. [google, 'ctpacw', yesman],
  543. [google, 'csiReport', yesman],
  544. [google, 'report', yesman],
  545. [google, 'aft', yesman],
  546. [google, 'kEI', '0']
  547. ]);
  548. });
  549.  
  550. // Reject Request by img tag
  551. //maps:log204
  552. const regBadImageSrc = /\/(?:(?:gen(?:erate)?|client|fp)_|log)204|(?:metric|csi)\.gstatic\./;
  553. Object.defineProperty(window.Image.prototype, 'src', {
  554. set: function(url) {
  555. if (!regBadImageSrc.test(url)) {
  556. this.setAttribute('src', url);
  557. }
  558. }
  559. });
  560.  
  561. // Reject unknown parameters by script tag
  562. Object.defineProperty(window.HTMLScriptElement.prototype, 'src', {
  563. set: function(url) {
  564. this.setAttribute('src', url.replace(regBadParameters, ''));
  565. }
  566. });
  567.  
  568. // hook XHR
  569. const origOpen = XMLHttpRequest.prototype.open;
  570. window.XMLHttpRequest.prototype.open = function(act, path) {
  571. if (!regBadPaths.test(path)) {
  572. origOpen.apply(this, [act, path.replace(regBadParameters, '')]);
  573. }
  574. };
  575. }
  576.  
  577. /* Execute */
  578.  
  579. init();
  580. console.timeEnd('init');
  581.  
  582. if (
  583. location.pathname !== '/' &&
  584. location.pathname !== '/search' &&
  585. document.querySelector('html').getAttribute('itemtype') !==
  586. 'http://schema.org/SearchResultsPage'
  587. ) {
  588. console.warn('');
  589. return;
  590. }
  591.  
  592. window.addEventListener('DOMContentLoaded', load);
  593.  
  594. // for older browser
  595. if (document.getElementById('universal') !== null) {
  596. load();
  597. }