remove google tracking UWAA

remove google tracking

当前为 2020-04-16 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @namespace jp.sceneq.rgtuwaaa
  3. // @name remove google tracking UWAA
  4. // @description remove google tracking
  5. // @homepageURL https://github.com/sceneq/RemoveGoogleTracking
  6. // @version 0.20
  7. // @include https://www.google.*/*
  8. // @grant none
  9. // @run-at document-body
  10. // @author sceneq
  11. // @license MIT
  12. // ==/UserScript==
  13.  
  14. const yes = () => true;
  15. const doNothing = () => {};
  16. const $ = (s, n = document) => n.querySelector(s);
  17. const $$ = (s, n = document) => [...n.querySelectorAll(s)];
  18. const sleep = millis => new Promise(resolve => setTimeout(resolve, millis));
  19. const zip = rows => rows[0].map((_, c) => rows.map(row => row[c]));
  20.  
  21. const rewriteProperties = props => {
  22. for (const p of props) {
  23. Object.defineProperty(p[0] || {}, p[1], {
  24. value: p[2],
  25. writable: false,
  26. });
  27. }
  28. };
  29.  
  30. const waitUntilDeclare = (obj, property, { interval = 100 }) => {
  31. console.debug('waitUntilDeclare', obj.toString(), property);
  32. return new Promise(async (resolve, reject) => {
  33. const propertyNames = property.split('.');
  34. let currObj = obj;
  35. for (const propertyName of propertyNames) {
  36. while (!(propertyName in currObj) || currObj[propertyName] === null) {
  37. await sleep(interval);
  38. }
  39. currObj = currObj[propertyName];
  40. }
  41. console.debug('waitUntilDeclare', obj.toString(), property, 'Done');
  42. resolve(currObj);
  43. });
  44. };
  45.  
  46. const waitUntilNode = (sel, { interval = 100 }) => {
  47. console.debug('waitUntilNode', sel);
  48. return new Promise(async (resolve, reject) => {
  49. let d = null;
  50. while (d === null) {
  51. d = $(sel);
  52. await sleep(interval);
  53. }
  54. console.debug('waitUntilNode Done', sel);
  55. resolve(d);
  56. });
  57. };
  58.  
  59. const untrackBuilder = arg => {
  60. const r = arg.badParamsRegex;
  61. return a => {
  62. const href = a.getAttribute('href');
  63. if (href === null) return;
  64. if (href.startsWith('/url?')) {
  65. a.href = new URLSearchParams(href.slice(5)).get('q');
  66. } else {
  67. a.removeAttribute('ping');
  68. a.href = a.href.replace(r, '');
  69. }
  70. };
  71. };
  72.  
  73. const Types = {
  74. search: Symbol('search'),
  75. isch: Symbol('image'),
  76. shop: Symbol('shop'),
  77. nws: Symbol('news'),
  78. vid: Symbol('video'),
  79. bks: Symbol('book'),
  80. maps: Symbol('maps'),
  81. fin: Symbol('finance'),
  82. toppage: Symbol('toppage'),
  83. // flights: Symbol('flights'),
  84. };
  85.  
  86. const tbmToType = tbm => {
  87. if (tbm === null) {
  88. return Types.search;
  89. }
  90. const t = {
  91. isch: Types.isch,
  92. shop: Types.shop,
  93. nws: Types.nws,
  94. vid: Types.vid,
  95. bks: Types.bks,
  96. fin: Types.fin,
  97. }[tbm];
  98. if (t === undefined) {
  99. return null;
  100. } else {
  101. return t;
  102. }
  103. };
  104.  
  105. const BadParamsBase = [
  106. 'biw', // offsetWidth
  107. 'bih', // offsetHeight
  108. 'ei',
  109. 'sa',
  110. 'ved',
  111. 'source',
  112. 'prds',
  113. 'bvm',
  114. 'bav',
  115. 'psi',
  116. 'stick',
  117. 'dq',
  118. 'ech',
  119. 'gs_gbg',
  120. 'gs_rn',
  121. 'cp',
  122. 'ictx',
  123. 'cshid',
  124. 'gs_lcp',
  125. ];
  126.  
  127. const BadParams = (() => {
  128. const o = {};
  129. o[Types.search] = [
  130. 'pbx',
  131. 'dpr',
  132. 'pf',
  133. 'gs_rn',
  134. 'gs_mss',
  135. 'pq',
  136. 'cp',
  137. 'oq',
  138. 'sclient',
  139. 'gs_l',
  140. 'aqs',
  141. ];
  142. o[Types.isch] = [
  143. 'scroll',
  144. 'vet',
  145. 'yv',
  146. 'iact',
  147. 'forward',
  148. 'ndsp',
  149. 'csi',
  150. 'tbnid',
  151. 'sclient',
  152. 'oq',
  153. ];
  154. o[Types.shop] = ['oq'];
  155. o[Types.nws] = ['oq'];
  156. o[Types.vid] = ['oq'];
  157. o[Types.bks] = ['oq'];
  158. o[Types.fin] = ['oq'];
  159. o[Types.toppage] = ['oq', 'sclient', 'uact'];
  160. o[Types.maps] = ['psi'];
  161. return o;
  162. })();
  163.  
  164. const searchFormUriuri = async arg => {
  165. let form = null;
  166. if (arg.pageType.mobileOld) {
  167. form = $('#sf');
  168. } else if (arg.pageType.ty === Types.isch) {
  169. form = await waitUntilDeclare(window, 'sf', { interval: 30 });
  170. } else {
  171. form = await waitUntilDeclare(window, 'tsf', { interval: 30 });
  172. }
  173.  
  174. if (form === null) {
  175. console.warn('form === null');
  176. return;
  177. }
  178.  
  179. for (const i of form) {
  180. if (i.tagName !== 'INPUT') continue;
  181. if (arg.badParams.includes(i.name)) {
  182. i.parentElement.removeChild(i);
  183. }
  184. }
  185. const orig = form.appendChild.bind(form);
  186. form.appendChild = e => {
  187. if (!arg.badParams.includes(e.name)) {
  188. orig(e);
  189. }
  190. };
  191. };
  192.  
  193. const untrackAnchors = (untrack, arg) => {
  194. let property = null;
  195. if (arg.pageType.mobile) {
  196. property = 'topstuff';
  197. } else if (arg.pageType.mobileOld) {
  198. property = 'main'; // 'rmenu';
  199. } else if (arg.pageType.ty === Types.search) {
  200. property = 'li_';
  201. } else if (arg.pageType.ty === Types.bks) {
  202. property = 'lr_';
  203. } else if (arg.pageType.ty === Types.vid) {
  204. property = 'ow6';
  205. } else if (arg.pageType.ty === Types.nws) {
  206. property = 'ow8';
  207. } else if (arg.pageType.ty === Types.isch) {
  208. property = 'i4';
  209. } else {
  210. property = 'search';
  211. }
  212.  
  213. return waitUntilDeclare(window, property, { interval: 20 }).then(_ => {
  214. for (const a of $$('a')) {
  215. untrack(a);
  216. }
  217. });
  218. };
  219.  
  220. const gcommon = async arg => {
  221. const untrack = untrackBuilder(arg);
  222. const p1 = waitUntilDeclare(window, 'google', { interval: 30 }).then(
  223. google => {
  224. rewriteProperties([
  225. [google, 'log', yes],
  226. [google, 'logUrl', doNothing],
  227. [google, 'getLEI', yes],
  228. [google, 'ctpacw', yes],
  229. [google, 'csiReport', yes],
  230. [google, 'report', yes],
  231. [google, 'aft', yes],
  232. //[google, 'kEI', '0'],
  233. //[google, 'getEI', yes], or ()=>"0"
  234. ]);
  235. }
  236. );
  237. rewriteProperties([
  238. [window, 'rwt', doNothing],
  239. [window.gbar_, 'Rm', doNothing],
  240. ]);
  241.  
  242. const p2 = untrackAnchors(untrack, arg);
  243. const p3 = searchFormUriuri(arg);
  244. await Promise.all([p1, p2, p3]);
  245.  
  246. if (arg.pageType.mobile) {
  247. const sel =
  248. arg.pageType.ty === Types.search
  249. ? '#bres + h1 + div > div + div'
  250. : '#bres + div > div + div';
  251. new MutationObserver(mutations => {
  252. const nodes = [];
  253. for (const m of mutations) {
  254. nodes.push(...m.addedNodes);
  255. }
  256. for (const n of nodes) {
  257. new MutationObserver((_, obs) => {
  258. console.debug('untrack', n);
  259. for (const a of $$('a', n)) {
  260. untrack(a);
  261. }
  262. obs.disconnect();
  263. }).observe(n, { childList: true });
  264. }
  265. }).observe($(sel), {
  266. childList: true,
  267. });
  268. }
  269. };
  270.  
  271. const fun = {};
  272.  
  273. // TODO mobile, mobileOld
  274. fun[Types.toppage] = searchFormUriuri;
  275.  
  276. fun[Types.search] = gcommon;
  277. fun[Types.vid] = gcommon;
  278. fun[Types.nws] = gcommon;
  279. fun[Types.bks] = gcommon;
  280. fun[Types.fin] = gcommon;
  281.  
  282. fun[Types.isch] = async arg => {
  283. // TODO desktop, mobile, mobileOld
  284. const untrack = untrackBuilder(arg);
  285. const p1 = untrackAnchors(untrack, arg);
  286. const p2 = searchFormUriuri(arg);
  287. await Promise.all([p1, p2]);
  288. };
  289.  
  290. fun[Types.shop] = async arg => {
  291. // TODO mobile, mobileOld
  292. const untrack = untrackBuilder(arg);
  293. const p1 = untrackAnchors(untrack, arg);
  294. const p2 = searchFormUriuri(arg);
  295. const p3 = waitUntilDeclare(window, 'google.pmc.spop.r', {
  296. interval: 30,
  297. }).then(shopObj => {
  298. // Rewrite to original link
  299. const tmp = $$("div[class$='__content'] a[jsaction='spop.c']");
  300. const [anchors, thumbs] = [0, 1].map(m =>
  301. tmp.filter((_, i) => i % 2 === m)
  302. );
  303. const shops = Object.values(shopObj);
  304. const links = shops.map(a => a[34][6]);
  305. if (anchors.length === links.length) {
  306. for (const [anchor, link, thumb, shop] of zip([
  307. anchors,
  308. links,
  309. thumbs,
  310. shops,
  311. ])) {
  312. anchors.href = thumb.href = link;
  313. shop[3][0][1] = link;
  314. shop[14][1] = link;
  315. shop[89][16] = link;
  316. shop[89][18][0] = link;
  317. if (shop[85] !== null) {
  318. shop[85][3] = link;
  319. }
  320. }
  321. } else {
  322. console.warn('length does not match', anchors.length, links.length);
  323. }
  324. });
  325. await Promise.all([p1, p2, p3]);
  326. };
  327.  
  328. fun[Types.maps] = async arg => {
  329. const untrack = a => {
  330. a.addEventListener('click', e => e.stopPropagation());
  331. for (const n of [...a.attributes]
  332. .map(at => at.name)
  333. .filter(n => ['href', 'class'].indexOf(n) === -1)) {
  334. a.removeAttribute(n);
  335. }
  336. };
  337. const main = await waitUntilNode('div[role=main]', { interval: 30 });
  338. new MutationObserver(mutations => {
  339. console.log(mutations);
  340. }).observe(main.children[1].children[0], {
  341. childList: true,
  342. });
  343. };
  344.  
  345. (async () => {
  346. 'use strict';
  347. console.debug('rgt: init');
  348. console.time('rgt');
  349.  
  350. const ty = (() => {
  351. if (location.pathname.startsWith('/maps')) {
  352. return Types.maps;
  353. }
  354. if (location.pathname === '/' || location.pathname === '/webhp') {
  355. return Types.toppage;
  356. }
  357. const tbm = new URLSearchParams(location.search).get('tbm');
  358. if (location.pathname === '/search') {
  359. return tbmToType(tbm);
  360. }
  361. return null;
  362. })();
  363.  
  364. if (ty === null) {
  365. console.debug('ty === null');
  366. return;
  367. }
  368.  
  369. const badParams = (() => {
  370. return [...BadParamsBase, ...BadParams[ty]];
  371. })();
  372. const badParamsRegex = new RegExp(
  373. '&(?:' + badParams.join('|') + ')=.*?(?=(&|$))',
  374. 'g'
  375. );
  376.  
  377. const badImageSrcRegex = /\/(?:(?:gen(?:erate)?|client|fp)_|log)204|(?:metric|csi)\.gstatic\.|(?:adservice)\.(google)/;
  378. Object.defineProperty(window.Image.prototype, 'src', {
  379. set: function(url) {
  380. if (badImageSrcRegex.test(url)) return;
  381. //console.debug('img send', url);
  382. this.setAttribute('src', url);
  383. },
  384. });
  385.  
  386. Object.defineProperty(window.HTMLScriptElement.prototype, 'src', {
  387. set: function(url) {
  388. //console.debug('script send', url);
  389. this.setAttribute('src', url.replace(badParamsRegex, ''));
  390. },
  391. });
  392.  
  393. const badPaths = [
  394. 'imgevent',
  395. 'shopping\\/product\\/.*?\\/popout',
  396. 'async/ecr',
  397. 'async/bgasy',
  398. ];
  399. const regBadPaths = new RegExp('^/(?:' + badPaths.join('|') + ')');
  400. const origOpen = XMLHttpRequest.prototype.open;
  401. window.XMLHttpRequest.prototype.open = function(act, path) {
  402. if (path === undefined) return;
  403. if (path.startsWith('https://aa.google.com/')) return;
  404. if (path.startsWith('https://play.google.com/log')) return;
  405. if (path.startsWith('https://www.google.com/log')) return;
  406. if (regBadPaths.test(path)) return;
  407. //console.debug('xhr send', act, path);
  408. origOpen.apply(this, [act, path.replace(badParamsRegex, '')]);
  409. };
  410.  
  411. if ('navigator' in window) {
  412. const origSendBeacon = navigator.sendBeacon.bind(navigator);
  413. navigator.sendBeacon = (path, data) => {
  414. if (path === undefined) return;
  415. if (path.startsWith('https://play.google.com/log')) return;
  416. if (badImageSrcRegex.test(path)) return;
  417. //console.debug('nav send', path);
  418. origSendBeacon(path, data);
  419. };
  420. }
  421.  
  422. if (ty in fun) {
  423. const mobileOld = $('html[itemtype]') === null; // &liteui=2
  424. const mobile = !mobileOld && $('meta[name=viewport]') !== null;
  425. const arg = {
  426. pageType: {
  427. ty,
  428. mobile,
  429. mobileOld,
  430. },
  431. badParams,
  432. badParamsRegex,
  433. };
  434. console.debug('arg', arg);
  435. await fun[ty](arg);
  436. } else {
  437. console.warn(`key not found in fun: ${ty.toString()}`);
  438. }
  439. console.timeEnd('rgt');
  440. })();