Greasy Fork is available in English.

Magnet Link Extractor

Extract and display magnet links with a single click

// ==UserScript==
// @name         Magnet Link Extractor
// @namespace    http://tampermonkey.net/
// @version      1.0
// @description  Extract and display magnet links with a single click
// @match        *://*/*
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_addStyle
// @run-at       document-end
// @license      MIT
// ==/UserScript==

(function () {
    'use strict';

    // Don't run on reCAPTCHA pages
    if (window.location.href.includes('recaptcha') ||
        document.querySelector('iframe[src*="recaptcha"]')) {
        return;
    }

    // Add CSS for the floating icon and popup
    GM_addStyle(`
        #magnet-icon {
            position: fixed;
            top: 50px;
            right: 5px;
            width: 25px;
            height: 25px;
            background: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iaXNvLTg4NTktMSI/Pg0KPCEtLSBVcGxvYWRlZCB0bzogU1ZHIFJlcG8sIHd3dy5zdmdyZXBvLmNvbSwgR2VuZXJhdG9yOiBTVkcgUmVwbyBNaXhlciBUb29scyAtLT4NCjxzdmcgaGVpZ2h0PSI4MDBweCIgd2lkdGg9IjgwMHB4IiB2ZXJzaW9uPSIxLjEiIGlkPSJMYXllcl8xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiANCgkgdmlld0JveD0iMCAwIDUxMi4wMTkgNTEyLjAxOSIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+DQo8cGF0aCBzdHlsZT0iZmlsbDojREE0NDUzOyIgZD0iTTM2NC40MzcsMjIzLjAyNkwyMDguMzczLDM3OS4wNTljLTIwLjc4MSwyMC44MTItNTQuNjI1LDIwLjgxMi03NS40MDYsMA0KCWMtMjAuODEzLTIwLjc4MS0yMC44MTMtNTQuNjI1LDAtNzUuNDA3bDE1Ni4wMzEtMTU2LjA2M0wyMDYuMDMsNjQuNjM1TDQ5Ljk5NywyMjAuNjgybDAsMGMtNjYuNjU3LDY2LjY1Ny02Ni42NTcsMTc0LjcwNSwwLDI0MS4zNDUNCgljNjYuNjU3LDY2LjY1NywxNzQuNzIsNjYuNjU3LDI0MS4zNDUsMGwxNTYuMDY0LTE1Ni4wMzJMMzY0LjQzNywyMjMuMDI2eiIvPg0KPHBvbHlnb24gc3R5bGU9ImZpbGw6I0NDRDFEOTsiIHBvaW50cz0iMTMwLjYyMywxNDAuMDU3IDIxMy41OTIsMjIzLjAyNiAyODguOTk4LDE0Ny41ODggMjA2LjAzLDY0LjYzNSAiLz4NCjxnPg0KCTxwYXRoIHN0eWxlPSJmaWxsOiNBQUIyQkQ7IiBkPSJNNDY2LjIxOSwyMTkuMjQ1YzEuOTM4LTEuOTM4LDMuMTI1LTQuNjA5LDMuMTI1LTcuNTYyYzAtNS44OTEtNC43ODEtMTAuNjU2LTEwLjY1Ni0xMC42NTYNCgkJYy0yLjkzOCwwLTUuNjI1LDEuMTg4LTcuNTYyLDMuMTI1bC0zMC4xNTYsMzAuMTU2bDAsMGMtMS45MzgsMS45MzgtMy4xMjUsNC41OTQtMy4xMjUsNy41NDZjMCw1Ljg5MSw0Ljc4MSwxMC42NzIsMTAuNjU2LDEwLjY3Mg0KCQljMi45NjksMCw1LjYyNS0xLjIwMyw3LjU2Mi0zLjE0MUw0NjYuMjE5LDIxOS4yNDV6Ii8+DQoJPHBhdGggc3R5bGU9ImZpbGw6I0FBQjJCRDsiIGQ9Ik01MDguODc1LDI2NC40OGMtMi4wNjItMi4wNzgtNC44MTItMy4xMjUtNy41MzEtMy4xMjVsMCwwaC00Mi42NTZsMCwwDQoJCWMtMi43MTksMC01LjQ2OSwxLjA0Ny03LjUzMSwzLjEyNWMtNC4xODgsNC4xNzItNC4xODgsMTAuOTIyLDAsMTUuMDc4YzIuMDYyLDIuMDk0LDQuODEyLDMuMTI1LDcuNTMxLDMuMTI1aDQyLjY1Ng0KCQljMi43MTksMCw1LjQ2OS0xLjAzMSw3LjUzMS0zLjEyNUM1MTMuMDYzLDI3NS40MDIsNTEzLjA2MywyNjguNjUyLDUwOC44NzUsMjY0LjQ4eiIvPg0KCTxwYXRoIHN0eWxlPSJmaWxsOiNBQUIyQkQ7IiBkPSJNNDA1Ljg3NSwxNjEuNDc5Yy00LjE1Ni00LjE3Mi0xMC45MDYtNC4xNzItMTUuMDYyLDBjLTIuMDk0LDIuMDc4LTMuMTI1LDQuODEyLTMuMTI1LDcuNTQ3djQyLjY1Nw0KCQljMCwyLjcxOSwxLjAzMSw1LjQ2OSwzLjEyNSw3LjUzMWM0LjE1Niw0LjE4OCwxMC45MDYsNC4xODgsMTUuMDYyLDBjMi4wOTQtMi4wNjIsMy4xMjUtNC44MTIsMy4xMjUtNy41MzFsMCwwdi00Mi42NTdsMCwwDQoJCUM0MDksMTY2LjI5MSw0MDcuOTY5LDE2My41NTcsNDA1Ljg3NSwxNjEuNDc5eiIvPg0KCTxwYXRoIHN0eWxlPSJmaWxsOiNBQUIyQkQ7IiBkPSJNMzA3Ljg3Myw2MC44NjhMMzA3Ljg3Myw2MC44NjhjMS45MzgtMS45MzgsMy4xMjYtNC41OTQsMy4xMjYtNy41MzENCgkJYzAtNS45MDYtNC43ODItMTAuNjcyLTEwLjY4OC0xMC42NzJjLTIuOTM4LDAtNS41OTQsMS4yMDMtNy41MzEsMy4xMjV2LTAuMDE2bC0zMC4xNTYsMzAuMTg4bDAsMA0KCQljLTEuOTM4LDEuOTIyLTMuMTI1LDQuNTk0LTMuMTI1LDcuNTMxYzAsNS44OTEsNC43ODEsMTAuNjcyLDEwLjY1NiwxMC42NzJjMi45MzgsMCw1LjYyNS0xLjIwMyw3LjUzMS0zLjEyNWwwLDBMMzA3Ljg3Myw2MC44Njh6Ig0KCQkvPg0KCTxwYXRoIHN0eWxlPSJmaWxsOiNBQUIyQkQ7IiBkPSJNMzUwLjUzMSwxMDYuMTE5Yy0yLjA5NC0yLjA3OC00LjgxMi0zLjEyNS03LjUzMS0zLjEyNWwwLDBoLTQyLjY1N2wwLDANCgkJYy0yLjc1LDAtNS40NjksMS4wNDctNy41NjIsMy4xMjVjLTQuMTU2LDQuMTcyLTQuMTU2LDEwLjkyMiwwLDE1LjA5NGMyLjA5NCwyLjA3OCw0LjgxMiwzLjEyNSw3LjU2MiwzLjEyNUgzNDMNCgkJYzIuNzE5LDAsNS40MzgtMS4wNDcsNy41MzEtMy4xMjVDMzU0LjY4NywxMTcuMDQxLDM1NC42ODcsMTEwLjI5MSwzNTAuNTMxLDEwNi4xMTl6Ii8+DQoJPHBhdGggc3R5bGU9ImZpbGw6I0FBQjJCRDsiIGQ9Ik0yNDcuNTMsMy4xMThjLTQuMTU2LTQuMTU3LTEwLjkwNi00LjE1Ny0xNS4wOTQsMGMtMi4wNjIsMi4wOTQtMy4xMjUsNC44MTItMy4xMjUsNy41NjJ2NDIuNjI1DQoJCWMwLDIuNzUsMS4wNjIsNS40NjksMy4xMjUsNy41NjJjNC4xODgsNC4xNTcsMTAuOTM4LDQuMTU3LDE1LjA5NCwwYzIuMDk0LTIuMDk0LDMuMTI1LTQuODEyLDMuMTI1LTcuNTQ3bDAsMFYxMC42NjVsMCwwDQoJCUMyNTAuNjU1LDcuOTMxLDI0OS42MjMsNS4yMTIsMjQ3LjUzLDMuMTE4eiIvPg0KPC9nPg0KPHBvbHlnb24gc3R5bGU9ImZpbGw6I0NDRDFEOTsiIHBvaW50cz0iNDQ3LjQwNiwzMDUuOTk1IDM2NC40MzcsMjIzLjAyNiAyODguOTk4LDI5OC40MzMgMzcxLjk2OCwzODEuNDAzICIvPg0KPC9zdmc+') no-repeat center center;
            background-size: cover;
            cursor: grab;
            z-index: 1000;
            filter: none;
        }

        #magnet-popup {
            display: none;
            position: fixed;
            bottom: 80px;
            right: 20px;
            width: 300px;
            max-height: 400px;
            background: white;
            border: 1px solid #ccc;
            box-shadow: 0px 0px 10px rgba(0,0,0,0.1);
            overflow-y: auto;
            z-index: 1001;
            padding: 10px;
        }

        #magnet-popup button {
            display: block;
            margin: 10px 0;
            padding: 5px 10px;
            background: #4CAF50;
            color: white;
            border: none;
            border-radius: 3px;
            cursor: pointer;
        }

        #magnet-popup button:hover {
            background: #45a049;
        }

        #magnet-popup textarea {
            width: 100%;
            height: 200px;
            padding: 5px;
            border: 1px solid #ddd;
            border-radius: 3px;
            resize: vertical;
        }

        .magnet-count {
            margin-bottom: 5px;
            color: #666;
            font-size: 12px;
        }
    `);

    // Create the floating icon
    const icon = document.createElement('div');
    icon.id = 'magnet-icon';
    document.body.appendChild(icon);

    // Create the popup
    const popup = document.createElement('div');
    popup.id = 'magnet-popup';
    popup.innerHTML = `
        <div class="magnet-count">Found magnets: <span id="magnet-count">0</span></div>
        <button id="copy-btn">Copy All</button>
        <textarea id="magnet-links" placeholder="No magnet links found on this page..."></textarea>
    `;
    document.body.appendChild(popup);

    // Improved magnet link extraction function
    function extractMagnetLinks() {
        // Multiple ways to find magnet links
        const links = [
            ...document.querySelectorAll('a[href^="magnet:?xt="]'),
            ...document.querySelectorAll('a[href*="magnet:"]'),
            ...document.querySelectorAll('[data-magnet]'),
            ...document.querySelectorAll('a[title*="magnet"]')
        ];

        const uniqueMagnets = new Set();

        links.forEach(link => {
            let magnetUrl = link.href || link.getAttribute('data-magnet') || link.getAttribute('title');
            if (magnetUrl && magnetUrl.includes('magnet:?')) {
                uniqueMagnets.add(magnetUrl);
            }
        });

        const output = Array.from(uniqueMagnets).join('\n');
        document.getElementById('magnet-links').value = output;
        document.getElementById('magnet-count').textContent = uniqueMagnets.size;

        // Update icon color based on whether links were found
        icon.style.opacity = uniqueMagnets.size > 0 ? '1' : '0.5';
    }

    // Popup visibility state
    let isPopupVisible = false;

    // Show or hide popup on icon click
    icon.addEventListener('click', () => {
        isPopupVisible = !isPopupVisible;
        popup.style.display = isPopupVisible ? 'block' : 'none';
        if (isPopupVisible) {
            extractMagnetLinks();
        }
    });

    // Copy all links to clipboard
    document.getElementById('copy-btn').addEventListener('click', () => {
        const textArea = document.getElementById('magnet-links');
        textArea.select();
        document.execCommand('copy');

        // Visual feedback
        const btn = document.getElementById('copy-btn');
        btn.textContent = 'Copied!';
        setTimeout(() => {
            btn.textContent = 'Copy All';
        }, 1000);
    });

    // Monitor for dynamic changes
    function monitorForMagnetLinks() {
        const observer = new MutationObserver((mutations) => {
            let shouldExtract = false;
            for (const mutation of mutations) {
                if (mutation.addedNodes.length > 0) {
                    shouldExtract = true;
                    break;
                }
            }
            if (shouldExtract && isPopupVisible) {
                extractMagnetLinks();
            }
        });

        observer.observe(document.body, {
            childList: true,
            subtree: true,
            attributes: true,
            attributeFilter: ['href', 'data-magnet']
        });
    }

    // Make the icon draggable
    let isDragging = false;
    let offsetX, offsetY;

    icon.addEventListener('mousedown', (e) => {
        isDragging = true;
        offsetX = e.clientX - parseInt(window.getComputedStyle(icon).left);
        offsetY = e.clientY - parseInt(window.getComputedStyle(icon).top);
        icon.style.cursor = 'grabbing';
        document.addEventListener('mousemove', onMouseMove);
        document.addEventListener('mouseup', onMouseUp);
    });

    function onMouseMove(e) {
        if (isDragging) {
            const newX = e.clientX - offsetX;
            const newY = e.clientY - offsetY;

            // Keep icon within viewport bounds
            const maxX = window.innerWidth - icon.offsetWidth;
            const maxY = window.innerHeight - icon.offsetHeight;

            icon.style.left = Math.min(Math.max(0, newX), maxX) + 'px';
            icon.style.top = Math.min(Math.max(0, newY), maxY) + 'px';
        }
    }

    function onMouseUp() {
        isDragging = false;
        icon.style.cursor = 'grab';
        document.removeEventListener('mousemove', onMouseMove);
        document.removeEventListener('mouseup', onMouseUp);
    }

    // Initial extraction
    extractMagnetLinks();

    // Start monitoring for dynamic changes
    monitorForMagnetLinks();
})();