// ==UserScript== // @name B站(bilibili)链接参数净化 // @namespace You Boy // @version 1.3 // @description 清理B站链接追踪参数,支持自定义、批量添加和重置规则,性能最优,无页面侵入。 // @author You Boy // @match *://*.bilibili.com/* // @grant GM_addStyle // @grant GM_getValue // @grant GM_setValue // @grant GM_registerMenuCommand // @run-at document-start // @license MIT // @downloadURL none // ==/UserScript== (function () { 'use strict'; // --- 1. Greasemonkey 沙箱环境 --- // 此部分负责GM API调用,并作为注入脚本的入口。 // 默认参数 感谢脚本 https://greasyfork.org/zh-CN/scripts/393995 提供 const DEFAULT_PARAMS = [ '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', 'tab', 'is_story_h5', 'share_from', 'plat_id', '-Arouter', 'spmid', 'live_from', 'launch_id', 'hotRank', 'trackid', ]; const paramsToInject = GM_getValue('customParams', DEFAULT_PARAMS); // 监听注入脚本的保存请求 (页面环境 -> 沙箱) window.addEventListener('blc-save-params', (event) => { GM_setValue('customParams', event.detail); }); // 注册菜单命令,点击时通知注入脚本打开设置面板 GM_registerMenuCommand('设置', () => { window.dispatchEvent(new CustomEvent('blc-open-settings')); }); // 注入UI样式 GM_addStyle(` #blc-settings-panel { display: none; position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); z-index: 10000; width: 450px; max-width: 90vw; height: 400px; max-height: 80vh; background: #fff; border-radius: 8px; box-shadow: 0 5px 15px rgba(0,0,0,0.3); flex-direction: column; } .blc-header { display: flex; justify-content: space-between; align-items: center; padding: 10px 15px; border-bottom: 1px solid #eee; flex-shrink: 0; } .blc-header h3 { margin: 0; font-size: 16px; } #blc-close-btn { font-size: 24px; cursor: pointer; color: #999; } .blc-add { display: flex; padding: 10px 15px; border-bottom: 1px solid #eee; flex-shrink: 0; } #blc-new-param { flex-grow: 1; border: 1px solid #ccc; border-radius: 4px; padding: 8px; margin-right: 10px; } #blc-add-btn, #blc-reset-btn { border: none; padding: 8px 15px; border-radius: 4px; cursor: pointer; } #blc-add-btn { background-color: #00a1d6; color: #fff; margin-right: 5px; } #blc-add-btn:hover { background-color: #00b5e5; } #blc-reset-btn { background-color: #f1f1f1; color: #333; border: 1px solid #ccc; } #blc-reset-btn:hover { background-color: #e0e0e0; } .blc-list { padding: 10px; overflow-y: auto; flex-grow: 1; display: flex; flex-wrap: wrap; align-content: flex-start; } .blc-param { display: inline-flex; align-items: center; background: #eef0f2; color: #333; padding: 5px 10px; border-radius: 15px; margin: 5px; font-size: 14px; } .blc-param span { margin-right: 8px; } .blc-delete { color: #999; cursor: pointer; font-weight: bold; font-size: 16px; } .blc-delete:hover { color: #ff4d4d; } `); // --- 2. 待注入的工作代码 --- // 此函数将转换为字符串并注入页面,以突破沙箱限制。 // 注意:此函数内部无法直接调用 GM_* API。 function injectedCode() { // 确保代码在注入后立即执行并拥有独立作用域 (() => { // 通过注入时挂载在script标签上的data属性,获取初始配置 const scriptTag = document.getElementById('blc-injected-script'); const initialParams = JSON.parse(scriptTag.dataset.initialParams); const defaultParams = JSON.parse(scriptTag.dataset.defaultParams); const paramsToRemove = new Set(initialParams); // 向沙箱中的“加载器”发送保存请求 function saveParams() { window.dispatchEvent(new CustomEvent('blc-save-params', { detail: Array.from(paramsToRemove) })); } // --- 核心净化逻辑 --- function cleanUrl(urlString) { if (!urlString || !urlString.startsWith('http')) return urlString; // 登录等重要功能页面不处理 if (urlString.includes('passport.bilibili.com')) return urlString; try { const url = new URL(urlString); let modified = false; const paramsToDelete = []; for (const param of url.searchParams.keys()) { if (paramsToRemove.has(param)) { paramsToDelete.push(param); modified = true; } } if (modified) { paramsToDelete.forEach(p => url.searchParams.delete(p)); return url.toString(); } return urlString; } catch (e) { // 如果URL解析失败,返回原始字符串 return urlString; } } // --- 事件净化策略 --- // 策略1: 鼠标悬停预处理,提升性能 document.addEventListener('mouseover', event => { const link = event.target.closest('a[href]'); if (link && !link.dataset.cleanedHref) { const cleanedHref = cleanUrl(link.href); if (link.href !== cleanedHref) { link.dataset.cleanedHref = cleanedHref; // 缓存净化后的链接 link.href = cleanedHref; } } }, true); // 策略2: 终极点击修复,拦截 mousedown/click/contextmenu 事件 const finalClickFix = e => { const link = e.target.closest('a[href]'); if (link) { // 优先使用缓存,否则实时计算 const cleanedHref = link.dataset.cleanedHref || cleanUrl(link.href); if (link.href !== cleanedHref) { link.href = cleanedHref; } // 阻止B站脚本在点击事件的后续阶段(如mouseup)重新污染链接 e.stopImmediatePropagation(); } }; document.addEventListener('mousedown', finalClickFix, true); document.addEventListener('click', finalClickFix, true); document.addEventListener('contextmenu', finalClickFix, true); // --- 导航补丁 (用于处理SPA页面跳转) --- // 拦截 history API const originalPushState = history.pushState; history.pushState = function (state, title, url) { const cleanedUrl = cleanUrl(url ? url.toString() : ''); return originalPushState.apply(this, [state, title, cleanedUrl]); }; const originalReplaceState = history.replaceState; history.replaceState = function (state, title, url) { const cleanedUrl = cleanUrl(url ? url.toString() : ''); return originalReplaceState.apply(this, [state, title, cleanedUrl]); }; // 拦截 window.open const originalOpen = window.open; window.open = function (url, target, features) { const cleanedUrl = cleanUrl(url ? url.toString() : ''); return originalOpen.apply(this, [cleanedUrl, target, features]); }; // 拦截 Navigation API (较新的浏览器) if (window.navigation) { window.navigation.addEventListener('navigate', e => { if (!e.canIntercept) return; const destinationUrl = e.destination.url; const cleanedUrl = cleanUrl(destinationUrl); if (destinationUrl !== cleanedUrl) { e.preventDefault(); history.replaceState(history.state, '', cleanedUrl); } }); } // --- 懒加载设置面板 --- let settingsPanel = null; function createSettingsPanel() { if (settingsPanel) return; settingsPanel = document.createElement('div'); settingsPanel.id = 'blc-settings-panel'; document.body.appendChild(settingsPanel); settingsPanel.addEventListener('click', e => { const targetId = e.target.id; if (targetId === 'blc-close-btn') { settingsPanel.style.display = 'none'; } else if (targetId === 'blc-add-btn') { addParamsFromInput(); } else if (targetId === 'blc-reset-btn') { if (confirm('确定要重置为默认列表吗?')) { paramsToRemove.clear(); defaultParams.forEach(p => paramsToRemove.add(p)); saveParams(); renderPanelContent(); } } else if (e.target.classList.contains('blc-delete')) { const paramToDelete = e.target.dataset.param; paramsToRemove.delete(paramToDelete); saveParams(); renderPanelContent(); } }); settingsPanel.addEventListener('keydown', e => { if (e.key === 'Enter' && e.target.id === 'blc-new-param') { addParamsFromInput(); } }); } function renderPanelContent() { if (!settingsPanel) return; settingsPanel.innerHTML = `

链接清理参数列表

×
${Array.from(paramsToRemove).sort().map(p => `
${p}×
`).join('')}
`; document.getElementById('blc-new-param').focus(); } function addParamsFromInput() { const input = document.getElementById('blc-new-param'); if (!input) return; const newParams = input.value.split(',').map(p => p.trim()).filter(p => p); if (newParams.length > 0) { newParams.forEach(p => paramsToRemove.add(p)); saveParams(); input.value = ''; renderPanelContent(); } } // --- 初始化 --- // 检查并清理当前页面URL,防止直接打开带参数的链接 const currentUrl = window.location.href; const cleanedPageUrl = cleanUrl(currentUrl); if (currentUrl !== cleanedPageUrl) { history.replaceState(history.state, '', cleanedPageUrl); } // 监听来自“加载器”的打开设置请求 window.addEventListener('blc-open-settings', () => { if (settingsPanel && settingsPanel.style.display !== 'none') { settingsPanel.style.display = 'none'; return; } // 确保body存在后再创建UI if (!document.body) { document.addEventListener('DOMContentLoaded', () => { createSettingsPanel(); renderPanelContent(); settingsPanel.style.display = 'flex'; }); } else { createSettingsPanel(); renderPanelContent(); settingsPanel.style.display = 'flex'; } }); })(); } // --- 3. 执行注入 --- const injectedScript = document.createElement('script'); injectedScript.id = 'blc-injected-script'; // 通过data属性将配置数据“携带”到页面环境 injectedScript.dataset.initialParams = JSON.stringify(paramsToInject); injectedScript.dataset.defaultParams = JSON.stringify(DEFAULT_PARAMS); injectedScript.textContent = `(${injectedCode.toString()})();`; // 尽早注入脚本以抢占B站脚本的API (document.head || document.documentElement).appendChild(injectedScript); injectedScript.remove(); })();