spm_Track_Block_Tool

移除链接中的spm跟踪参数

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

您需要先安装一个扩展,例如 篡改猴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.6.1
// @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': '*', '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': '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) { void (value) },
                enumerable: false,
                configurable: true
            });
        }
    }
    catch (error) {
        console.warn(error);
    }

return (function (global) {

    //var expr = /\?[\s\S]*spm/i;

    /**
     * 去除字符串中的spm参数
     * @param {String} str URL to remove spm
     * @returns 去除spm后的结果
     */
    var 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,
                remove_spm(url));
        };
        let hr = function (data, title, url) {
            return _realhistoryreplaceState.call(
                window.history, data, title,
                remove_spm(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)) {
            _realhistoryreplaceState.call(window.history,
                {}, document.title,
                remove_spm(location.href));
        }
    }, 800);

    /*
    // 测试代码
    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);

})();