您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
在全部网页中尽可能删除值为整数或UUID的AFF/REF相关参数和链接,包括POST请求,并处理路径中的AFF链接。
// ==UserScript== // @name Affiliate Cleaner // @name:zh-CN AFF/REF链接自动清理 // @description:zh-CN 在全部网页中尽可能删除值为整数或UUID的AFF/REF相关参数和链接,包括POST请求,并处理路径中的AFF链接。 // @namespace http://tampermonkey.net/ // @version 1.4 // @description Removes affiliate tracking parameters from links, the current URL, cookies, and outgoing POST requests. Handles both query and path-based trackers. // @author 0x7 // @match *://*/* // @grant none // @run-at document-start // @license MIT // ==/UserScript== (function() { 'use strict'; // Matches parameters starting with 'aff' or 'ref' (case-insensitive) const AFFILIATE_PARAM_REGEX = /^(aff|ref)/i; // Matches values that are purely integers const INTEGER_VALUE_REGEX = /^\d+$/; // Matches values that are in UUID format (case-insensitive) const UUID_VALUE_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i; // Matches affiliate patterns embedded in the URL path, like 'aff.php?aff=123/' const AFFILIATE_PATH_REGEX = /aff\.php\?aff=\d+\//i; /** * Checks if a URL parameter key and value match the affiliate tracking pattern for query parameters. * @param {string} key - The parameter key. * @param {string} value - The parameter value. * @returns {boolean} - True if it's an affiliate parameter to be removed. */ const isAffiliateParam = (key, value) => { if (!AFFILIATE_PARAM_REGEX.test(key)) { return false; } // Check if the value is either an integer or a UUID return INTEGER_VALUE_REGEX.test(value) || UUID_VALUE_REGEX.test(value); }; /** * Cleans a URL by removing specified affiliate tracking parameters from the path and query string. * @param {string} urlString - The URL to clean. * @returns {string} - The cleaned URL. */ const cleanUrl = (urlString) => { let currentUrl = urlString; // --- Part 1: Clean Path-based Affiliate Links --- if (AFFILIATE_PATH_REGEX.test(currentUrl)) { currentUrl = currentUrl.replace(AFFILIATE_PATH_REGEX, ''); console.log(`[Affiliate Cleaner] Removed path segment from URL: ${urlString}`); } // --- Part 2: Clean Query-based Affiliate Links --- try { const url = new URL(currentUrl); let paramsChanged = false; const newParams = new URLSearchParams(); url.searchParams.forEach((value, key) => { if (!isAffiliateParam(key, value)) { newParams.append(key, value); } else { paramsChanged = true; console.log(`[Affiliate Cleaner] Removed query param: ${key}=${value} from URL: ${url.hostname}`); } }); if (paramsChanged) { url.search = newParams.toString(); return url.toString(); } // If we are here, either only the path was changed, or nothing was changed. // Returning url.toString() handles both cases correctly. return url.toString(); } catch (e) { // This might happen for "javascript:void(0)" or if path cleaning resulted in an invalid URL. // Return the URL after path cleaning, as it's our best effort. return currentUrl; } }; /** * Cleans the current page's URL in the address bar without reloading. */ const cleanCurrentUrl = () => { const originalUrl = window.location.href; const cleanedUrl = cleanUrl(originalUrl); if (originalUrl !== cleanedUrl) { window.history.replaceState(null, '', cleanedUrl); console.log('[Affiliate Cleaner] Cleaned current page URL.'); } }; /** * Scans and cleans all <a> tags currently in the DOM. */ const cleanAllLinks = () => { document.querySelectorAll('a').forEach(link => { if (link.href) { const originalHref = link.href; const cleanedHref = cleanUrl(originalHref); if (originalHref !== cleanedHref) { link.href = cleanedHref; } } }); }; /** * Removes cookies whose names match the affiliate pattern. * This is a best-effort attempt, as domain/path specifics can be tricky. */ const cleanCookies = () => { const cookies = document.cookie.split(';'); for (const cookie of cookies) { const eqPos = cookie.indexOf('='); const name = eqPos > -1 ? cookie.substr(0, eqPos).trim() : cookie.trim(); // This part is interpretive: we assume the cookie *name* starts with 'aff' or 'ref'. if (AFFILIATE_PARAM_REGEX.test(name)) { const domain = window.location.hostname; const path = '/'; // Expire the cookie by setting its date to the past. document.cookie = `${name}=; expires=Thu, 01 Jan 1970 00:00:00 GMT; domain=${domain}; path=${path};`; // Attempt to remove from parent domain as well const parentDomain = domain.substring(domain.indexOf('.') + 1); document.cookie = `${name}=; expires=Thu, 01 Jan 1970 00:00:00 GMT; domain=${parentDomain}; path=${path};`; console.log(`[Affiliate Cleaner] Removed cookie: ${name}`); } } }; /** * Cleans data objects for POST requests (FormData, URLSearchParams, or JSON). * @param {*} data - The data to be sent. * @returns {*} - The cleaned data. */ const cleanRequestData = (data) => { if (data instanceof URLSearchParams || data instanceof FormData) { const keysToDelete = []; for (const [key, value] of data.entries()) { if (typeof value === 'string' && isAffiliateParam(key, value)) { keysToDelete.push(key); } } keysToDelete.forEach(key => { data.delete(key); console.log(`[Affiliate Cleaner] Removed param from POST data: ${key}`); }); } else if (typeof data === 'string') { try { // Attempt to parse as JSON const jsonData = JSON.parse(data); let dataChanged = false; for (const key in jsonData) { if (Object.prototype.hasOwnProperty.call(jsonData, key)) { const value = jsonData[key]; if ((typeof value === 'number' || typeof value === 'string') && isAffiliateParam(key, String(value))) { delete jsonData[key]; dataChanged = true; console.log(`[Affiliate Cleaner] Removed param from JSON POST data: ${key}`); } } } if (dataChanged) { return JSON.stringify(jsonData); } } catch (e) { // Not a JSON string, could be URL-encoded string. const params = new URLSearchParams(data); const cleanedParams = cleanUrl(`http://dummy.com?${params.toString()}`).split('?')[1] || ''; if (cleanedParams !== params.toString()) { return cleanedParams; } } } return data; }; /** * Intercept fetch requests to clean their body. */ const originalFetch = window.fetch; window.fetch = function(input, init) { if (init && init.body && init.method && init.method.toUpperCase() === 'POST') { init.body = cleanRequestData(init.body); } return originalFetch.apply(this, arguments); }; /** * Intercept XMLHttpRequest to clean the data sent. */ const originalSend = XMLHttpRequest.prototype.send; XMLHttpRequest.prototype.send = function(body) { if (body) { body = cleanRequestData(body); } return originalSend.apply(this, [body]); }; // --- Main Execution --- // 1. Clean the URL as soon as the script runs. This handles the 302 redirect case upon arrival. cleanCurrentUrl(); cleanCookies(); // 2. When the initial DOM is loaded, clean all links. document.addEventListener('DOMContentLoaded', () => { cleanAllLinks(); }); // 3. Set up a MutationObserver to clean links added to the page dynamically. const observer = new MutationObserver((mutations) => { mutations.forEach((mutation) => { if (mutation.addedNodes.length) { mutation.addedNodes.forEach(node => { // We only care about element nodes if (node.nodeType === 1) { // Check if the node itself is a link if (node.tagName === 'A' && node.href) { node.href = cleanUrl(node.href); } // Check for any links within the added node node.querySelectorAll('a').forEach(link => { if (link.href) { link.href = cleanUrl(link.href); } }); } }); } }); }); // Start observing the entire document body for changes. // Use a try-catch in case the body isn't ready immediately on some pages. const observeBody = () => { if (document.body) { observer.observe(document.body, { childList: true, subtree: true }); } else { // If body is not available, wait for it. window.addEventListener('DOMContentLoaded', () => { observer.observe(document.body, { childList: true, subtree: true }); }); } }; observeBody(); const origSetAttribute = Element.prototype.setAttribute; Element.prototype.setAttribute = function(name, value) { if (this.tagName === 'A' && name === 'href' && typeof value === 'string') { value = cleanUrl(value); } return origSetAttribute.call(this, name, value); }; const hrefDesc = Object.getOwnPropertyDescriptor(HTMLAnchorElement.prototype, 'href'); Object.defineProperty(HTMLAnchorElement.prototype, 'href', { set: function(v) { hrefDesc.set.call(this, cleanUrl(v)); }, get: hrefDesc.get }); cleanAllLinks(); })();