Link Collector - Smart Magnet Link Tool

Smart link collector with localStorage persistence, SPA support, and multi-protocol detection. Supports magnet links, eD2k links, and more!

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         Link Collector - Smart Magnet Link Tool
// @namespace    https://github.com/
// @version      2.1
// @description  Smart link collector with localStorage persistence, SPA support, and multi-protocol detection. Supports magnet links, eD2k links, and more!
// @author       Link Collector Tools
// @match        *://*/*
// @grant        GM_setClipboard
// @grant        unsafeWindow
// @license      MIT
// ==/UserScript==


(function() {
    'use strict';

    // Global state management
    var observer = null;
    var button = null;
    var STORAGE_KEY = 'link-collector-storage';

    // Link schemas configuration - easily extensible
    var LINK_SCHEMAS = {
        magnet: {
            pattern: /magnet:\?xt=[^\s<>"']+/gi,
            name: 'Magnet Link',
            icon: '🧲',
            color: '#2196F3'
        },
        ed2k: {
            pattern: /ed2k:\/\/\|\S+\|/gi,
            name: 'eD2k Link',
            icon: '🔗',
            color: '#FF9800'
        }
        // Add more schemas here in the future
        // Example:
        // thunder: {
        //     pattern: /thunder:\/\/\S+/gi,
        //     name: 'Thunder Link',
        //     icon: '⚡',
        //     color: '#4CAF50'
        // }
    };

    // Get all link patterns combined
    function getAllLinkPatterns() {
        return Object.values(LINK_SCHEMAS).map(schema => schema.pattern);
    }

    // Get all link patterns as a single regex
    function getCombinedPattern() {
        var patterns = Object.values(LINK_SCHEMAS).map(schema => schema.pattern.source);
        return new RegExp('(' + patterns.join('|') + ')', 'gi');
    }

    // Identify link schema from URL
    function identifyLinkSchema(linkUrl) {
        for (var [schemaType, schema] of Object.entries(LINK_SCHEMAS)) {
            if (schema.pattern.test(linkUrl)) {
                return { type: schemaType, schema: schema };
            }
        }
        return null;
    }

    // Initialize data from localStorage
    function initializeFromStorage() {
        try {
            var stored = localStorage.getItem(STORAGE_KEY);
            if (stored) {
                var data = JSON.parse(stored);
                return {
                    discoveredLinks: new Set(data.discoveredLinks || []),
                    copiedLinks: new Set(data.copiedLinks || []),
                    lastUpdated: data.lastUpdated || Date.now()
                };
            }
        } catch (e) {
            console.warn('Failed to load from localStorage:', e);
        }
        return {
            discoveredLinks: new Set(),
            copiedLinks: new Set(),
            lastUpdated: Date.now()
        };
    }

    // Save state to localStorage
    function saveToStorage(discoveredLinks, copiedLinks) {
        try {
            var data = {
                discoveredLinks: Array.from(discoveredLinks),
                copiedLinks: Array.from(copiedLinks),
                lastUpdated: Date.now(),
                url: window.location.href
            };
            localStorage.setItem(STORAGE_KEY, JSON.stringify(data));
        } catch (e) {
            console.warn('Failed to save to localStorage:', e);
            // If storage is full, clear old data and try again
            try {
                localStorage.removeItem(STORAGE_KEY);
                var data = {
                    discoveredLinks: Array.from(discoveredLinks),
                    copiedLinks: Array.from(copiedLinks),
                    lastUpdated: Date.now(),
                    url: window.location.href
                };
                localStorage.setItem(STORAGE_KEY, JSON.stringify(data));
            } catch (e2) {
                console.warn('LocalStorage is not available or full');
            }
        }
    }

    // Clear old storage data (older than 15 minutes of inactivity)
    function clearOldStorageData() {
        try {
            var stored = localStorage.getItem(STORAGE_KEY);
            if (stored) {
                var data = JSON.parse(stored);
                // Use 15 minutes for localStorage to prevent stale data
                if (data.lastUpdated && Date.now() - data.lastUpdated > 900000) { // 15 minutes
                    localStorage.removeItem(STORAGE_KEY);
                    return true;
                }
            }
        } catch (e) {
            console.warn('Failed to clear old storage data:', e);
        }
        return false;
    }

    // Check if storage is expired
    function isStorageExpired() {
        try {
            var stored = localStorage.getItem(STORAGE_KEY);
            if (stored) {
                var data = JSON.parse(stored);
                return data.lastUpdated && Date.now() - data.lastUpdated > 900000; // 15 minutes
            }
        } catch (e) {
            console.warn('Failed to check storage expiry:', e);
        }
        return false;
    }

    // Get storage age in minutes
    function getStorageAge() {
        try {
            var stored = localStorage.getItem(STORAGE_KEY);
            if (stored) {
                var data = JSON.parse(stored);
                if (data.lastUpdated) {
                    return Math.floor((Date.now() - data.lastUpdated) / 60000);
                }
            }
        } catch (e) {
            console.warn('Failed to get storage age:', e);
        }
        return 0;
    }

    // Get storage size information
    function getStorageInfo() {
        try {
            var stored = localStorage.getItem(STORAGE_KEY);
            var size = stored ? new Blob([stored]).size : 0;
            var ageMinutes = getStorageAge();
            var expired = isStorageExpired();

            return {
                size: size,
                sizeText: size > 1024 ? (size / 1024).toFixed(2) + ' KB' : size + ' bytes',
                itemCount: stored ? JSON.parse(stored).discoveredLinks.length : 0,
                ageMinutes: ageMinutes,
                expired: expired
            };
        } catch (e) {
            return { size: 0, sizeText: '0 bytes', itemCount: 0, ageMinutes: 0, expired: false };
        }
    }

    // Initialize state from storage
    var state = initializeFromStorage();
    var discoveredLinks = state.discoveredLinks;
    var copiedLinks = state.copiedLinks;

    // Wait for DOM to be ready
    function waitForDOM() {
        if (document.readyState === 'loading') {
            document.addEventListener('DOMContentLoaded', initScript);
        } else {
            initScript();
        }
    }

    // Extract all supported link types from text content
    function extractLinksFromText(text) {
        var combinedPattern = getCombinedPattern();
        var matches = text.match(combinedPattern) || [];
        return matches;
    }

    // Extract magnet links from text content (legacy function for compatibility)
    function extractMagnetLinksFromText(text) {
        return extractLinksFromText(text).filter(function(link) {
            return link.startsWith('magnet:');
        });
    }

    // Scan page for all supported link types and update state
    function scanForMagnetLinks() {
        var newLinksFound = false;

        // Priority 1: Find supported links in actual <a> tags
        Object.keys(LINK_SCHEMAS).forEach(function(schemaType) {
            var links = document.querySelectorAll('a[href^="' + schemaType + ':"]');
            links.forEach(function(link) {
                if (!discoveredLinks.has(link.href)) {
                    discoveredLinks.add(link.href);
                    newLinksFound = true;
                    var schemaInfo = LINK_SCHEMAS[schemaType];
                    console.log('Found ' + schemaInfo.name + ' in <a> tag:', link.href);
                }
            });
        });

        // Priority 2: Find supported links in text content (lower priority)
        // Get all text nodes that might contain supported links
        var textNodes = [];
        var walker = document.createTreeWalker(
            document.body,
            NodeFilter.SHOW_TEXT,
            {
                acceptNode: function(node) {
                    // Skip script, style, and other non-content elements
                    var parentTag = node.parentElement.tagName.toLowerCase();
                    if (['script', 'style', 'noscript', 'iframe'].includes(parentTag)) {
                        return NodeFilter.FILTER_REJECT;
                    }

                    // Only process text nodes that contain supported link patterns
                    var textContent = node.textContent;
                    for (var schemaType in LINK_SCHEMAS) {
                        if (textContent.includes(schemaType + ':')) {
                            return NodeFilter.FILTER_ACCEPT;
                        }
                    }

                    return NodeFilter.FILTER_REJECT;
                }
            }
        );

        var node;
        while (node = walker.nextNode()) {
            textNodes.push(node);
        }

        // Extract supported links from text nodes
        textNodes.forEach(function(textNode) {
            var links = extractLinksFromText(textNode.textContent);
            links.forEach(function(link) {
                if (!discoveredLinks.has(link)) {
                    discoveredLinks.add(link);
                    newLinksFound = true;
                    var schemaInfo = identifyLinkSchema(link);
                    console.log('Found ' + (schemaInfo ? schemaInfo.schema.name : 'Unknown Link') + ' in text:', link);
                }
            });
        });

        // Additional scan for input values, textarea content, and code blocks
        var extraElements = document.querySelectorAll('input[type="text"], textarea, code, pre, .magnet, .torrent-info, .ed2k, .download-link');
        extraElements.forEach(function(element) {
            var text = element.value || element.textContent || element.innerText;
            var links = extractLinksFromText(text);
            links.forEach(function(link) {
                if (!discoveredLinks.has(link)) {
                    discoveredLinks.add(link);
                    newLinksFound = true;
                    var schemaInfo = identifyLinkSchema(link);
                    console.log('Found ' + (schemaInfo ? schemaInfo.schema.name : 'Unknown Link') + ' in element:', link, 'from:', element.tagName || element.className);
                }
            });
        });

        // Save to storage whenever new links are found
        if (newLinksFound) {
            saveToStorage(discoveredLinks, copiedLinks);
            console.log('Total links found:', discoveredLinks.size);
        }

        return newLinksFound;
    }

    // Check if there are magnet links on the page
    function checkAndCreateButton() {
        scanForMagnetLinks(); // Initial scan

        if (discoveredLinks.size > 0) {
            createButton();
            observePageChanges();
        }
    }

    // Initialize the script
    function initScript() {
        // Check if button already exists
        if (document.getElementById('copy-magnet-links-btn')) {
            return;
        }

        // Clear expired storage data
        var wasCleared = clearOldStorageData();

        checkAndCreateButton();

        // Save initial state
        saveToStorage(discoveredLinks, copiedLinks);

        // Show notification about storage status
        if (wasCleared) {
            setTimeout(function() {
                showNotification('Link Collector data expired (15min). Reset completed! 🔄');
            }, 1000);
        }
    }

    // Create the copy button
    function createButton() {
        // Update button display with link count
        function updateButtonDisplay() {
            var newCount = discoveredLinks.size - copiedLinks.size;
            if (newCount > 0) {
                button.innerHTML = `
                    <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="pointer-events: none;">
                        <rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect>
                        <path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path>
                    </svg>
                    <span style="position: absolute; top: -5px; right: -5px; background: #ff4757; color: white; border-radius: 50%; width: 16px; height: 16px; font-size: 10px; display: flex; align-items: center; justify-content: center; font-weight: bold;">${newCount}</span>
                `;
                var storageInfo = getStorageInfo();
            button.title = `Link Collector: ${newCount} magnet link${newCount > 1 ? 's' : ''} ready to collect\nTotal found: ${discoveredLinks.size} | Already collected: ${copiedLinks.size}\nStorage: ${storageInfo.sizeText}\nRight-click to clear storage`;
            } else {
                button.innerHTML = `
                    <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="pointer-events: none;">
                        <path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"></path>
                        <polyline points="22 4 12 14.01 9 11.01"></polyline>
                    </svg>
                `;
                button.title = "Link Collector: All links successfully collected! ✅";
            }
        }

        // Function to copy magnet links to clipboard
        function copyMagnetLinks() {
            // Get only links that haven't been copied yet
            var linksToCopy = [];
            discoveredLinks.forEach(function(link) {
                if (!copiedLinks.has(link)) {
                    linksToCopy.push(link);
                }
            });

            if (linksToCopy.length === 0) {
                showNotification("Link Collector: All links collected! ✅");
                return;
            }

            // Add loading state
            button.disabled = true;
            button.style.cursor = "wait";
            button.innerHTML = `
                <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="pointer-events: none; animation: spin 1s linear infinite;">
                    <path d="M21 2v6h-6"></path>
                    <path d="M3 12a9 9 0 0 1 15-6.7L21 8"></path>
                    <path d="M21 12a9 9 0 0 1-15 6.7L3 16"></path>
                    <path d="M3 22v-6h6"></path>
                </svg>
            `;

            var magnetLinks = linksToCopy.join("\n");

            // Simulate async operation for better UX
            setTimeout(function() {
                // Try multiple copy methods for better compatibility
                function tryCopy(text) {
                    // Method 1: Modern Clipboard API (Chrome, Edge, Firefox, Safari 13.1+)
                    if (navigator.clipboard && navigator.clipboard.writeText) {
                        navigator.clipboard.writeText(text).then(function() {
                            handleCopySuccess(linksToCopy);
                        }).catch(function() {
                            fallbackCopy(text);
                        });
                    }
                    // Method 2: Greasemonkey/Tampermonkey API
                    else if (typeof GM_setClipboard !== 'undefined') {
                        GM_setClipboard(magnetLinks);
                        handleCopySuccess(linksToCopy);
                    }
                    // Method 3: Legacy execCommand method
                    else {
                        fallbackCopy(text);
                    }
                }

                function fallbackCopy(text) {
                    try {
                        var textarea = document.createElement("textarea");
                        textarea.value = text;
                        textarea.style.position = "fixed"; // Prevent scrolling to bottom
                        textarea.style.opacity = "0";
                        document.body.appendChild(textarea);
                        textarea.select();
                        textarea.setSelectionRange(0, 99999); // For mobile devices

                        var successful = document.execCommand("copy");
                        document.body.removeChild(textarea);

                        if (successful) {
                            handleCopySuccess(linksToCopy);
                        } else {
                            showNotification("Failed to copy. Please try manually.");
                        }
                        resetButton();
                    } catch (err) {
                        showNotification("Copy failed: " + err.message);
                        resetButton();
                    }
                }

                // Handle successful copy
                function handleCopySuccess(copiedItems) {
                    // Mark links as copied
                    copiedItems.forEach(function(link) {
                        copiedLinks.add(link);
                    });

                    // Save updated state to storage
                    saveToStorage(discoveredLinks, copiedLinks);

                    showNotification(`Link Collector collected ${copiedItems.length} magnet link${copiedItems.length > 1 ? 's' : ''}! 📋`);
                    updateButtonDisplay();
                    resetButton();
                }

                tryCopy(magnetLinks);
            }, 300); // Small delay for better UX
        }

        // Reset button to original state
        function resetButton() {
            button.disabled = false;
            button.style.cursor = "pointer";
            updateButtonDisplay();
        }

        // Function to show a notification
        function showNotification(message) {
            var notification = document.createElement("div");
            notification.className = "magnet-notification";
            notification.innerText = message;
            notification.style.backgroundColor = "#333";
            notification.style.color = "#fff";
            notification.style.padding = "10px";
            notification.style.borderRadius = "5px";
            notification.style.zIndex = 1001;
            notification.style.fontSize = "14px";
            notification.style.opacity = "0.9";
            document.body.appendChild(notification);

            // Automatically remove the notification after 2 seconds
            setTimeout(function() {
                if (document.body.contains(notification)) {
                    document.body.removeChild(notification);
                }
            }, 2000);
        }

        // Add CSS animation and styles
        var style = document.createElement('style');
        style.textContent = `
            @keyframes spin {
                from { transform: rotate(0deg); }
                to { transform: rotate(360deg); }
            }

            #copy-magnet-links-btn {
                position: fixed !important;
                bottom: 10px !important;
                right: 10px !important;
                left: auto !important;
                top: auto !important;
                transform: none !important;
            }

            .magnet-notification {
                position: fixed !important;
                bottom: 60px !important;
                right: 10px !important;
                left: auto !important;
                top: auto !important;
                transform: none !important;
            }
        `;
        document.head.appendChild(style);

        // Create a button to trigger the copy action
        button = document.createElement("button");
        button.id = "copy-magnet-links-btn";
        button.style.position = "fixed";
        button.style.bottom = "10px";
        button.style.right = "10px"; // Place the button in the bottom-right corner
        button.style.zIndex = "2147483647"; // Maximum z-index to ensure it's on top
        button.style.width = "32px";
        button.style.height = "32px";
        button.style.padding = "6px";
        button.style.backgroundColor = "rgba(0, 123, 255, 0.9)";
        button.style.color = "white";
        button.style.border = "none";
        button.style.borderRadius = "6px";
        button.style.cursor = "pointer";
        button.style.display = "flex";
        button.style.alignItems = "center";
        button.style.justifyContent = "center";
        button.style.boxShadow = "0 2px 6px rgba(0,0,0,0.3)";
        button.style.transition = "all 0.2s ease";
        button.style.position = "relative"; // For badge positioning

        // Add hover effects
        button.addEventListener("mouseenter", function() {
            if (!button.disabled) {
                button.style.backgroundColor = "rgba(0, 86, 179, 0.95)";
                button.style.transform = "scale(1.1)";
            }
        });

        button.addEventListener("mouseleave", function() {
            if (!button.disabled) {
                button.style.backgroundColor = "rgba(0, 123, 255, 0.9)";
                button.style.transform = "scale(1)";
            }
        });

        document.body.appendChild(button);

        // Bind updateButtonDisplay to button for external access
        button.updateButtonDisplay = updateButtonDisplay;

        // Initialize button display
        updateButtonDisplay();

        // Add event listener for the button
        button.addEventListener("click", function() {
            copyMagnetLinks();
        });

        // Add right-click context menu to clear storage
        button.addEventListener("contextmenu", function(e) {
            e.preventDefault();

            var storageInfo = getStorageInfo();
            var ageText = storageInfo.expired ? `${storageInfo.ageMinutes} min ago (expired)` : `${storageInfo.ageMinutes} min ago`;
            var message = 'Clear Link Collector storage?\n\n' +
                         'Storage info:\n' +
                         '- ' + storageInfo.itemCount + ' links collected\n' +
                         '- Size: ' + storageInfo.sizeText + '\n' +
                         '- Age: ' + ageText + '\n' +
                         '- This will reset the collection\n\n' +
                         'Note: Data expires after 15min inactivity';

            if (confirm(message)) {
                try {
                    localStorage.removeItem(STORAGE_KEY);
                    discoveredLinks.clear();
                    copiedLinks.clear();

                    // Re-scan current page
                    var newLinksFound = scanForMagnetLinks();
                    updateButtonDisplay();

                    showNotification('Link Collector reset! ' + (newLinksFound ? 'Found ' + discoveredLinks.size + ' new links to collect.' : 'No magnet links found.'));
                } catch (err) {
                    showNotification('Failed to clear storage: ' + err.message);
                }
            }
        });
    }

    // Observe page changes for dynamic content
    function observePageChanges() {
        // Initial scan will be handled by createButton function

        observer = new MutationObserver(function(mutations) {
            var newLinksFound = false;

            mutations.forEach(function(mutation) {
                if (mutation.type === 'childList') {
                    for (var i = 0; i < mutation.addedNodes.length; i++) {
                        var node = mutation.addedNodes[i];
                        if (node.nodeType === Node.ELEMENT_NODE) {
                            // Priority 1: Check if the node itself is a supported link
                            if (node.tagName === 'A' && node.href) {
                                var schemaInfo = identifyLinkSchema(node.href);
                                if (schemaInfo) {
                                    if (!discoveredLinks.has(node.href)) {
                                        discoveredLinks.add(node.href);
                                        newLinksFound = true;
                                        console.log('Found ' + schemaInfo.schema.name + ' in new node:', node.href);
                                    }
                                }
                            }
                            // Priority 2: Check if the node contains supported links in <a> tags
                            else if (node.querySelectorAll) {
                                Object.keys(LINK_SCHEMAS).forEach(function(schemaType) {
                                    var links = node.querySelectorAll('a[href^="' + schemaType + ':"]');
                                    links.forEach(function(link) {
                                        if (!discoveredLinks.has(link.href)) {
                                            discoveredLinks.add(link.href);
                                            newLinksFound = true;
                                            console.log('Found ' + LINK_SCHEMAS[schemaType].name + ' in new node <a>:', link.href);
                                        }
                                    });
                                });
                            }

                            // Priority 3: Check if the new node contains supported links in text content
                            var textContent = node.textContent || node.innerText || '';
                            var hasSupportedLinks = false;
                            for (var schemaType in LINK_SCHEMAS) {
                                if (textContent.includes(schemaType + ':')) {
                                    hasSupportedLinks = true;
                                    break;
                                }
                            }

                            if (hasSupportedLinks) {
                                var linksFromText = extractLinksFromText(textContent);
                                linksFromText.forEach(function(link) {
                                    if (!discoveredLinks.has(link)) {
                                        discoveredLinks.add(link);
                                        newLinksFound = true;
                                        var linkSchemaInfo = identifyLinkSchema(link);
                                        console.log('Found ' + (linkSchemaInfo ? linkSchemaInfo.schema.name : 'Unknown Link') + ' in new node text:', link, 'from:', node.tagName);
                                    }
                                });
                            }

                            // Also check special elements that might contain magnet links
                            var specialElements = node.querySelectorAll && node.querySelectorAll('input[type="text"], textarea, code, pre, .magnet, .torrent-info');
                            if (specialElements) {
                                specialElements.forEach(function(element) {
                                    var text = element.value || element.textContent || element.innerText;
                                    var magnetLinks = extractMagnetLinksFromText(text);
                                    magnetLinks.forEach(function(magnetLink) {
                                        if (!discoveredLinks.has(magnetLink)) {
                                            discoveredLinks.add(magnetLink);
                                            newLinksFound = true;
                                            console.log('Found magnet link in special element:', magnetLink, 'from:', element.tagName || element.className);
                                        }
                                    });
                                });
                            }
                        }
                    }
                }
                // Also handle attribute changes (e.g., href changes)
                else if (mutation.type === 'attributes' && mutation.attributeName === 'href') {
                    var target = mutation.target;
                    if (target.tagName === 'A' && target.href) {
                        var schemaInfo = identifyLinkSchema(target.href);
                        if (schemaInfo && !discoveredLinks.has(target.href)) {
                            discoveredLinks.add(target.href);
                            newLinksFound = true;
                            console.log('Found ' + schemaInfo.schema.name + ' in href change:', target.href);
                        }
                    }
                }
                // Also handle text changes in elements that might contain supported links
                else if (mutation.type === 'characterData') {
                    var target = mutation.target;
                    var textContent = target.textContent;
                    var hasSupportedLinks = false;
                    for (var schemaType in LINK_SCHEMAS) {
                        if (textContent.includes(schemaType + ':')) {
                            hasSupportedLinks = true;
                            break;
                        }
                    }

                    if (hasSupportedLinks) {
                        var linksFromText = extractLinksFromText(textContent);
                        linksFromText.forEach(function(link) {
                            if (!discoveredLinks.has(link)) {
                                discoveredLinks.add(link);
                                newLinksFound = true;
                                var linkSchemaInfo = identifyLinkSchema(link);
                                console.log('Found ' + (linkSchemaInfo ? linkSchemaInfo.schema.name : 'Unknown Link') + ' in text change:', link, 'in:', target.parentElement.tagName);
                            }
                        });
                    }
                }
            });

            // Update button if new links were found
            if (newLinksFound && button && button.updateButtonDisplay) {
                button.updateButtonDisplay();
            }

            // Create button if it doesn't exist and we have links
            if (discoveredLinks.size > 0 && !document.getElementById('copy-magnet-links-btn')) {
                createButton();
            }
        });

        // Start observing with comprehensive options
        observer.observe(document.body, {
            childList: true,      // Watch for added/removed nodes
            subtree: true,        // Watch all descendants
            attributes: true,     // Watch for attribute changes
            attributeFilter: ['href'], // Specifically watch href changes
            characterData: true   // Watch for text content changes
        });

        // Also do periodic scans to catch any missed changes
        setInterval(function() {
            var hadNewLinks = scanForMagnetLinks();
            if (hadNewLinks && button && button.updateButtonDisplay) {
                button.updateButtonDisplay();
            }
        }, 2000); // Check every 2 seconds
    }

    // Start the script
    waitForDOM();
})();