spm_Track_Block_Tool

移除链接中的spm跟踪参数

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==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*://*.cctv.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=114514
    -> https://www.bilibili.com/video/av170001

    https://www.bilibili.com/video/av170001?query1=arg2&spm_id_from=114514&query2=data3
    -> https://www.bilibili.com/video/av170001?query1=arg2&query2=data3

    https://www.bilibili.com/video/av170001?spm=114514.1919810#hash
    -> https://www.bilibili.com/video/av170001#hash

    https://www.bilibili.com/video/av170001?spm=114514.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);

  // 修复cctv等网站去除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('Fuck 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=114514',
        'https://www.bilibili.com/video/BV18X4y1N7Yh?spm=114514.1919810',
        'https://www.bilibili.com/video/BV18X4y1N7Yh?spm_id_from=114514.123',

        'https://www.bilibili.com/video/av170001',
        'https://www.bilibili.com/video/av170001?spm_id_from=114514',
        'https://www.bilibili.com/video/av170001?spm=114514.1919810',
        'https://www.bilibili.com/video/av170001?spm_id_from=114514.123',

        'https://www.bilibili.com/video/av170001?query1=arg2&spm_id_from=114514',
        'https://www.bilibili.com/video/av170001?query1=arg2&spm=114514.1919810',
        'https://www.bilibili.com/video/av170001?query1=arg2&spm_id_from=114514.123',
        'https://www.bilibili.com/video/av170001?query1=arg2&spm_id_from=114514',
        'https://www.bilibili.com/video/av170001?query1=arg2&spm=114514.1919810',
        'https://www.bilibili.com/video/av170001?query1=arg2&spm_id_from=114514.123',

        'https://www.bilibili.com/video/av170001?query1=arg2&spm_id_from=114514&query2=data3',
        'https://www.bilibili.com/video/av170001?query1=arg2&spm=114514.1919810&query2=data3',

        'https://www.bilibili.com/video/av170001?spm_id_from=114514#hash',
        'https://www.bilibili.com/video/av170001?query1=arg2&spm_id_from=114514#hash1',
        'https://www.bilibili.com/video/av170001?query1=arg2&spm=114514.1919810#hash1',

        'https://www.bilibili.com/video/av170001?spm_id_from=114514&query2=data3#hash1',
        'https://www.bilibili.com/video/av170001?spm=114514.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);

})();