spm_Track_Block_Tool

移除链接中的spm跟踪参数

  1. // ==UserScript==
  2. // @name spm_Track_Block_Tool
  3. // @namespace _s7util__
  4. // @version 0.7.5
  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. // 以下是本脚本检测的跟踪参数,检测到后自动去除
  55. // 格式: { 'domain': '应用到哪个网站,只要填顶级域名即可,例如www.baidu.com只要写baidu.com', 'keyword': '跟踪参数的名称' }
  56. { 'domain': '*', 'keyword': 'spm' },
  57. { 'domain': '*', 'keyword': 'spm_id_from' },
  58. { 'domain': '*', 'keyword': 'from_source' },
  59. { 'domain': 'bilibili.com', 'keyword': 'from' },
  60. { 'domain': 'bilibili.com', 'keyword': 'seid' },
  61. { 'domain': 'bilibili.com', 'keyword': 'vd_source' },
  62. { 'domain': 'bilibili.com', 'keyword': 'search_source' },
  63. { 'domain': 'baike.baidu.com', 'keyword': 'fr' },
  64. { 'domain': 'alibaba.com', 'keyword': 'tracelog' },
  65. ];
  66. var unwritable_list = [
  67. //// https://gf.qytechs.cn/zh-CN/scripts/443049/discussions/132536
  68. //{ object: window, key: 'goldlog' },
  69. ];
  70. try {
  71. for (let i of unwritable_list) {
  72. Object.defineProperty(i.object, i.key, {
  73. get() { return undefined },
  74. set(value) { return !void (value) },
  75. enumerable: false,
  76. configurable: true
  77. });
  78. }
  79. }
  80. catch (error) {
  81. console.warn(error);
  82. }
  83.  
  84. return (function (global) {
  85.  
  86. //var expr = /\?[\s\S]*spm/i;
  87.  
  88. /**
  89. * 检测域名是否匹配,支持域名层级
  90. * @param {String} pattern 主域名
  91. * @param {String} str 要检测的域名,可以和主域名一样,也可以是主域名的子域名
  92. * @returns 匹配则返回true,否则false
  93. * @example DomainCheck('baidu.com', 'yiyan.baidu.com') === true
  94. * 注意:请自行进行类型检查,如果参数不是String则行为未知
  95. */
  96. const DomainCheck = function (pattern, str) {
  97. if (str === pattern) return true;
  98. pattern = '.' + pattern;
  99. if (str.endsWith(pattern)) return true;
  100. return false;
  101. };
  102.  
  103. /**
  104. * 去除字符串中的spm参数
  105. * @param {String} str URL to remove spm
  106. * @returns 去除spm后的结果
  107. */
  108. const remove_spm = function (str) {
  109. // 新版方案,使用 URL 相关api操作
  110. try {
  111. const url = new URL(str, globalThis.location.href); // 构造URL对象
  112. // 改用此法后,可轻松处理 //search.bilibili.com/ 之类的不以http(s)开头的链接
  113. // 对于url构造失败的(无效url之类的),回退到旧版基于字符串的方案
  114.  
  115. const hostname = url.hostname;
  116. for (const i of track_args_list) {
  117. if (!(i.domain === '*' || DomainCheck(i.domain, hostname))) continue;
  118. if (url.searchParams.has(i.keyword)) { // 存在track参数!!!
  119. url.searchParams.delete(i.keyword); // 去掉
  120. }
  121. }
  122. return url.href; // 优雅
  123.  
  124. } catch (error) {
  125. // 回退到旧版方案
  126. return OLD__remove_spm(str);
  127. }
  128. };
  129. // 以下是旧版方案
  130. // 古语云:代码能跑就不要删
  131. var OLD__remove_spm = function (str) {
  132. if (typeof (str) != 'string') return str;
  133. var newstr = '';
  134. var len = str.length;
  135. // 只去除查询参数部分,避免正常url被替换而导致404
  136. var hash_part_begin = str.indexOf('#');
  137. var query_part_begin = str.indexOf('?');
  138. if (query_part_begin == -1 ||
  139. (hash_part_begin != -1 && query_part_begin > hash_part_begin))
  140. { return str; } // 没有查询参数或?在#后面,直接返回
  141. newstr = str.substring(0, query_part_begin);
  142. var domain = '';
  143. {
  144. let index = str.indexOf('://');
  145. if (index + 1) {
  146. index = str.indexOf('/', index + 3);
  147. if (index + 1) {
  148. domain = str.substring(0, index);
  149. }
  150. }
  151. }
  152.  
  153. for (let i = query_part_begin, need_break; i < len; ++i) {
  154. for (let j = 0; j < track_args_list.length; ++j) {
  155. if (!(track_args_list[j].domain == '*' ||
  156. domain.indexOf(track_args_list[j].domain) != -1)) {
  157. need_break = false;
  158. break;
  159. }
  160. need_break = true;
  161. if (track_args_list[j].keyword == str.substring(i,
  162. i + track_args_list[j].keyword.length - 0)) {
  163. // 检测到
  164. while ((++i) < len) {
  165. if (str[i] == '&') { // 不能单独保留一个 & 号
  166. i++;
  167. break; // 去掉
  168. }
  169. if (str[i] == '#') break; // 保留hash部分
  170. }
  171. if (i == len) break; // 越界,直接break,以免url出现undefined
  172. }
  173. need_break = false;
  174. }
  175. if (need_break) break;
  176. newstr += str[i];
  177. }
  178.  
  179. var _lastchar;
  180. for (let i = 0; i < newstr.length; ++i) {
  181. _lastchar = newstr[newstr.length - 1];
  182. if (_lastchar == '?' || _lastchar == '&') { // 如果移除后只剩下 ? 或 &
  183. newstr = newstr.substring(0, newstr.length - 1); // 去掉
  184. } else break;
  185. }
  186. // Bug-Fix:
  187. // https://example.com/example?q1=arg&spm=123#hash1
  188. // -> https://example.com/example?q1=arg&#hash1
  189. // Invalid URL syntax at ^^
  190. newstr = newstr.replace(/\&\#/igm, '#');
  191. newstr = newstr.replace(/\?\#/igm, '#');
  192. return newstr;
  193. }
  194. var test_spm = function (str) {
  195. const currentDomain = window.location.hostname;
  196. for (let tracker of track_args_list) {
  197. if (currentDomain.endsWith(tracker.domain) && new RegExp(tracker.keyword, 'i').test(str)) {
  198. return true;
  199. }
  200. }
  201. return false;
  202. };
  203. var _realwindowopen = window.open;
  204. var _realhistorypushState = window.history.pushState;
  205. var _realhistoryreplaceState = window.history.replaceState;
  206.  
  207. /*var _link_click_test = function (val) {
  208. if (/\#/.test(val)) return true;
  209. if (/javascript\:/i.test(val)) return true;
  210. return false;
  211. };
  212. var _link_click = function (event) {
  213. if (_link_click_test(this.href)) return;
  214. event.preventDefault();
  215. // 防止被再次加入spm
  216. this.href = remove_spm(this.href);
  217. _realwindowopen(this.href, this.target || '_self');
  218. return false;
  219. };*/
  220. var _link_mouseover = function () {
  221. if (test_spm(this.href)) this.href = remove_spm(this.href);
  222. };
  223. var link_clean_worker = function (el) {
  224. if (test_spm(el.href)) {
  225. // 链接已经被加入spm , 需要移除
  226. el.href = remove_spm(el.href);
  227. }
  228. }
  229. var linkclickhandlerinit = function () {
  230. var el = document.querySelectorAll('a[href]');
  231. for (let i = el.length - 1; i >= 0; --i) {
  232. link_clean_worker(el[i]);
  233. }
  234. };
  235.  
  236. try {
  237. let wopen = function (url, target, features) {
  238. return _realwindowopen.call(window,
  239. remove_spm(url),
  240. target,
  241. features);
  242. };
  243. let hp = function (data, title, url) {
  244. return _realhistorypushState.call(
  245. window.history, data, title,
  246. (typeof url === 'string') ? remove_spm(url) : url);
  247. };
  248. let hr = function (data, title, url) {
  249. return _realhistoryreplaceState.call(
  250. window.history, data, title,
  251. (typeof url === 'string') ? remove_spm(url) : url);
  252. };
  253. wopen.toString =
  254. hp.toString =
  255. hr.toString =
  256. ({ toString() { return 'function () { [native code] }' } }.toString);
  257. // 必须定义成 writable 否则一些网站(例如B站收藏夹页面)会出错
  258. Object.defineProperty(window, 'open', {
  259. value: wopen,
  260. writable: true,
  261. enumerable: true,
  262. configurable: true
  263. }); // 重定义window.open 以阻止弹出窗口中的spm
  264. Object.defineProperty(window.history, 'pushState', {
  265. value: hp,
  266. writable: true,
  267. enumerable: true,
  268. configurable: true
  269. }); // 重定义history.pushState
  270. Object.defineProperty(window.history, 'replaceState', {
  271. value: hr,
  272. writable: true,
  273. enumerable: true,
  274. configurable: true
  275. }); // 重定义history.replaceState
  276.  
  277. }
  278. catch (error) {
  279. console.warn("This browser doesn't support redefining" +
  280. " window.open , so [SpmBlockTool] cannot remove" +
  281. " spm in popup window.\nError:", error);
  282. }
  283.  
  284. var DOM_observer;
  285. let DOM_observer_observe = function () {
  286. DOM_observer.observe(document.body, {
  287. attributes: true,
  288. childList: true,
  289. subtree: true
  290. });
  291. };
  292. DOM_observer = new MutationObserver(function (args) {
  293. //debugger
  294. // console.log('DOM changed: ', args);
  295. DOM_observer.disconnect();
  296. for (let i of args) {
  297. if (i.type == 'attributes') {
  298. link_clean_worker(i.target);
  299. }
  300. else if (i.type == 'childList') {
  301. for (let j of i.addedNodes) {
  302. link_clean_worker(j);
  303. }
  304. }
  305. }
  306. DOM_observer.takeRecords();
  307. DOM_observer_observe();
  308. });
  309.  
  310. window.addEventListener('DOMContentLoaded', function () {
  311. // window.setInterval(linkclickhandlerinit, 5000);
  312. new Promise(o => { linkclickhandlerinit(); o() }); // 异步执行
  313.  
  314. DOM_observer_observe();
  315. });
  316.  
  317. // 移除当前页面的spm
  318. // 当然,实际上spm已经在userscript加载前被发送到服务器,
  319. // 所以该功能仅美化url.
  320. // 如果要禁用该功能,删除下面一行开头的斜杠。
  321. //if(0)
  322. // Remove spm from current page
  323. // Of course, in fact, spm has been sent to the server
  324. // before userscript is loaded, so this function only beautifies the URL.
  325. // If you want to disable this feature, remove the slash
  326. // at the beginning of the following line:
  327. //if(0)
  328. if (test_spm(location.href)) {
  329. _realhistoryreplaceState.call(window.history,
  330. {}, document.title,
  331. remove_spm(location.href));
  332. }
  333.  
  334. // https://gf.qytechs.cn/zh-CN/scripts/443049/discussions/156657
  335. // https://gf.qytechs.cn/zh-CN/scripts/443049/discussions/132536
  336. setInterval(function () {
  337. // 确认过了,只是检查页面有没有跟踪参数,不进行大范围DOM访问,性能开销可以忽略
  338. if (test_spm(location.href)) {
  339. // 2023.12.31改:鬼知道test_spm里面是什么屎山代码,元旦有时间了重构一下才发现一堆问题,
  340. // 又不敢删,斟酌后决定把此处延时由800改为5000
  341. _realhistoryreplaceState.call(window.history,
  342. {}, document.title,
  343. remove_spm(location.href));
  344. }
  345. }, 5000);
  346.  
  347. // 修复cctv等网站去除goldlog后部分功能异常的bug
  348. const fakeGoldlog = {
  349. afterRecord() { },
  350. afterSendPV() { },
  351. aplusBridgeName() { },
  352. aplus_pubsub: { handlers: [], pubs: {} },
  353. appendMetaInfo() { },
  354. beforeRecord() { },
  355. beforeSendPV() { },
  356. cdnPath: "//g.alicdn.com",
  357. cloneDeep() { },
  358. create() { },
  359. extend() { },
  360. getCdnPath() { },
  361. getCookie() { },
  362. getMetaInfo() { },
  363. getParam() { },
  364. getPvId() { },
  365. isInternational() { },
  366. launch() { },
  367. lver: "8.10.5",
  368. nameStorage: window.localStorage,
  369. on() { },
  370. pv_context: {},
  371. pvid: "ffffffffffffff",
  372. record() { },
  373. recordUdata() { },
  374. req: window.location.href,
  375. send() { },
  376. sendPV() { },
  377. setMetaInfo() { },
  378. setPageSPM() { },
  379. spm_ab: ['X12345', 'xxxxxxxxxxxx']
  380. };
  381. const proxiedFakeGoldlog = new Proxy(fakeGoldlog, {
  382. set(target, p, newValue, receiver) {
  383. console.warn(new TypeError('Fuck you, alicdn goldlog'));
  384. Reflect.set(target, p, newValue, receiver);
  385. return true;
  386. }
  387. });
  388. Object.defineProperty(globalThis, 'goldlog', {
  389. get() { return proxiedFakeGoldlog },
  390. set(value) { return true },
  391. enumerable: true,
  392. configurable: false
  393. })
  394.  
  395. /*
  396. // 测试代码
  397. var test_urls = [
  398. 'https://www.bilibili.com/video/BV18X4y1N7Yh',
  399. 'https://www.bilibili.com/video/BV18X4y1N7Yh?spm_id_from=114514',
  400. 'https://www.bilibili.com/video/BV18X4y1N7Yh?spm=114514.1919810',
  401. 'https://www.bilibili.com/video/BV18X4y1N7Yh?spm_id_from=114514.123',
  402.  
  403. 'https://www.bilibili.com/video/av170001',
  404. 'https://www.bilibili.com/video/av170001?spm_id_from=114514',
  405. 'https://www.bilibili.com/video/av170001?spm=114514.1919810',
  406. 'https://www.bilibili.com/video/av170001?spm_id_from=114514.123',
  407.  
  408. 'https://www.bilibili.com/video/av170001?query1=arg2&spm_id_from=114514',
  409. 'https://www.bilibili.com/video/av170001?query1=arg2&spm=114514.1919810',
  410. 'https://www.bilibili.com/video/av170001?query1=arg2&spm_id_from=114514.123',
  411. 'https://www.bilibili.com/video/av170001?query1=arg2&spm_id_from=114514',
  412. 'https://www.bilibili.com/video/av170001?query1=arg2&spm=114514.1919810',
  413. 'https://www.bilibili.com/video/av170001?query1=arg2&spm_id_from=114514.123',
  414.  
  415. 'https://www.bilibili.com/video/av170001?query1=arg2&spm_id_from=114514&query2=data3',
  416. 'https://www.bilibili.com/video/av170001?query1=arg2&spm=114514.1919810&query2=data3',
  417.  
  418. 'https://www.bilibili.com/video/av170001?spm_id_from=114514#hash',
  419. 'https://www.bilibili.com/video/av170001?query1=arg2&spm_id_from=114514#hash1',
  420. 'https://www.bilibili.com/video/av170001?query1=arg2&spm=114514.1919810#hash1',
  421.  
  422. 'https://www.bilibili.com/video/av170001?spm_id_from=114514&query2=data3#hash1',
  423. 'https://www.bilibili.com/video/av170001?spm=114514.1919810&query2=data3#hash1',
  424. ];
  425. for(let i=0;i<test_urls.length;++i){
  426. let el=document.createElement('a');
  427. el.href=test_urls[i];
  428. el.innerHTML=i+1 + '';
  429. document.documentElement.appendChild(el);
  430. }
  431. for(let i=0;i<test_urls.length;++i){
  432. let el=document.createElement('a');
  433. el.href=test_urls[i];
  434. el.innerHTML=i+1 + ' blank';
  435. el.target='_blank';
  436. document.documentElement.appendChild(el);
  437. }
  438. */
  439.  
  440. })(window);
  441.  
  442. })();

QingJ © 2025

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