remove google tracking UWAA

remove google tracking

目前為 2017-07-07 提交的版本,檢視 最新版本

  1. // ==UserScript==
  2. // @name remove google tracking UWAA
  3. // @namespace jp.sceneq.rgtuwaaa
  4. // @description remove google tracking
  5. // @version 0.4
  6. // @include https://www.google.*/*
  7. // @grant none
  8. // @run-at document-start
  9. // ==/UserScript==
  10.  
  11. 'use strict';
  12.  
  13. // jump
  14. if(location.pathname === '/' && location.search !== ''){
  15. // localStorage.clear();
  16. location.search = '';
  17. }
  18.  
  19. // matching tracking paramaters
  20. const badParametersNames = [
  21. 'biw'
  22. ,'bih'
  23. ,'ei'
  24. ,'sa'
  25. ,'ved'
  26. ,'source'
  27. ,'prmd'
  28. ,'bvm'
  29. ,'bav'
  30. ,'psi'
  31. ,'stick'
  32. ,'dq'
  33.  
  34. //search
  35. ,'scroll'
  36. ,'vet'
  37. ,'yv'
  38. ,'ijn'
  39. ,'iact'
  40. ,'forward'
  41. ,'ndsp'
  42. ];
  43. const badAttrNamesObj = {
  44. default: ['onmousedown', 'jsaction', 'ping', 'oncontextmenu'],
  45. search: ['onmousedown', 'jsaction', 'ping', 'oncontextmenu'],
  46. vid: ['onmousedown'],
  47. isch: [],
  48. };
  49.  
  50. // From the nodes selected here, delete parameters specified by badParametersNames
  51. const dirtyLinkSelectors = [
  52. // menu
  53. 'a.q.qs',
  54. ];
  55.  
  56. /* Compile */
  57. // The first paramater is probably 'q' so '?' does not consider
  58. const regBadParameters = new RegExp(
  59. '&(?:' + badParametersNames.join('|') + ')=.*?(?=(&|$))'
  60. , 'g');
  61. const dirtyLinkSelector = dirtyLinkSelectors.map(s=>s+":not([href=''])").join(',');
  62.  
  63.  
  64. /*
  65. * Functions
  66. */
  67. /* Return 'q' parameter value */
  68. function extractDirectLink(str){
  69. //(?<=q=)(.*)(?=&)/
  70. const res = /[?&]q(=([^&#]*))/.exec(str);
  71. if(!res || !res[2]) return '';
  72. return decodeURIComponent(res[2]);
  73. }
  74.  
  75. /* Return the current Google search mode */
  76. function getParam(parameter, name){
  77. var results = new RegExp('[\?&]' + name + '=([^&#]*)').exec(parameter);
  78. if (results === null){
  79. return null;
  80. }
  81. else{
  82. return results.pop() || 0;
  83. }
  84. }
  85.  
  86. /* return search mode */
  87. function getMode(){
  88. const parameter = location.search + location.hash;
  89. return getParam(parameter, 'tbm') || "search";
  90. }
  91.  
  92. function sleep(ms) {
  93. return new Promise(resolve => setTimeout(resolve, ms));
  94. }
  95.  
  96. /* Return Promise when declared the variable name specified by argument */
  97. async function waitForDeclare(obj, propertyStr, interval=80) {
  98. return new Promise(async function(resolve, reject){
  99. const propertyNames = propertyStr.split(".");
  100. let currObj = obj;
  101. for(const propertyName of propertyNames){
  102. while(!(propertyName in currObj) || currObj[propertyName] === null){
  103. await sleep(interval);
  104. }
  105. currObj = currObj[propertyName];
  106. }
  107. resolve(currObj);
  108. });
  109. }
  110.  
  111. function rewriteFunctions(prop){
  112. prop.forEach((table) => {
  113. //const targetObject = typeof table[0] === 'function' ? table[0]() : table[0];
  114. Object.defineProperty(table[0] || {}, table[1], {
  115. value: table[2],
  116. writable: false,
  117. });
  118. });
  119. }
  120.  
  121. function load(){
  122. console.time("LOAD");
  123.  
  124. /* Overwrite disturbing functions */
  125. const yesman = function(){return true};
  126. const tired = function(){};
  127. rewriteFunctions([
  128. [window, "rwt", yesman],
  129. [window.gbar_, 'Rm', yesman],
  130. [google, 'rll', yesman],
  131. [google, 'log', yesman],
  132. [google, 'logUrl', tired],
  133. [google, 'getEI', yesman],
  134. [google, 'getLEI', yesman],
  135. [google, 'ctpacw', yesman],
  136. [google, 'csiReport', yesman],
  137. [google, 'report', yesman],
  138. [google, 'aft', yesman],
  139. ]);
  140.  
  141. // Do not send gen_204 flag
  142. for(const node of document.querySelectorAll(".csi")){
  143. node.parentNode.removeChild(node);
  144. }
  145.  
  146. /*
  147. * Variables
  148. */
  149. // Whether to use AJAX
  150. const legacy = document.getElementById("cst") === null;
  151.  
  152. /* Nodes */
  153. const nodeMain = window.main;
  154. const nodeCnt = window.cnt;
  155. const root = (()=>{
  156. if(legacy){
  157. return nodeCnt || nodeMain || window.document;
  158. } else {
  159. return nodeMain || nodeCnt || window.document;
  160. }
  161. })();
  162.  
  163. // Define selector function
  164. const $ = root.querySelector.bind(root);
  165. const $$ = (sel) => Array.prototype.slice.call(root.querySelectorAll.call(root, [sel]));
  166.  
  167. // Selector pointing to anchors to purify
  168. const dirtySelector = (()=>{
  169. if(root === window.document){
  170. return "body a";
  171. } else if(legacy){
  172. return "#cnt a";
  173. } else {
  174. return "#rcnt a";
  175. }
  176. })();
  177.  
  178. /*
  179. * Functions
  180. */
  181. function removeTracking(nodes=[]){
  182. console.time("removeTracking");
  183. const mode = getMode();
  184. const badAttrNames = badAttrNamesObj[mode] ?
  185. badAttrNamesObj[mode] : badAttrNamesObj["default"];
  186.  
  187. // search result
  188. for(const dirtyAnchor of $$(dirtySelector)){
  189. // remove attributes
  190. badAttrNames.map((s)=>{ dirtyAnchor.removeAttribute(s); });
  191.  
  192. // hide referrer
  193. dirtyAnchor.rel = 'noreferrer';
  194.  
  195. // remove google redirect link(legacy)
  196. if (dirtyAnchor.hasAttribute('href') && dirtyAnchor.getAttribute('href').startsWith('/url?')){
  197. dirtyAnchor.href = extractDirectLink(dirtyAnchor.href);
  198. }
  199. dirtyAnchor.href = dirtyAnchor.href.replace(regBadParameters, '');
  200. }
  201. for(const dirtyLink of $$(dirtyLinkSelector)){
  202. dirtyLink.href = dirtyLink.href.replace(regBadParameters, '');
  203. }
  204.  
  205. switch(mode){
  206. case "shop":
  207. // Overwrite links(desktop version only)
  208. //Object.values(google.pmc.smpo.r).map(s=>{return {title:s[14][0],link:s[28][8]}})
  209. if(legacy) break;
  210. waitForDeclare(google, "pmc.spop.r").then((shopObj) => {
  211. const shopElements = $$(".pstl");
  212. const shopLinks = Object.values(shopObj).map(a=>a[34][6]);
  213.  
  214. if(shopElements.length !== shopLinks.length) {
  215. console.warn("length does not match", shopElements.length, shopLinks.length);
  216. return;
  217. }
  218.  
  219. const zip = rows=>rows[0].map((_,c)=>rows.map(row=>row[c]))
  220. for(const detail of zip([shopElements, shopLinks])){
  221. detail[0].href = detail[1];
  222. }
  223. console.log("Links Rewrited");
  224. });
  225. break;
  226. default:
  227. break;
  228. }
  229. console.timeEnd("removeTracking");
  230. }
  231.  
  232. function startObserve(targetElement, op, func, conf={childList:true}){
  233. //console.log("Operation", op , "Register To", targetElement)
  234. new MutationObserver((mutations, observer) => {
  235. let nodes = Array.prototype.concat.apply([],
  236. mutations.map(s => Array.prototype.slice.call(s.addedNodes))
  237. ).filter(n => n.nodeName === 'DIV');
  238.  
  239. //console.log("Nodes Captured By", op, nodes);
  240.  
  241. switch(op){
  242. case "IMAGE":
  243. // Exclude DOM inserted at click
  244. nodes = nodes.filter(n => n.id !== "irc_pbg");
  245. break;
  246. case "HDTBLOADED":
  247. nodes = nodes.filter(n => n.className === "hdtb-mn-cont");
  248. break;
  249. case "HDTBCHANGE":
  250. nodes = nodes.filter(n => n.id === "cnt")
  251. break;
  252. case "PAGECHANGE":
  253. nodes = nodes.filter(n => n.dataset && n.dataset.ved !== undefined);
  254. break;
  255. default:
  256. break;
  257. }
  258.  
  259. if(nodes.length >= 1){
  260. //console.log("Operation", op , "Fired", nodes[0])
  261. func();
  262. if(["HDTBLOADED"].includes(op)){
  263. observer.disconnect();
  264. }
  265. }
  266. }).observe(targetElement, conf);
  267. }
  268.  
  269. function pageInit(){
  270. removeTracking();
  271. startObserve($("#search"), "PAGECHANGE", removeTracking);
  272. }
  273.  
  274. if(legacy){
  275. removeTracking();
  276. console.timeEnd("LOAD");
  277. console.warn("legacy mode");
  278. return;
  279. }
  280.  
  281. const initMode = getMode();
  282. if(initMode === "isch"){ // image search
  283. removeTracking();
  284. startObserve($("#rg_s"), "IMAGE", removeTracking);
  285. return;
  286. }
  287.  
  288. // Wait for .hdtb-mn-cont appears in the first page access
  289. startObserve(nodeMain, "HDTBLOADED", ()=>{
  290. pageInit();
  291.  
  292. // Wait for #cnt inserted. In HDTB switching, since .hdtb-mn-cont does not appear
  293. startObserve(nodeMain, "HDTBCHANGE", pageInit);
  294. }, {childList:true, subtree:true});
  295.  
  296. console.timeEnd("LOAD");
  297. }
  298.  
  299. (function init(){
  300. // hook XHR
  301. const origOpen = XMLHttpRequest.prototype.open;
  302. window.XMLHttpRequest.prototype.open = function(act, path) {
  303. // take over the parameters ex:num=20
  304. // path += location.search.replace(/./, '');
  305. origOpen.apply(this, [act, path.replace(regBadParameters, '')]);
  306. };
  307.  
  308. // do not send referrer
  309. const noreferrerMeta = document.createElement("meta");
  310. noreferrerMeta.setAttribute("name", "referrer");
  311. noreferrerMeta.setAttribute("content", "no-referrer");
  312. document.querySelector("head").appendChild(noreferrerMeta);
  313. })();
  314.  
  315. window.addEventListener('DOMContentLoaded', load);
  316.  
  317. // for older browser
  318. if(document.getElementById("universal") !== null){
  319. load();
  320. }