移除链接中的spm跟踪参数
// ==UserScript== // @name spm_Track_Block_Tool // @namespace _s7util__ // @version 0.7.5 // @description:en Remove [spm] track paramter in URL // @description 移除链接中的spm跟踪参数 // @author shc0743 // @icon data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw== // @grant none // @license GPL-3.0 // @supportURL https://github.com/shc0743/MyUtility/issues/new // @run-at document-start // @match http*://*.bilibili.com/* // @match http*://*.baidu.com/* // @match http*://*.####.com/* // @match http*://*.taobao.com/* // @match http*://*.alibaba.com/* // @exclude http*://*.paypal.com/* // @exclude http*://*.alipay.com/* // ==/UserScript== /* Description: 说明: This user script removes the spm paramter in <a href> elements. 此脚本移除 <a href> 元素中的spm参数。 If it doesn't work, try refreshing it a few times or wait a while. 若无法生效,请尝试刷新几次或等一会。 Examples: 示例: https://www.bilibili.com/video/av170001?spm_id_from=###### -> https://www.bilibili.com/video/av170001 https://www.bilibili.com/video/av170001?query1=arg2&spm_id_from=######&query2=data3 -> https://www.bilibili.com/video/av170001?query1=arg2&query2=data3 https://www.bilibili.com/video/av170001?spm=######.1919810#hash -> https://www.bilibili.com/video/av170001#hash https://www.bilibili.com/video/av170001?spm=######.1919810&query2=data3#hash1 -> https://www.bilibili.com/video/av170001?query2=data3#hash1 */ (function () { 'use strict'; // Your code here... var track_args_list = [ // 以下是本脚本检测的跟踪参数,检测到后自动去除 // 格式: { 'domain': '应用到哪个网站,只要填顶级域名即可,例如www.baidu.com只要写baidu.com', 'keyword': '跟踪参数的名称' } { 'domain': '*', 'keyword': 'spm' }, { 'domain': '*', 'keyword': 'spm_id_from' }, { 'domain': '*', 'keyword': 'from_source' }, { 'domain': 'bilibili.com', 'keyword': 'from' }, { 'domain': 'bilibili.com', 'keyword': 'seid' }, { 'domain': 'bilibili.com', 'keyword': 'vd_source' }, { 'domain': 'bilibili.com', 'keyword': 'search_source' }, { 'domain': 'baike.baidu.com', 'keyword': 'fr' }, { 'domain': 'alibaba.com', 'keyword': 'tracelog' }, ]; var unwritable_list = [ //// https://greasyfork.org/zh-CN/scripts/443049/discussions/132536 //{ object: window, key: 'goldlog' }, ]; try { for (let i of unwritable_list) { Object.defineProperty(i.object, i.key, { get() { return undefined }, set(value) { return !void (value) }, enumerable: false, configurable: true }); } } catch (error) { console.warn(error); } return (function (global) { //var expr = /\?[\s\S]*spm/i; /** * 检测域名是否匹配,支持域名层级 * @param {String} pattern 主域名 * @param {String} str 要检测的域名,可以和主域名一样,也可以是主域名的子域名 * @returns 匹配则返回true,否则false * @example DomainCheck('baidu.com', 'yiyan.baidu.com') === true * 注意:请自行进行类型检查,如果参数不是String则行为未知 */ const DomainCheck = function (pattern, str) { if (str === pattern) return true; pattern = '.' + pattern; if (str.endsWith(pattern)) return true; return false; }; /** * 去除字符串中的spm参数 * @param {String} str URL to remove spm * @returns 去除spm后的结果 */ const remove_spm = function (str) { // 新版方案,使用 URL 相关api操作 try { const url = new URL(str, globalThis.location.href); // 构造URL对象 // 改用此法后,可轻松处理 //search.bilibili.com/ 之类的不以http(s)开头的链接 // 对于url构造失败的(无效url之类的),回退到旧版基于字符串的方案 const hostname = url.hostname; for (const i of track_args_list) { if (!(i.domain === '*' || DomainCheck(i.domain, hostname))) continue; if (url.searchParams.has(i.keyword)) { // 存在track参数!!! url.searchParams.delete(i.keyword); // 去掉 } } return url.href; // 优雅 } catch (error) { // 回退到旧版方案 return OLD__remove_spm(str); } }; // 以下是旧版方案 // 古语云:代码能跑就不要删 var OLD__remove_spm = function (str) { if (typeof (str) != 'string') return str; var newstr = ''; var len = str.length; // 只去除查询参数部分,避免正常url被替换而导致404 var hash_part_begin = str.indexOf('#'); var query_part_begin = str.indexOf('?'); if (query_part_begin == -1 || (hash_part_begin != -1 && query_part_begin > hash_part_begin)) { return str; } // 没有查询参数或?在#后面,直接返回 newstr = str.substring(0, query_part_begin); var domain = ''; { let index = str.indexOf('://'); if (index + 1) { index = str.indexOf('/', index + 3); if (index + 1) { domain = str.substring(0, index); } } } for (let i = query_part_begin, need_break; i < len; ++i) { for (let j = 0; j < track_args_list.length; ++j) { if (!(track_args_list[j].domain == '*' || domain.indexOf(track_args_list[j].domain) != -1)) { need_break = false; break; } need_break = true; if (track_args_list[j].keyword == str.substring(i, i + track_args_list[j].keyword.length - 0)) { // 检测到 while ((++i) < len) { if (str[i] == '&') { // 不能单独保留一个 & 号 i++; break; // 去掉 } if (str[i] == '#') break; // 保留hash部分 } if (i == len) break; // 越界,直接break,以免url出现undefined } need_break = false; } if (need_break) break; newstr += str[i]; } var _lastchar; for (let i = 0; i < newstr.length; ++i) { _lastchar = newstr[newstr.length - 1]; if (_lastchar == '?' || _lastchar == '&') { // 如果移除后只剩下 ? 或 & newstr = newstr.substring(0, newstr.length - 1); // 去掉 } else break; } // Bug-Fix: // https://example.com/example?q1=arg&spm=123#hash1 // -> https://example.com/example?q1=arg&#hash1 // Invalid URL syntax at ^^ newstr = newstr.replace(/\&\#/igm, '#'); newstr = newstr.replace(/\?\#/igm, '#'); return newstr; } var test_spm = function (str) { const currentDomain = window.location.hostname; for (let tracker of track_args_list) { if (currentDomain.endsWith(tracker.domain) && new RegExp(tracker.keyword, 'i').test(str)) { return true; } } return false; }; var _realwindowopen = window.open; var _realhistorypushState = window.history.pushState; var _realhistoryreplaceState = window.history.replaceState; /*var _link_click_test = function (val) { if (/\#/.test(val)) return true; if (/javascript\:/i.test(val)) return true; return false; }; var _link_click = function (event) { if (_link_click_test(this.href)) return; event.preventDefault(); // 防止被再次加入spm this.href = remove_spm(this.href); _realwindowopen(this.href, this.target || '_self'); return false; };*/ var _link_mouseover = function () { if (test_spm(this.href)) this.href = remove_spm(this.href); }; var link_clean_worker = function (el) { if (test_spm(el.href)) { // 链接已经被加入spm , 需要移除 el.href = remove_spm(el.href); } } var linkclickhandlerinit = function () { var el = document.querySelectorAll('a[href]'); for (let i = el.length - 1; i >= 0; --i) { link_clean_worker(el[i]); } }; try { let wopen = function (url, target, features) { return _realwindowopen.call(window, remove_spm(url), target, features); }; let hp = function (data, title, url) { return _realhistorypushState.call( window.history, data, title, (typeof url === 'string') ? remove_spm(url) : url); }; let hr = function (data, title, url) { return _realhistoryreplaceState.call( window.history, data, title, (typeof url === 'string') ? remove_spm(url) : url); }; wopen.toString = hp.toString = hr.toString = ({ toString() { return 'function () { [native code] }' } }.toString); // 必须定义成 writable 否则一些网站(例如B站收藏夹页面)会出错 Object.defineProperty(window, 'open', { value: wopen, writable: true, enumerable: true, configurable: true }); // 重定义window.open 以阻止弹出窗口中的spm Object.defineProperty(window.history, 'pushState', { value: hp, writable: true, enumerable: true, configurable: true }); // 重定义history.pushState Object.defineProperty(window.history, 'replaceState', { value: hr, writable: true, enumerable: true, configurable: true }); // 重定义history.replaceState } catch (error) { console.warn("This browser doesn't support redefining" + " window.open , so [SpmBlockTool] cannot remove" + " spm in popup window.\nError:", error); } var DOM_observer; let DOM_observer_observe = function () { DOM_observer.observe(document.body, { attributes: true, childList: true, subtree: true }); }; DOM_observer = new MutationObserver(function (args) { //debugger // console.log('DOM changed: ', args); DOM_observer.disconnect(); for (let i of args) { if (i.type == 'attributes') { link_clean_worker(i.target); } else if (i.type == 'childList') { for (let j of i.addedNodes) { link_clean_worker(j); } } } DOM_observer.takeRecords(); DOM_observer_observe(); }); window.addEventListener('DOMContentLoaded', function () { // window.setInterval(linkclickhandlerinit, 5000); new Promise(o => { linkclickhandlerinit(); o() }); // 异步执行 DOM_observer_observe(); }); // 移除当前页面的spm // 当然,实际上spm已经在userscript加载前被发送到服务器, // 所以该功能仅美化url. // 如果要禁用该功能,删除下面一行开头的斜杠。 //if(0) // Remove spm from current page // Of course, in fact, spm has been sent to the server // before userscript is loaded, so this function only beautifies the URL. // If you want to disable this feature, remove the slash // at the beginning of the following line: //if(0) if (test_spm(location.href)) { _realhistoryreplaceState.call(window.history, {}, document.title, remove_spm(location.href)); } // https://greasyfork.org/zh-CN/scripts/443049/discussions/156657 // https://greasyfork.org/zh-CN/scripts/443049/discussions/132536 setInterval(function () { // 确认过了,只是检查页面有没有跟踪参数,不进行大范围DOM访问,性能开销可以忽略 if (test_spm(location.href)) { // 2023.12.31改:鬼知道test_spm里面是什么屎山代码,元旦有时间了重构一下才发现一堆问题, // 又不敢删,斟酌后决定把此处延时由800改为5000 _realhistoryreplaceState.call(window.history, {}, document.title, remove_spm(location.href)); } }, 5000); // 修复####等网站去除goldlog后部分功能异常的bug const fakeGoldlog = { afterRecord() { }, afterSendPV() { }, aplusBridgeName() { }, aplus_pubsub: { handlers: [], pubs: {} }, appendMetaInfo() { }, beforeRecord() { }, beforeSendPV() { }, cdnPath: "//g.alicdn.com", cloneDeep() { }, create() { }, extend() { }, getCdnPath() { }, getCookie() { }, getMetaInfo() { }, getParam() { }, getPvId() { }, isInternational() { }, launch() { }, lver: "8.10.5", nameStorage: window.localStorage, on() { }, pv_context: {}, pvid: "ffffffffffffff", record() { }, recordUdata() { }, req: window.location.href, send() { }, sendPV() { }, setMetaInfo() { }, setPageSPM() { }, spm_ab: ['X12345', 'xxxxxxxxxxxx'] }; const proxiedFakeGoldlog = new Proxy(fakeGoldlog, { set(target, p, newValue, receiver) { console.warn(new TypeError('#### you, alicdn goldlog')); Reflect.set(target, p, newValue, receiver); return true; } }); Object.defineProperty(globalThis, 'goldlog', { get() { return proxiedFakeGoldlog }, set(value) { return true }, enumerable: true, configurable: false }) /* // 测试代码 var test_urls = [ 'https://www.bilibili.com/video/BV18X4y1N7Yh', 'https://www.bilibili.com/video/BV18X4y1N7Yh?spm_id_from=######', 'https://www.bilibili.com/video/BV18X4y1N7Yh?spm=######.1919810', 'https://www.bilibili.com/video/BV18X4y1N7Yh?spm_id_from=######.123', 'https://www.bilibili.com/video/av170001', 'https://www.bilibili.com/video/av170001?spm_id_from=######', 'https://www.bilibili.com/video/av170001?spm=######.1919810', 'https://www.bilibili.com/video/av170001?spm_id_from=######.123', 'https://www.bilibili.com/video/av170001?query1=arg2&spm_id_from=######', 'https://www.bilibili.com/video/av170001?query1=arg2&spm=######.1919810', 'https://www.bilibili.com/video/av170001?query1=arg2&spm_id_from=######.123', 'https://www.bilibili.com/video/av170001?query1=arg2&spm_id_from=######', 'https://www.bilibili.com/video/av170001?query1=arg2&spm=######.1919810', 'https://www.bilibili.com/video/av170001?query1=arg2&spm_id_from=######.123', 'https://www.bilibili.com/video/av170001?query1=arg2&spm_id_from=######&query2=data3', 'https://www.bilibili.com/video/av170001?query1=arg2&spm=######.1919810&query2=data3', 'https://www.bilibili.com/video/av170001?spm_id_from=#######hash', 'https://www.bilibili.com/video/av170001?query1=arg2&spm_id_from=#######hash1', 'https://www.bilibili.com/video/av170001?query1=arg2&spm=######.1919810#hash1', 'https://www.bilibili.com/video/av170001?spm_id_from=######&query2=data3#hash1', 'https://www.bilibili.com/video/av170001?spm=######.1919810&query2=data3#hash1', ]; for(let i=0;i<test_urls.length;++i){ let el=document.createElement('a'); el.href=test_urls[i]; el.innerHTML=i+1 + ''; document.documentElement.appendChild(el); } for(let i=0;i<test_urls.length;++i){ let el=document.createElement('a'); el.href=test_urls[i]; el.innerHTML=i+1 + ' blank'; el.target='_blank'; document.documentElement.appendChild(el); } */ })(window); })();