remove google tracking UWAA

remove google tracking

当前为 2019-01-11 提交的版本,查看 最新版本

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