XCancel.com to X.com Redirector

Redirects XCancel.com links to X.com

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==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();
        }
    });

})();