remove google tracking UWAA

remove google tracking

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

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