// ==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
// ==/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[0].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
}
});
})();