spm_Track_Block_Tool

移除链接中的spm跟踪参数

当前为 2023-07-21 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name spm_Track_Block_Tool
  3. // @namespace _s7util__
  4. // @version 0.6.1
  5. // @description:en Remove [spm] track paramter in URL
  6. // @description 移除链接中的spm跟踪参数
  7. // @author shc0743
  8. // @icon data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==
  9. // @grant none
  10. // @license GPL-3.0
  11. // @supportURL https://github.com/shc0743/MyUtility/issues/new
  12. // @run-at document-start
  13. // @match http*://*.bilibili.com/*
  14. // @match http*://*.baidu.com/*
  15. // @match http*://*.cctv.com/*
  16. // @match http*://*.taobao.com/*
  17. // @match http*://*.alibaba.com/*
  18. // @exclude http*://*.paypal.com/*
  19. // @exclude http*://*.alipay.com/*
  20. // ==/UserScript==
  21.  
  22. /*
  23. Description:
  24. 说明:
  25.  
  26. This user script removes the spm paramter in <a href> elements.
  27. 此脚本移除 <a href> 元素中的spm参数。
  28.  
  29. If it doesn't work, try refreshing it a few times or wait a while.
  30. 若无法生效,请尝试刷新几次或等一会。
  31.  
  32. Examples:
  33. 示例:
  34.  
  35. https://www.bilibili.com/video/av170001?spm_id_from=114514
  36. -> https://www.bilibili.com/video/av170001
  37.  
  38. https://www.bilibili.com/video/av170001?query1=arg2&spm_id_from=114514&query2=data3
  39. -> https://www.bilibili.com/video/av170001?query1=arg2&query2=data3
  40.  
  41. https://www.bilibili.com/video/av170001?spm=114514.1919810#hash
  42. -> https://www.bilibili.com/video/av170001#hash
  43.  
  44. https://www.bilibili.com/video/av170001?spm=114514.1919810&query2=data3#hash1
  45. -> https://www.bilibili.com/video/av170001?query2=data3#hash1
  46. */
  47.  
  48. (function () {
  49. 'use strict';
  50.  
  51. // Your code here...
  52.  
  53. var track_args_list = [
  54. { 'domain': '*', 'keyword': 'spm' },
  55. { 'domain': '*', 'keyword': 'spm_id_from' },
  56. { 'domain': '*', 'keyword': 'from_source' },
  57. { 'domain': 'bilibili.com', 'keyword': 'from' },
  58. { 'domain': 'bilibili.com', 'keyword': 'seid' },
  59. { 'domain': 'bilibili.com', 'keyword': 'vd_source' },
  60. { 'domain': 'baike.baidu.com', 'keyword': 'fr' },
  61. { 'domain': 'alibaba.com', 'keyword': 'tracelog' },
  62. ];
  63. var unwritable_list = [
  64. // https://greasyfork.org/zh-CN/scripts/443049/discussions/132536
  65. { object: window, key: 'goldlog' },
  66. ];
  67. try {
  68. for (let i of unwritable_list) {
  69. Object.defineProperty(i.object, i.key, {
  70. get() { return undefined },
  71. set(value) { void (value) },
  72. enumerable: false,
  73. configurable: true
  74. });
  75. }
  76. }
  77. catch (error) {
  78. console.warn(error);
  79. }
  80.  
  81. return (function (global) {
  82.  
  83. //var expr = /\?[\s\S]*spm/i;
  84.  
  85. /**
  86. * 去除字符串中的spm参数
  87. * @param {String} str URL to remove spm
  88. * @returns 去除spm后的结果
  89. */
  90. var remove_spm = function (str) {
  91. if (typeof (str) != 'string') return str;
  92. var newstr = '';
  93. var len = str.length;
  94. // 只去除查询参数部分,避免正常url被替换而导致404
  95. var hash_part_begin = str.indexOf('#');
  96. var query_part_begin = str.indexOf('?');
  97. if (query_part_begin == -1 ||
  98. (hash_part_begin != -1 && query_part_begin > hash_part_begin))
  99. { return str; } // 没有查询参数或?在#后面,直接返回
  100. newstr = str.substring(0, query_part_begin);
  101. var domain = '';
  102. {
  103. let index = str.indexOf('://');
  104. if (index + 1) {
  105. index = str.indexOf('/', index + 3);
  106. if (index + 1) {
  107. domain = str.substring(0, index);
  108. }
  109. }
  110. }
  111.  
  112. for (let i = query_part_begin, need_break; i < len; ++i) {
  113. for (let j = 0; j < track_args_list.length; ++j) {
  114. if (!(track_args_list[j].domain == '*' ||
  115. domain.indexOf(track_args_list[j].domain) != -1)) {
  116. need_break = false;
  117. break;
  118. }
  119. need_break = true;
  120. if (track_args_list[j].keyword == str.substring(i,
  121. i + track_args_list[j].keyword.length - 0)) {
  122. // 检测到
  123. while ((++i) < len) {
  124. if (str[i] == '&') { // 不能单独保留一个 & 号
  125. i++;
  126. break; // 去掉
  127. }
  128. if (str[i] == '#') break; // 保留hash部分
  129. }
  130. if (i == len) break; // 越界,直接break,以免url出现undefined
  131. }
  132. need_break = false;
  133. }
  134. if (need_break) break;
  135. newstr += str[i];
  136. }
  137.  
  138. var _lastchar;
  139. for (let i = 0; i < newstr.length; ++i) {
  140. _lastchar = newstr[newstr.length - 1];
  141. if (_lastchar == '?' || _lastchar == '&') { // 如果移除后只剩下 ? 或 &
  142. newstr = newstr.substring(0, newstr.length - 1); // 去掉
  143. } else break;
  144. }
  145. // Bug-Fix:
  146. // https://example.com/example?q1=arg&spm=123#hash1
  147. // -> https://example.com/example?q1=arg&#hash1
  148. // Invalid URL syntax at ^^
  149. newstr = newstr.replace(/\&\#/igm, '#');
  150. newstr = newstr.replace(/\?\#/igm, '#');
  151. return newstr;
  152. }
  153. var test_spm = function (str) {
  154. const currentDomain = window.location.hostname;
  155. for (let tracker of track_args_list) {
  156. if (currentDomain.endsWith(tracker.domain) && new RegExp(tracker.keyword, 'i').test(str)) {
  157. return true;
  158. }
  159. }
  160. return false;
  161. };
  162. var _realwindowopen = window.open;
  163. var _realhistorypushState = window.history.pushState;
  164. var _realhistoryreplaceState = window.history.replaceState;
  165.  
  166. /*var _link_click_test = function (val) {
  167. if (/\#/.test(val)) return true;
  168. if (/javascript\:/i.test(val)) return true;
  169. return false;
  170. };
  171. var _link_click = function (event) {
  172. if (_link_click_test(this.href)) return;
  173. event.preventDefault();
  174. // 防止被再次加入spm
  175. this.href = remove_spm(this.href);
  176. _realwindowopen(this.href, this.target || '_self');
  177. return false;
  178. };*/
  179. var _link_mouseover = function () {
  180. if (test_spm(this.href)) this.href = remove_spm(this.href);
  181. };
  182. var link_clean_worker = function (el) {
  183. if (test_spm(el.href)) {
  184. // 链接已经被加入spm , 需要移除
  185. el.href = remove_spm(el.href);
  186. }
  187. }
  188. var linkclickhandlerinit = function () {
  189. var el = document.querySelectorAll('a[href]');
  190. for (let i = el.length - 1; i >= 0; --i) {
  191. link_clean_worker(el[i]);
  192. }
  193. };
  194.  
  195. try {
  196. let wopen = function (url, target, features) {
  197. return _realwindowopen.call(window,
  198. remove_spm(url),
  199. target,
  200. features);
  201. };
  202. let hp = function (data, title, url) {
  203. return _realhistorypushState.call(
  204. window.history, data, title,
  205. remove_spm(url));
  206. };
  207. let hr = function (data, title, url) {
  208. return _realhistoryreplaceState.call(
  209. window.history, data, title,
  210. remove_spm(url));
  211. };
  212. wopen.toString =
  213. hp.toString =
  214. hr.toString =
  215. ({ toString() { return 'function () { [native code] }' } }.toString);
  216. // 必须定义成 writable 否则一些网站(例如B站收藏夹页面)会出错
  217. Object.defineProperty(window, 'open', {
  218. value: wopen,
  219. writable: true,
  220. enumerable: true,
  221. configurable: true
  222. }); // 重定义window.open 以阻止弹出窗口中的spm
  223. Object.defineProperty(window.history, 'pushState', {
  224. value: hp,
  225. writable: true,
  226. enumerable: true,
  227. configurable: true
  228. }); // 重定义history.pushState
  229. Object.defineProperty(window.history, 'replaceState', {
  230. value: hr,
  231. writable: true,
  232. enumerable: true,
  233. configurable: true
  234. }); // 重定义history.replaceState
  235.  
  236. }
  237. catch (error) {
  238. console.warn("This browser doesn't support redefining" +
  239. " window.open , so [SpmBlockTool] cannot remove" +
  240. " spm in popup window.\nError:", error);
  241. }
  242.  
  243. var DOM_observer;
  244. let DOM_observer_observe = function () {
  245. DOM_observer.observe(document.body, {
  246. attributes: true,
  247. childList: true,
  248. subtree: true
  249. });
  250. };
  251. DOM_observer = new MutationObserver(function (args) {
  252. //debugger
  253. // console.log('DOM changed: ', args);
  254. DOM_observer.disconnect();
  255. for (let i of args) {
  256. if (i.type == 'attributes') {
  257. link_clean_worker(i.target);
  258. }
  259. else if (i.type == 'childList') {
  260. for (let j of i.addedNodes) {
  261. link_clean_worker(j);
  262. }
  263. }
  264. }
  265. DOM_observer.takeRecords();
  266. DOM_observer_observe();
  267. });
  268.  
  269. window.addEventListener('DOMContentLoaded', function () {
  270. // window.setInterval(linkclickhandlerinit, 5000);
  271. new Promise(o => { linkclickhandlerinit(); o() }); // 异步执行
  272.  
  273. DOM_observer_observe();
  274. });
  275.  
  276. // 移除当前页面的spm
  277. // 当然,实际上spm已经在userscript加载前被发送到服务器,
  278. // 所以该功能仅美化url.
  279. // 如果要禁用该功能,删除下面一行开头的斜杠。
  280. //if(0)
  281. // Remove spm from current page
  282. // Of course, in fact, spm has been sent to the server
  283. // before userscript is loaded, so this function only beautifies the URL.
  284. // If you want to disable this feature, remove the slash
  285. // at the beginning of the following line:
  286. //if(0)
  287. if (test_spm(location.href)) {
  288. _realhistoryreplaceState.call(window.history,
  289. {}, document.title,
  290. remove_spm(location.href));
  291. }
  292.  
  293. // https://greasyfork.org/zh-CN/scripts/443049/discussions/156657
  294. // https://greasyfork.org/zh-CN/scripts/443049/discussions/132536
  295. setInterval(function () {
  296. // 确认过了,只是检查页面有没有跟踪参数,不进行大范围DOM访问,性能开销可以忽略
  297. if (test_spm(location.href)) {
  298. _realhistoryreplaceState.call(window.history,
  299. {}, document.title,
  300. remove_spm(location.href));
  301. }
  302. }, 800);
  303.  
  304. /*
  305. // 测试代码
  306. var test_urls = [
  307. 'https://www.bilibili.com/video/BV18X4y1N7Yh',
  308. 'https://www.bilibili.com/video/BV18X4y1N7Yh?spm_id_from=114514',
  309. 'https://www.bilibili.com/video/BV18X4y1N7Yh?spm=114514.1919810',
  310. 'https://www.bilibili.com/video/BV18X4y1N7Yh?spm_id_from=114514.123',
  311.  
  312. 'https://www.bilibili.com/video/av170001',
  313. 'https://www.bilibili.com/video/av170001?spm_id_from=114514',
  314. 'https://www.bilibili.com/video/av170001?spm=114514.1919810',
  315. 'https://www.bilibili.com/video/av170001?spm_id_from=114514.123',
  316.  
  317. 'https://www.bilibili.com/video/av170001?query1=arg2&spm_id_from=114514',
  318. 'https://www.bilibili.com/video/av170001?query1=arg2&spm=114514.1919810',
  319. 'https://www.bilibili.com/video/av170001?query1=arg2&spm_id_from=114514.123',
  320. 'https://www.bilibili.com/video/av170001?query1=arg2&spm_id_from=114514',
  321. 'https://www.bilibili.com/video/av170001?query1=arg2&spm=114514.1919810',
  322. 'https://www.bilibili.com/video/av170001?query1=arg2&spm_id_from=114514.123',
  323.  
  324. 'https://www.bilibili.com/video/av170001?query1=arg2&spm_id_from=114514&query2=data3',
  325. 'https://www.bilibili.com/video/av170001?query1=arg2&spm=114514.1919810&query2=data3',
  326.  
  327. 'https://www.bilibili.com/video/av170001?spm_id_from=114514#hash',
  328. 'https://www.bilibili.com/video/av170001?query1=arg2&spm_id_from=114514#hash1',
  329. 'https://www.bilibili.com/video/av170001?query1=arg2&spm=114514.1919810#hash1',
  330.  
  331. 'https://www.bilibili.com/video/av170001?spm_id_from=114514&query2=data3#hash1',
  332. 'https://www.bilibili.com/video/av170001?spm=114514.1919810&query2=data3#hash1',
  333. ];
  334. for(let i=0;i<test_urls.length;++i){
  335. let el=document.createElement('a');
  336. el.href=test_urls[i];
  337. el.innerHTML=i+1 + '';
  338. document.documentElement.appendChild(el);
  339. }
  340. for(let i=0;i<test_urls.length;++i){
  341. let el=document.createElement('a');
  342. el.href=test_urls[i];
  343. el.innerHTML=i+1 + ' blank';
  344. el.target='_blank';
  345. document.documentElement.appendChild(el);
  346. }
  347. */
  348.  
  349. })(window);
  350.  
  351. })();