// ==UserScript==
// @name ❀ 浮岚 Bilibili 链接净化
// @namespace https://bento.me/ranburiedbyacat
// @version 1.1.1
// @description 加载前清洁链接,移除跟踪参数。
// @author 嵐 @ranburiedbyacat
// @license CC-BY-NC-SA-4.0
// @match *://*.bilibili.com/*
// @compatible Safari
// @compatible Firefox
// @compatible Chrome
// @icon https://www.bilibili.com/favicon.ico
// @grant none
// @run-at document-start
// ==/UserScript==
(function() {
'use strict';
/**
* ───────────────────────────────────────────────
* ① 冗余参数
* ───────────────────────────────────────────────
*/
const chenruiMama = new Set([
// ────────────── 来源跟踪 ──────────────
'spm_id_from', 'from_source', 'sourceFrom', 'from_spmid', 'csource', 'vd_source', 'source', 'search_source',
'from', 'buvid', 'mid', 'timestamp',
// ────────────── 分享参数 ───────────────
'share_source', 'share_medium', 'share_plat', 'share_session_id', 'share_tag', 'share_from', 'plat_id', 'up_id',
// ────────────── 广告统计 ───────────────
'trackid', 'session_id', 'visit_id', 'unique_k', 'spmid', '-Arouter',
// ────────────── 功能标记 ───────────────
'msource', 'bsource', 'tab', 'is_story_h5', 'hotRank', 'launch_id', 'live_from', 'popular_rank',
]);
/**
* ───────────────────────────────────────────────
* ② URL 解析
* ───────────────────────────────────────────────
*/
function parseURL(str) {
try {
if (typeof str === 'string' && str.includes('.') && !/^[a-z]+:/.test(str)) {
// 以 // 开头则补充协议
str = str.startsWith("//") ? location.protocol + str : str;
}
return new URL(str, location.href);
} catch (e) {
return null;
}
}
/**
* ───────────────────────────────────────────────
* ③ URL 净化
* ───────────────────────────────────────────────
*/
function cleanUrl(urlStr) {
const url = parseURL(urlStr);
if (!url) return urlStr;
if (!/bilibili\.com/.test(url.hostname)) return urlStr;
if (url.hostname.includes('bilibili.tv')) url.hostname = 'www.bilibili.com';
// 删除冗余参数
for (const key of Array.from(url.searchParams.keys())) {
if (chenruiMama.has(key)) {
url.searchParams.delete(key);
}
// 特殊处理 p 参数
if (key === 'p') {
const pVal = parseInt(url.searchParams.get('p'), 10);
if (pVal === 1) {
url.searchParams.delete('p');
}
}
}
// 视频页面强制尾斜杠
if (/^\/video\/BV/i.test(url.pathname) && !url.pathname.endsWith('/')) {
url.pathname += '/';
}
// 视频页面 BV 号,且清洁后没有其他搜索参数时,强制尾斜杠
if (/^\/video\/BV/i.test(url.pathname) && !url.pathname.endsWith('/') && !url.searchParams.toString()) {
url.pathname += '/';
}
return url.toString();
}
/**
* ───────────────────────────────────────────────
* ④ 地址栏即时替换
* ───────────────────────────────────────────────
*/
function replaceLocation(url) {
if (url !== location.href) {
history.replaceState(history.state, '', url);
}
}
replaceLocation(cleanUrl(location.href));
/**
* ───────────────────────────────────────────────
* ⑤ 链接点击拦截
* ───────────────────────────────────────────────
*/
function handleClickCleanHistory(e) {
const a = e.target.closest('a[href*="bilibili.com"]');
if (!a) return;
if (e.button !== 0) return;
e.preventDefault();
const clean = cleanUrl(a.href);
if (a.target !== '_blank' && !e.ctrlKey && !e.metaKey && !e.shiftKey) {
history.replaceState(null, '', clean);
location.assign(clean);
} else {
window.open(clean, '_blank');
}
}
window.addEventListener('click', handleClickCleanHistory, true);
/**
* ───────────────────────────────────────────────
* ⑥ window.open 拦截
* ───────────────────────────────────────────────
*/
const _open = window.open;
window.open = (url, target, features) => _open.call(window, cleanUrl(url), target || '_blank', features);
/**
* ───────────────────────────────────────────────
* ⑦ SPA 导航拦截
* ───────────────────────────────────────────────
*/
['pushState', 'replaceState'].forEach(fn => {
const orig = history[fn];
history[fn] = (...args) => {
if (typeof args[2] === 'string') {
args[2] = cleanUrl(args[2]);
}
return orig.apply(history, args);
};
});
/**
* ───────────────────────────────────────────────
* ⑧ Navigation API 拦截
* ───────────────────────────────────────────────
*/
if (window.navigation) {
window.navigation.addEventListener('navigate', e => {
const newURL = cleanUrl(e.destination.url);
if (newURL !== e.destination.url) {
e.preventDefault();
if (newURL !== location.href) {
history.replaceState(history.state, '', newURL);
}
}
});
}
/**
* ───────────────────────────────────────────────
* ⑨ 动态节点净化
* ───────────────────────────────────────────────
*/
const observer = new MutationObserver(muts => {
for (const m of muts) {
for (const node of m.addedNodes) {
if (node.nodeType !== 1) continue;
const links = node.querySelectorAll?.('a[href*="bilibili.com"]') || [];
for (const a of links) {
a.href = cleanUrl(a.href);
a.removeAttribute('ping');
}
}
}
});
observer.observe(document, { childList: true, subtree: true });
})();