remove google tracking UWAA

remove google tracking

当前为 2020-07-27 提交的版本,查看 最新版本

  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.21
  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 = (arg) => {
  22. for (const [obj, prop, value] of arg) {
  23. if (!obj) continue;
  24. Object.defineProperty(obj, prop, {
  25. value,
  26. writable: false,
  27. });
  28. }
  29. };
  30.  
  31. const waitUntilDeclare = (obj, property, option = { interval: 100 }) => {
  32. console.debug('waitUntilDeclare Start', obj.toString(), property);
  33. return new Promise(async (resolve, reject) => {
  34. const propertyNames = property.split('.');
  35. let currObj = obj;
  36. for (const propertyName of propertyNames) {
  37. while (!(propertyName in currObj) || currObj[propertyName] === null) {
  38. await sleep(option.interval);
  39. }
  40. currObj = currObj[propertyName];
  41. }
  42. console.debug('waitUntilDeclare Done', obj.toString(), property);
  43. resolve(currObj);
  44. });
  45. };
  46.  
  47. const waitUntilNode = (sel, option = { interval: 100 }) => {
  48. console.debug('waitUntilNode Start', sel);
  49. return new Promise(async (resolve, reject) => {
  50. let d = null;
  51. while (d === null) {
  52. d = $(sel);
  53. await sleep(option.interval);
  54. }
  55. console.debug('waitUntilNode Done', sel);
  56. resolve(d);
  57. });
  58. };
  59.  
  60. const untrackBuilder = (arg) => {
  61. const badParams = arg.badParams;
  62. return (a) => {
  63. const href = a?.href;
  64. if (!href) return;
  65. const url = new URL(href);
  66. if (a.getAttribute('href') === '/url') {
  67. a.href = url.searchParams.get('url'); // todo q?
  68. } else {
  69. a.removeAttribute('ping');
  70. a.href = delParams(href, badParams);
  71. }
  72. };
  73. };
  74.  
  75. const delParams = (sUrl, params) => {
  76. if (sUrl.startsWith('/')) {
  77. sUrl = location.origin + sUrl;
  78. }
  79. const url = new URL(sUrl);
  80. const keys = [...url.searchParams.keys()];
  81. for (const k of keys) {
  82. if (!params.includes(k)) continue;
  83. url.searchParams.delete(k);
  84. }
  85. return url.toString();
  86. };
  87.  
  88. const Types = {
  89. search: Symbol('search'),
  90. isch: Symbol('image'),
  91. shop: Symbol('shop'),
  92. nws: Symbol('news'),
  93. vid: Symbol('video'),
  94. bks: Symbol('book'),
  95. maps: Symbol('maps'),
  96. fin: Symbol('finance'),
  97. toppage: Symbol('toppage'),
  98. // flights: Symbol('flights'),
  99. };
  100.  
  101. const tbmToType = (tbm) => {
  102. if (tbm === null) {
  103. return Types.search;
  104. }
  105. const t = {
  106. isch: Types.isch,
  107. shop: Types.shop,
  108. nws: Types.nws,
  109. vid: Types.vid,
  110. bks: Types.bks,
  111. fin: Types.fin,
  112. }[tbm];
  113. if (t === undefined) {
  114. return null;
  115. } else {
  116. return t;
  117. }
  118. };
  119.  
  120. const BadParamsBase = [
  121. 'biw', // offsetWidth
  122. 'bih', // offsetHeight
  123. 'ei',
  124. 'sa',
  125. 'ved',
  126. 'source',
  127. 'prds',
  128. 'bvm',
  129. 'bav',
  130. 'psi',
  131. 'stick',
  132. 'dq',
  133. 'ech',
  134. 'gs_gbg',
  135. 'gs_rn',
  136. 'cp',
  137. 'ictx',
  138. 'cshid',
  139. 'gs_lcp',
  140. ];
  141.  
  142. const BadParams = (() => {
  143. const o = {};
  144. o[Types.search] = [
  145. 'vet',
  146. 'pbx',
  147. 'dpr',
  148. 'pf',
  149. 'gs_rn',
  150. 'gs_mss',
  151. 'pq',
  152. 'cp',
  153. 'oq',
  154. 'sclient',
  155. 'gs_l',
  156. 'aqs',
  157. 'sxsrf',
  158. ];
  159. o[Types.isch] = [
  160. 'vet',
  161. 'scroll',
  162. 'yv',
  163. 'iact',
  164. 'forward',
  165. 'ndsp',
  166. 'csi',
  167. 'tbnid',
  168. 'sclient',
  169. 'oq',
  170. ];
  171. o[Types.shop] = ['oq'];
  172. o[Types.nws] = ['oq'];
  173. o[Types.vid] = ['oq'];
  174. o[Types.bks] = ['oq'];
  175. o[Types.fin] = ['oq'];
  176. o[Types.toppage] = ['oq', 'sclient', 'uact'];
  177. o[Types.maps] = ['psi'];
  178. return o;
  179. })();
  180.  
  181. const overwrite = (arg) => {
  182. const badImageSrcRegex = /\/(?:(?:gen(?:erate)?|client|fp)_|log)204|(?:metric|csi)\.gstatic\.|(?:adservice)\.(google)/;
  183. Object.defineProperty(window.Image.prototype, 'src', {
  184. set: function (url) {
  185. if (badImageSrcRegex.test(url)) return;
  186. //console.debug('img send', url);
  187. this.setAttribute('src', url);
  188. },
  189. });
  190.  
  191. Object.defineProperty(window.HTMLScriptElement.prototype, 'src', {
  192. set: function (url) {
  193. //console.debug('script send', url);
  194. if(typeof(url) === "string"){
  195. this.setAttribute('src', delParams(url, arg.badParams));
  196. } else {
  197. this.setAttribute('src', url);
  198. }
  199. },
  200. });
  201.  
  202. const blockOnOpenPaths = [
  203. '/imgevent',
  204. '/async/ecr',
  205. '/async/bgasy',
  206. '/shopping/product/.+?/popout',
  207. '/_/VisualFrontendUi/browserinfo',
  208. '/_/VisualFrontendUi/jserror',
  209. ];
  210.  
  211. // todo fakeXHR?
  212. const blockOnSendPaths = ['/log'];
  213.  
  214. const regBlockOnOpenPaths = new RegExp(
  215. '^(?:' + blockOnOpenPaths.join('|') + ')'
  216. );
  217. const regBlockOnSendPaths = new RegExp(
  218. '^(?:' + blockOnSendPaths.join('|') + ')'
  219. );
  220.  
  221. const origOpen = window.XMLHttpRequest.prototype.open;
  222. window.XMLHttpRequest.prototype.open = function (act, path) {
  223. try {
  224. this.__path = path;
  225. this.__url = null;
  226. if (path === undefined) return;
  227. if (path.startsWith('https://')) {
  228. const url = new URL(path);
  229. this.__url = url;
  230. if (this.__url.origin === 'aa.google.com') return;
  231. if (regBlockOnOpenPaths.test(this.__url.pathname)) return;
  232. } else if (regBlockOnOpenPaths.test(this.__path)) {
  233. return;
  234. }
  235. const new_path = delParams(path, arg.badParams);
  236. } catch (e) {
  237. console.error(e);
  238. return;
  239. }
  240. console.debug('xhr open', this.__path);
  241. return origOpen.apply(this, [act, this.__path]);
  242. };
  243.  
  244. const origSend = window.XMLHttpRequest.prototype.send;
  245. window.XMLHttpRequest.prototype.send = function (body) {
  246. try {
  247. if (this.__url !== null) {
  248. if (regBlockOnSendPaths.test(this.__url.pathname)) return;
  249. } else if (regBlockOnOpenPaths.test(this.__path)) {
  250. return;
  251. }
  252. } catch (e) {
  253. console.error(e);
  254. return;
  255. }
  256. console.debug('xhr send', this.__path);
  257. return origSend.apply(this, [body]);
  258. };
  259.  
  260. if ('navigator' in window) {
  261. const origSendBeacon = navigator.sendBeacon.bind(navigator);
  262. navigator.sendBeacon = (path, data) => {
  263. if (path === undefined) return;
  264. if (path.startsWith('https://play.google.com/log')) return;
  265. if (badImageSrcRegex.test(path)) return;
  266. //console.debug('nav send', path);
  267. origSendBeacon(path, data);
  268. };
  269. }
  270. };
  271.  
  272. const searchFormUriuri = async (arg) => {
  273. // TODO mobile, mobileOld
  274. let form = null;
  275. if (arg.pageType.mobileOld) {
  276. form = $('#sf');
  277. } else if (arg.pageType.ty === Types.isch) {
  278. form = await waitUntilDeclare(window, 'sf');
  279. } else {
  280. form = await waitUntilDeclare(window, 'tsf');
  281. }
  282.  
  283. if (form === null) {
  284. console.warn('form === null');
  285. return;
  286. }
  287.  
  288. for (const i of form) {
  289. if (i.tagName !== 'INPUT') continue;
  290. if (arg.badParams.includes(i.name)) {
  291. i.parentElement.removeChild(i);
  292. }
  293. }
  294. const orig = form.appendChild.bind(form);
  295. form.appendChild = (e) => {
  296. if (!arg.badParams.includes(e.name)) {
  297. orig(e);
  298. }
  299. };
  300. };
  301.  
  302. const untrackAnchors = (untrack, arg) => {
  303. let property = null;
  304. if (arg.pageType.mobile) {
  305. property = 'topstuff';
  306. } else if (arg.pageType.mobileOld) {
  307. property = 'main'; // 'rmenu';
  308. } else if (arg.pageType.ty === Types.search) {
  309. property = 'li_';
  310. } else if (arg.pageType.ty === Types.bks) {
  311. property = 'lr_';
  312. } else if (arg.pageType.ty === Types.vid) {
  313. property = 'ow6';
  314. } else if (arg.pageType.ty === Types.nws) {
  315. property = 'ow8';
  316. } else if (arg.pageType.ty === Types.isch) {
  317. property = 'i4';
  318. } else {
  319. property = 'search';
  320. }
  321.  
  322. return waitUntilDeclare(window, property).then((_) => {
  323. for (const a of $$('a')) {
  324. untrack(a);
  325. }
  326. });
  327. };
  328.  
  329. const gcommon = async (arg) => {
  330. const untrack = untrackBuilder(arg);
  331. const p1 = waitUntilDeclare(window, 'google').then((google) => {
  332. rewriteProperties([
  333. [google, 'log', yes],
  334. [google, 'logUrl', doNothing],
  335. [google, 'getLEI', yes],
  336. [google, 'ctpacw', yes],
  337. [google, 'csiReport', yes],
  338. [google, 'report', yes],
  339. [google, 'aft', yes],
  340. //[google, 'kEI', '0'],
  341. //[google, 'getEI', yes], or ()=>"0"
  342. ]);
  343. });
  344. rewriteProperties([
  345. [window, 'rwt', doNothing],
  346. [window.gbar_, 'Rm', doNothing],
  347. ]);
  348.  
  349. const p2 = untrackAnchors(untrack, arg);
  350. const p3 = searchFormUriuri(arg);
  351. await Promise.all([p1, p2, p3]);
  352.  
  353. if (arg.pageType.mobile) {
  354. const sel =
  355. arg.pageType.ty === Types.search
  356. ? '#bres + h1 + div > div + div'
  357. : '#bres + div > div + div';
  358. new MutationObserver((mutations) => {
  359. const nodes = mutations.flatMap((d) => [...d.addedNodes]);
  360. for (const n of nodes) {
  361. new MutationObserver((_, obs) => {
  362. console.debug('untrack', n);
  363. for (const a of $$('a', n)) {
  364. untrack(a);
  365. }
  366. obs.disconnect();
  367. }).observe(n, { childList: true });
  368. }
  369. }).observe($(sel), { childList: true });
  370. }
  371. };
  372.  
  373. const fun = {};
  374.  
  375. fun[Types.toppage] = searchFormUriuri;
  376.  
  377. fun[Types.search] = gcommon;
  378. fun[Types.vid] = gcommon;
  379. fun[Types.nws] = gcommon;
  380. fun[Types.bks] = gcommon;
  381. fun[Types.fin] = gcommon;
  382.  
  383. fun[Types.isch] = async (arg) => {
  384. // TODO mobile, mobileOld
  385. const untrack = untrackBuilder(arg);
  386. const p1 = untrackAnchors(untrack, arg);
  387. const p2 = searchFormUriuri(arg);
  388. const p3 = waitUntilDeclare(window, 'islrg').then((islrg) => {
  389. const imagesWrapper = islrg.children[0];
  390. const uuuu = (div) => {
  391. if (div.children.length !== 2) return;
  392. const a = div.children[1];
  393. untrack(a);
  394. a.removeAttribute('jsaction');
  395. };
  396. for (const div of $$(':scope > div', imagesWrapper)) {
  397. uuuu(div);
  398. }
  399. new MutationObserver((mutations) => {
  400. for (const div of mutations.flatMap((m) => [...m.addedNodes])) {
  401. uuuu(div);
  402. }
  403. }).observe(imagesWrapper, { childList: true });
  404. });
  405.  
  406. /*
  407. const p4 = waitUntilNode('.ZsbmCf').then(_ => {
  408. for (const a of $$('.ZsbmCf, .Beeb4e')) {
  409. const ee = e => untrack(a);
  410. a.addEventListener('click', ee);
  411. a.addEventListener('contextmenu', ee);
  412. }
  413. });
  414. */
  415.  
  416. /*
  417. const safeurl = a => {
  418. const s = a.j ?? a;
  419. if (s.startsWith('/imgres?')) {
  420. return delParams(location.origin + s, [
  421. 'vet',
  422. 'w',
  423. 'h',
  424. 'ved',
  425. // q imgurl imgrefurl tbnid docid
  426. ]);
  427. }
  428. if (s.startsWith('/')) return s;
  429. return new URLSearchParams(s).get('url') ?? s;
  430. };
  431. const p4 = waitUntilDeclare(window, 'default_VisualFrontendUi').then(_ => {
  432. rewriteProperties([[_, 'zd', safeurl]]);
  433. });
  434. */
  435. await Promise.all([p1, p2, p3]);
  436. };
  437.  
  438. fun[Types.shop] = async (arg) => {
  439. // TODO desktop, mobile, mobileOld
  440. const untrack = untrackBuilder(arg);
  441. const p1 = untrackAnchors(untrack, arg);
  442. const p2 = searchFormUriuri(arg);
  443. const p3 = waitUntilDeclare(window, 'google.pmc.spop.r', {
  444. interval: 30,
  445. }).then((shopObj) => {
  446. for (const result of $$('.sh-dlr__list-result')) {
  447. const shop = shopObj[result.dataset.docid];
  448. const link = shop[34][6];
  449. result.querySelector("a[class$='__merchant-name']").href = link;
  450. result.querySelector('a.translate-content').href = link;
  451. result.querySelector('div.sh-dlr__thumbnail > a').href = link;
  452. shop[3][0][1] = link;
  453. shop[14][1] = link;
  454. shop[89][16] = link;
  455. shop[89][18][0] = link;
  456. if (shop[85] !== null) {
  457. shop[85][3] = link;
  458. }
  459. }
  460. });
  461. await Promise.all([p1, p2, p3]);
  462. };
  463.  
  464. fun[Types.maps] = async (arg) => {
  465. // TODO desktop, mobile, mobileOld
  466. const untrack = (a) => {
  467. a.addEventListener('click', (e) => e.stopPropagation());
  468. for (const n of [...a.attributes]
  469. .map((at) => at.name)
  470. .filter((n) => ['href', 'class'].indexOf(n) === -1)) {
  471. a.removeAttribute(n);
  472. }
  473. };
  474. const main = await waitUntilNode('div[role=main]', { interval: 30 });
  475. new MutationObserver((mutations) => {
  476. console.log(mutations);
  477. }).observe(main.children[1].children[0], { childList: true });
  478. };
  479.  
  480. (async () => {
  481. 'use strict';
  482. console.debug('rgt: init');
  483. console.time('rgt');
  484.  
  485. const ty = (() => {
  486. if (location.pathname.startsWith('/maps')) {
  487. return Types.maps;
  488. }
  489. if (location.pathname === '/' || location.pathname === '/webhp') {
  490. return Types.toppage;
  491. }
  492. const tbm = new URLSearchParams(location.search).get('tbm');
  493. if (location.pathname === '/search') {
  494. return tbmToType(tbm);
  495. }
  496. return null;
  497. })();
  498.  
  499. if (ty === null) {
  500. console.debug('ty === null', location.href);
  501. return;
  502. }
  503.  
  504. const badParams = (() => {
  505. return [...BadParamsBase, ...BadParams[ty]];
  506. })();
  507.  
  508. if (ty in fun) {
  509. const mobileOld = $('html[itemtype]') === null; // &liteui=2
  510. const mobile = !mobileOld && $('meta[name=viewport]') !== null;
  511. const arg = {
  512. pageType: {
  513. ty,
  514. mobile,
  515. mobileOld,
  516. },
  517. badParams,
  518. };
  519. console.debug('arg', arg);
  520. overwrite(arg);
  521. await fun[ty](arg);
  522. } else {
  523. console.warn(`key not found in fun: ${ty.toString()}`);
  524. }
  525. console.timeEnd('rgt');
  526. })();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址