您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Redirects XCancel.com links to X.com
// ==UserScript== // @name XCancel.com to X.com Redirector // @namespace http://tampermonkey.net/ // @version 1.0 // @description Redirects XCancel.com links to X.com // @author drowned1 // @match *://*/* // @grant none // @run-at document-start // @license MIT // ==/UserScript== (function() { 'use strict'; const sourceDomain = 'xcancel.com'; const targetDomain = 'x.com'; const sourceHrefSelector = `a[href*="${sourceDomain}"]`; // Function to replace links and text within a specific element or node list function replaceInElements(elements) { if (!elements) return; // Ensure elements is iterable (NodeList, Array, or single element) const elementList = (elements instanceof Node || typeof elements.forEach !== 'function') ? [elements] : elements; elementList.forEach(element => { // Only process element nodes if (element.nodeType !== Node.ELEMENT_NODE) return; // Replace href attributes in anchor tags within the element or if it is an anchor itself if (element.matches && element.matches(sourceHrefSelector)) { if (element.href.includes(sourceDomain)) { element.href = element.href.replace(sourceDomain, targetDomain); } } const links = element.querySelectorAll(sourceHrefSelector); links.forEach(link => { if (link.href.includes(sourceDomain)) { link.href = link.href.replace(sourceDomain, targetDomain); } }); // --- Optional Text Replacement (More Performant but still potentially heavy) --- // Consider if text replacement is truly necessary, as it's more complex and performance-intensive. // This version uses TreeWalker scoped to the current element. /* const walker = document.createTreeWalker( element, NodeFilter.SHOW_TEXT, { acceptNode: function(node) { // Skip nodes inside script/style tags and check for the source domain const parentTag = node.parentElement?.tagName?.toLowerCase(); if (parentTag !== 'script' && parentTag !== 'style' && node.nodeValue.includes(sourceDomain)) { return NodeFilter.FILTER_ACCEPT; } return NodeFilter.FILTER_REJECT; } }, false ); let node; while (node = walker.nextNode()) { node.nodeValue = node.nodeValue.replace(new RegExp(sourceDomain.replace('.', '\\.'), 'g'), targetDomain); } */ // --- End Optional Text Replacement --- }); } // Initial run on existing content when DOM is ready function runInitialScan() { // Use querySelectorAll directly on the document for the initial scan const initialLinks = document.querySelectorAll(sourceHrefSelector); initialLinks.forEach(link => { if (link.href.includes(sourceDomain)) { link.href = link.href.replace(sourceDomain, targetDomain); } }); // If enabling text replacement, uncomment the initial text scan: /* replaceInElements(document.body); // Run text replacement on the whole body once initially */ } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', runInitialScan); } else { runInitialScan(); } // Optimized MutationObserver: Process only added nodes const observer = new MutationObserver((mutationsList) => { for (const mutation of mutationsList) { if (mutation.type === 'childList' && mutation.addedNodes.length > 0) { // Process only the nodes that were actually added replaceInElements(mutation.addedNodes); } // Note: CharacterData mutations are ignored here for performance. // If text replacement within existing nodes is crucial, you'd need // to handle mutation.type === 'characterData' carefully, // potentially checking mutation.target and its parent elements. } }); // Observe the body once it exists const observerConfig = { childList: true, subtree: true }; if (document.body) { observer.observe(document.body, observerConfig); } else { // If body doesn't exist yet (e.g., @run-at document-start), wait for it. new MutationObserver((_, obs) => { if (document.body) { observer.observe(document.body, observerConfig); // Also run the initial scan now that the body exists, in case DOMContentLoaded fired early runInitialScan(); obs.disconnect(); // Stop observing the document element } }).observe(document.documentElement, { childList: true }); } // --- Intercept network requests (Optimized with checks) --- const originalOpen = XMLHttpRequest.prototype.open; XMLHttpRequest.prototype.open = function(method, url) { if (url && typeof url === 'string' && url.includes(sourceDomain)) { arguments[1] = url.replace(sourceDomain, targetDomain); } return originalOpen.apply(this, arguments); }; const originalFetch = window.fetch; window.fetch = function(input, init) { let url = input instanceof Request ? input.url : input; let request = input instanceof Request ? input : null; if (typeof url === 'string' && url.includes(sourceDomain)) { const newUrl = url.replace(sourceDomain, targetDomain); if (request) { // Clone Request object with modified URL const newRequestInit = {}; // Manually list properties to clone for broader compatibility ['method', 'headers', 'body', 'mode', 'credentials', 'cache', 'redirect', 'referrer', 'integrity', 'keepalive', 'signal'].forEach(prop => { if (request[prop] !== undefined) { newRequestInit[prop] = request[prop]; } }); arguments[0] = new Request(newUrl, newRequestInit); } else { arguments[0] = newUrl; } } return originalFetch.apply(this, arguments); }; // --- Cleanup --- window.addEventListener('unload', () => { if (observer) { observer.disconnect(); } }); })();