remove google tracking UWAA

remove google tracking

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

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