// ==UserScript== // @name Bilibili 干净链接 // @namespace Motoori Kashin // @version 2.0.5 // @description 去除bilibili链接中不需要的参数,如spm_id_from/from_sourse/from/等,还地址栏以清白干净 // @author Motoori Kashin // @match *://*.bilibili.com/* // @grant none // @run-at document-start // @downloadURL none // ==/UserScript== (function () { class UrlFormat { /** 去除参数和锚的基链接 */ base = undefined; /** 查询参数转化的对象 */ searchParams = new Proxy({}, { get: (t, p) => t[p] ? decodeURIComponent(t[p]) : t[p], set: (t, p, v) => { t[p] = v ? encodeURIComponent(v) : v; return true } }); /** 锚 */ hash = undefined; /** 锚中的参数 */ hashParams = new Proxy({}, { get: (t, p) => t[p] ? decodeURIComponent(t[p]) : t[p], set: (t, p, v) => { t[p] = v ? encodeURIComponent(v) : v; return true } }); /** 所有参数(包括锚中的参数)。 */ params() { return new Proxy({ ...this.searchParams, ...this.hashParams }, { set: (t, p, v) => { t[p] = v; (Reflect.has(this.hashParams, p) ? this.hashParams : this.searchParams)[p] = v ? encodeURIComponent(v) : v; return true; }, deleteProperty: (t, p) => { delete t[p]; delete (Reflect.has(this.hashParams, p) ? this.hashParams : this.searchParams)[p]; return true; } }); } /** * 格式化URL * @param url URL字符串 */ constructor(url) { try { // 原生URL处理函数要求太严格,无法处理自定义链接 url = new URL(url).href; } catch (e) { } const one = url.split("#"); // 分离锚 const two = one[0].split("?"); // 分离参数 this.base = two[0]; // 分离基链接 one.shift(); two.shift(); // 参数转对象 if (two[0]) { two[0].split("&").forEach(d => { const arr = d.split("="); this.searchParams[arr.shift()] = arr.join("="); }); } // 锚处理 if (one[0] || one[0] === "") { const three = one.join("#").split("?"); this.hash = three[0]; three.shift(); // 锚参数转对象 if (three[0]) { three[0].split("&").forEach(d => { const arr = d.split("="); this.searchParams[arr.shift()] = arr.join("="); }); } } } /** 拼合链接 */ toJSON() { const base = []; // 基栈 this.base && base.push(this.base); // 基链接 // 参数 const searchParams = Object.entries(this.searchParams).reduce((s, d) => { d[1] !== null && d[1] !== undefined && s.push(d.join("=")); return s; }, []).join("&"); searchParams && base.push(searchParams); const searchParam = base.join("?"); // 含参基链 const hash = []; // 锚栈 this.hash && hash.push(this.hash); const hashParams = Object.entries(this.hashParams).reduce((s, d) => { d[1] !== null && d[1] !== undefined && s.push(d.join("=")); return s; }, []).join("&"); hashParams && hash.push(hashParams); const hashParam = hash.join("?"); // 含参锚 const result = []; // 结果栈 searchParam && result.push(searchParam); hashParam && result.push(hashParam); if (this.hash === "") result.push(""); // 空锚 return result.join("#"); } } /** 垃圾参数 */ const paramsSet = new Set([ 'spm_id_from', 'from_source', 'msource', 'bsource', 'seid', 'source', 'session_id', 'visit_id', 'sourceFrom', 'from_spmid', 'share_source', 'share_medium', 'share_plat', 'share_session_id', 'share_tag', 'unique_k', "csource", "vd_source" ]); /** 节点监听暂存 */ const nodelist = []; /** * 清理url * @param str 原url * @returns 新url */ function clean(str) { const url = new UrlFormat(str); const params = url.params(); paramsSet.forEach(d => { Reflect.deleteProperty(params, d); }); // 非参数型bv号转化为av号; return url.toJSON() } /** 地址备份 */ let locationBackup; /** 处理地址栏 */ function cleanLocation() { const { href } = location; if (href === locationBackup) return; replaceUrl(locationBackup = clean(href)); } /** 处理href属性 */ function anchor(list) { list.forEach(d => { if (!d.href) return; d.href.includes("bilibili.tv") && (d.href = d.href.replace("bilibili.tv", "bilibili.com")); // tv域名失效 d.href = clean(d.href); }); } /** 检查a标签 */ function click(e) { // 代码copy自B站spm.js var f = e.target; for (; f && "A" !== f.tagName;) { f = f.parentNode } if ("A" !== (null == f ? void 0 : f.tagName)) { return } anchor([f]); } /** * 修改当前URL而不出发重定向 * **无法跨域操作!** * @param url 新URL */ function replaceUrl(url) { window.history.replaceState(window.history.state, "", url); } cleanLocation(); // 及时处理地址栏 // 处理注入的节点 let timer = 0; observerAddedNodes((node) => { clearTimeout(timer); timer = setTimeout(() => { cleanLocation(); anchor(document.querySelectorAll("a")); }); }); // 处理点击事件 window.addEventListener("click", click, !1); // 处理右键菜单 window.addEventListener("contextmenu", click, !1); // 页面载入完成 document.addEventListener("load", ()=>anchor(document.querySelectorAll("a")), !1); /** * 注册节点添加监听 * **监听节点变动开销极大,如非必要请改用其他方法并且用后立即销毁!** * @param callback 添加节点后执行的回调函数 * @returns 注册编号,用于使用`removeObserver`销毁监听 */ function observerAddedNodes(callback) { try { if (typeof callback === "function") nodelist.push(callback); return nodelist.length - 1; } catch (e) { console.error(e) } } const observe = new MutationObserver(d => d.forEach(d => { d.addedNodes[0] && nodelist.forEach(async f => { try { f(d.addedNodes[0]) } catch (e) { console.error(d) } }) })); observe.observe(document, { childList: true, subtree: true }); window.open = ((__open__) => { return (url, name, params) => { __open__(clean(url), name, params) } })(window.open) window.navigation?.addEventListener('navigate', e => { const newURL = clean(e.destination.url) if(e.destination.url!=newURL) { e.preventDefault(); // 返回前还是先阻止原事件吧 if(newURL == window.location.href) return // 如果清理后和原来一样就直接返回 // 否则就处理清理后的链接 window.history.replaceState(window.history.state, "", newURL) return } }); })();