// ==UserScript== // @name Universal Link Cleaner // @name:zh-CN 通用链接净化器 // @name:zh-TW 通用連結淨化器 // @name:ja ユニバーサルリンククリーナー // @name:ko 유니버설 링크 클리너 // @name:es Limpiador Universal de Enlaces // @name:fr Nettoyeur Universel de Liens // @name:de Universeller Link-Reiniger // @name:ru Универсальный очиститель ссылок // @namespace https://github.com/cruzclane/universal-link-cleaner // @version 3.0.0 // @author deepseek // @description Automatically remove tracking parameters from URLs across multiple websites for cleaner links and better privacy // @description:zh-CN 自动清理各大网站链接中的追踪参数,保护隐私,让链接更干净 // @description:zh-TW 自動清理各大網站連結中的追蹤參數,保護隱私,讓連結更乾淨 // @description:ja 複数のWebサイトからトラッキングパラメータを自動削除し、プライバシーを保護、リンクをクリーンにします // @description:ko 여러 웹사이트의 URL에서 추적 매개변수를 자동으로 제거하여 개인정보를 보호하고 링크를 깔끔하게 유지합니다 // @description:es Elimina automáticamente los parámetros de seguimiento de las URL en múltiples sitios web para una mejor privacidad // @description:fr Supprime automatiquement les paramètres de suivi des URL sur plusieurs sites web pour une meilleure confidentialité // @description:de Entfernt automatisch Tracking-Parameter aus URLs auf mehreren Websites für mehr Privatsphäre // @description:ru Автоматически удаляет параметры отслеживания из URL на нескольких сайтах для лучшей конфиденциальности // @homepage https://github.com/cruzclane/universal-link-cleaner // @homepageURL https://github.com/cruzclane/universal-link-cleaner // @supportURL https://github.com/cruzclane/universal-link-cleaner/issues // @license MIT // @icon https://www.google.com/s2/favicons?domain=github.com // @match *://*/* // @grant none // @run-at document-start // @compatible chrome 80+ // @compatible firefox 75+ // @compatible edge 80+ // @compatible safari 14+ // @note 如果发现新网站需要支持,请在GitHub提交Issue或PR // @note:zh-CN 如果发现新网站需要支持,请在GitHub提交Issue或PR // @downloadURL https://update.greasyfork.icu/scripts/569607/Universal%20Link%20Cleaner.user.js // @updateURL https://update.greasyfork.icu/scripts/569607/Universal%20Link%20Cleaner.meta.js // ==/UserScript== (function () { 'use strict'; /** * 站点配置 * 可以添加更多网站的规则 */ const SITE_CONFIGS = [ { // Bilibili 规则 domains: ['bilibili.com', 'bilibili.tv'], excludeDomains: ['passport.bilibili.com'], // 排除的域名 params: 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', 'tab', 'is_story_h5', 'share_from', 'plat_id', '-Arouter', 'spmid', 'utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content' ]), cleanUrl: (url, urlObj) => { // 特定站点的额外处理 if (urlObj.hostname.includes('bilibili.tv')) { urlObj.hostname = urlObj.hostname.replace('bilibili.tv', 'bilibili.com'); } return urlObj; } }, { // 淘宝/天猫规则 domains: ['taobao.com', 'tmall.com'], excludeDomains: ['login.taobao.com', 'passport.tmall.com'], params: new Set([ 'spm', 'spm', 'scm', 'skuId', 'trackInfo', 'ut_sk', 'sourceType', 'suid', 'shareUid', 'un', 'share_crt', 'utm', 'abbucket' ]) }, { // 京东规则 domains: ['jd.com', 'jd.hk'], excludeDomains: ['passport.jd.com'], params: new Set([ 'utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'jd_pop', 'abt', 'cu', 'scm', 'spread', 'resourceType', 'resourceValue', 'keyword', 'referring' ]) }, { // 知乎规则 domains: ['zhihu.com'], params: new Set([ 'utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'spm', 'source', 'share', 'campaign', 'wechatShare' ]) }, { // 豆瓣规则(保留原有支持) domains: ['douban.com'], params: new Set([ 'utm_source', 'utm_medium', 'utm_campaign', 'from', 'spm', 'track','_spm_id' ]) }, { // YouTube规则 domains: ['youtube.com', 'youtu.be'], params: new Set([ 'utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'si', 'feature', 'list', 'index', 'pp', 'source' ]) }, { // Twitter/X规则 domains: ['twitter.com', 'x.com'], params: new Set([ 'utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 's', 't', 'i', 'ref_src', 'ref_url' ]) } ]; /** 全局默认过滤参数(适用于所有网站) */ const DEFAULT_PARAMS = new Set([ 'utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'utm_id', 'utm_source_platform', 'utm_creative_format', 'utm_marketing_tactic', 'fbclid', 'gclid', 'msclkid', 'twclid', 'li_fat_id', 'mc_cid', 'mc_eid', '_ga', '_gl', 'utm_debug', 'utm_pubreferrer', 'utm_reader', 'utm_referrer', 'utm_user', 'utm_viz_id', 'wt_mc', 'yclid', 'igshid', 'ref', 'source', 'via', 'action', 'user', 'userId', 'shareId' ]); /** * 判断URL是否属于某个站点规则 * @param {URL} urlObj URL对象 * @returns {Object|null} 匹配的站点规则,没有则返回null */ function getMatchingConfig(urlObj) { const hostname = urlObj.hostname; for (const config of SITE_CONFIGS) { // 检查域名匹配 const domainMatch = config.domains.some(domain => hostname.includes(domain)); if (!domainMatch) continue; // 检查排除域名 if (config.excludeDomains) { const excludeMatch = config.excludeDomains.some(domain => hostname.includes(domain)); if (excludeMatch) continue; } return config; } return null; } /** * 合并参数集合 * @param {Set} defaultSet 默认参数集合 * @param {Set} customSet 自定义参数集合 * @returns {Set} 合并后的参数集合 */ function mergeParams(defaultSet, customSet) { return new Set([...defaultSet, ...(customSet || [])]); } /** * 判断字符串是否为有效URL * @param {string} url 要判断的字符串 * @param {string} base 基础URL * @returns {URL|false} URL对象或false */ function isValidURL(url, base) { try { if (typeof url === "string" && /^[\W\w]+\.[\W\w]+/.test(url) && !/^[a-z]+:/.test(url)) { // 处理省略协议头的情况 const str = url.startsWith("//") ? "" : "//"; url = location.protocol + str + url; } return new URL(url, base); } catch (e) { return false; } } /** * 清理URL参数 * @param {string} str 原URL * @returns {string} 清理后的URL */ function clean(str) { const urlObj = isValidURL(str); if (!urlObj) return str; // 获取匹配的站点配置 const config = getMatchingConfig(urlObj); // 如果没有匹配的配置,只清理默认参数 if (!config) { let changed = false; DEFAULT_PARAMS.forEach(param => { if (urlObj.searchParams.has(param)) { urlObj.searchParams.delete(param); changed = true; } }); return changed ? urlObj.toString() : str; } // 有匹配配置,合并默认参数和自定义参数 const allParams = mergeParams(DEFAULT_PARAMS, config.params); // 清理参数 let changed = false; allParams.forEach(param => { if (urlObj.searchParams.has(param)) { urlObj.searchParams.delete(param); changed = true; } }); // 执行特定站点的额外清理 if (config.cleanUrl) { const processedObj = config.cleanUrl(str, urlObj); if (processedObj instanceof URL) { return processedObj.toString(); } } return changed ? urlObj.toString() : str; } /** 地址备份 */ let locationBackup; /** 处理地址栏 */ function cleanLocation() { const { href } = location; if (href === locationBackup) return; const cleanedHref = clean(href); if (cleanedHref !== href) { replaceUrl(locationBackup = cleanedHref); } } /** 处理a标签的href属性 */ function processAnchors(anchors) { anchors.forEach(anchor => { if (!anchor.href) return; const cleanedHref = clean(anchor.href); if (cleanedHref !== anchor.href) { anchor.href = cleanedHref; } }); } /** 点击事件处理 */ function handleClick(e) { let target = e.target; for (; target && target.tagName !== "A";) { target = target.parentNode; } if (target && target.tagName === "A") { processAnchors([target]); } } /** * 修改当前URL而不触发重定向 * @param {string} url 新URL */ function replaceUrl(url) { if (url !== window.location.href) { window.history.replaceState(window.history.state, "", url); } } // 初始化处理 cleanLocation(); // 处理动态添加的节点 let debounceTimer; const observer = new MutationObserver(() => { clearTimeout(debounceTimer); debounceTimer = setTimeout(() => { cleanLocation(); processAnchors(document.querySelectorAll("a")); }, 100); }); observer.observe(document, { childList: true, subtree: true }); // 事件监听 window.addEventListener("click", handleClick, false); window.addEventListener("contextmenu", handleClick, false); document.addEventListener("DOMContentLoaded", () => { processAnchors(document.querySelectorAll("a")); }); // 拦截 window.open window.open = ((originalOpen) => { return function(url, name, params) { const cleanedUrl = clean(url); return originalOpen.call(this, cleanedUrl, name, params); }; })(window.open); // 处理导航事件(如果支持) if (window.navigation) { window.navigation.addEventListener('navigate', (e) => { const newURL = clean(e.destination.url); if (e.destination.url !== newURL) { e.preventDefault(); if (newURL !== window.location.href) { window.history.replaceState(window.history.state, "", newURL); } } }); } console.log('通用链接净化器已加载,当前站点配置数:', SITE_CONFIGS.length); })();