spm_Track_Block_Tool

移除链接中的spm跟踪参数

目前为 2022-05-14 提交的版本,查看 最新版本

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