XCancel.com to X.com Redirector

Redirects XCancel.com links to X.com

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 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();
        }
    });

})();