Magnet Link Extractor

Extract and display magnet links with a single click

// ==UserScript==
// @name         Magnet Link Extractor
// @namespace    http://tampermonkey.net/
// @version      1.2
// @description  Extract and display magnet links with a single click
// @note         v1.2 updates: 
// @note         - The icon now only appears on pages with magnet links; 
// @note         - Clicking the magnet icon automatically copies all detected magnet links to the clipboard. No additional action is required;
// @note         - The panel now features a selectable design with checkboxes for better user control.
// @match        *://*/*
// @grant        GM_addStyle
// @run-at       document-end
// @license      MIT

// ==/UserScript==

(function () {
    'use strict';

    // Add CSS
    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+DQoJPHBhdGggc3R5bGU9ImZpbGw6I0FBQjJCRDsiIGQ9Ik0yNDcuNTMsMy4xMThjLTQuMTU2LTQuMTU3LTEwLjkwNi00LjE1Ny0xNS4wOTQsMGMtMi4wNjIsMi4wOTQtMy4xMjUsNC44MTIsMy4xMjUsNy41NjJ2NDIuNjI1DQoJCWMwLDIuNzUsMS4wNjIsNS40NjksMy4xMjUsNy41NjJjNC4xODgsNC4xNTcsMTAuOTM4LDQuMTU3LDE1LjA5NCwwYzIuMDk0LTIuMDk0LDMuMTI1LTQuODEyLDMuMTI1LTcuNTQ3bDAsMFYxMC42NjVsMCwwDQoJCUMyNTAuNjU1LDcuOTMxLDI0OS42MjMsNS4yMTIsMjQ3LjUzLDMuMTE4eiIvPg0KPC9nPg0KPHBvbHlnb24gc3R5bGU9ImZpbGw6I0NDRDFEOTsiIHBvaW50cz0iNDQ3LjQwNiwzMDUuOTk1IDM2NC40MzcsMjIzLjAyNiAyODguOTk4LDI5OC40MzMgMzcxLjk2OCwzODEuNDAzICIvPg0KPC9zdmc+') no-repeat center center;
            background-size: cover;
            cursor: grab;
            z-index: 1000;
            display: none; /* Start hidden */
        }

        #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;
        }

        #select-options {
            display: flex;
            justify-content: space-between;
            margin-bottom: 10px;
        }

        .button-group {
            display: flex;
            gap: 3px; /* Adjust this value to change button spacing */
        }

        button {
            cursor: pointer;
            padding: 4px 8px; /* Adjust padding for button size */
            border: none;
            border-radius: 3px;
            font-size: 12px; /* Adjust font size */
        }

        #select-all-btn {
            background: #4CAF50;
            color: white;
        }

        #select-none-btn {
            background: #f44336;
            color: white;
        }

        #copy-selected-btn {
            background: #2196F3;
            color: white;
            margin-left: auto; /* Move to the right */
        }

        #copy-feedback {
            font-size: 12px;
            color: green;
            margin-bottom: 0px; /* Space between feedback and links */
            text-align: center;
            display: none;
            font-weight: normal;
        }

        .magnet-count {
            text-align: left; /* Align the count to the left */
            font-size: 12px; /* Adjust the text size */
            color: #666; /* Set the color */
            margin-top: 2px; /* Add spacing above the count */
            margin-bottom: 5px; /* Add spacing below the count */
        }

        .magnet-item {
            display: flex;
            align-items: center; /* Center-align the checkbox and label */
            margin: 0; /* Reset margins */
            font-family: sans-serif; /* Unified font */
            font-size: 12px; /* Adjust text size */
            font-weight: normal; /* Ensure the text is not bold */
            text-decoration: none;
            color: #333; /* Unified color */
            white-space: nowrap; /* Prevent line breaking */
        }

        .magnet-checkbox {
            appearance: auto; /* Ensure the checkbox retains its native appearance */
            margin-right: 8px; /* Space between checkbox and label */
            width: 16px; /* Optional: Set a consistent width */
            height: 16px; /* Optional: Set a consistent height */
            vertical-align: middle; 
        }

        .magnet-item label {
            margin-left: 2px; /* Space between checkbox and link text */
            font-family: sans-serif; /* Set desired font */
            font-size: 12px; /* Unified size */
            font-weight: normal; /* Ensure normal weight */
            color: #333; /* Unified color */
            white-space: nowrap; /* Prevent line breaking */
            line-height: 16px; /* Match the checkbox height */
        }
    `);

    // 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 id="select-options" class="button-group">
            <button id="select-all-btn">All</button>
            <button id="select-none-btn">None</button>
            <div id="copy-feedback" style="flex: 1; text-align: center; display: none;">Links copied!</div>
            <button id="copy-selected-btn">Copy</button>
        </div>
        <div class="magnet-count">Counts: <span id="magnet-count">0</span></div>
        <div id="magnet-list"></div>
    `;
    document.body.appendChild(popup);

    // Extract magnet links
    function extractMagnetLinks() {
        const links = new Set();
        document.querySelectorAll('a[href^="magnet:?xt="]').forEach(link => {
            if (link.href.startsWith('magnet:?xt=')) {
                links.add(link.href);
            }
        });

        const magnetList = document.getElementById('magnet-list');
        magnetList.innerHTML = ''; // Clear existing content

        Array.from(links).forEach((magnet, index) => {
            const item = document.createElement('div');
            item.className = 'magnet-item';

            item.innerHTML = `
                <input type="checkbox" class="magnet-checkbox" id="magnet-${index}" data-magnet="${magnet}" checked>
                <label for="magnet-${index}" title="${magnet}">${magnet}</label>
            `;

            magnetList.appendChild(item);
        });

        const linkCount = links.size;
        document.getElementById('magnet-count').textContent = linkCount;

        // Show the icon only if links are found
        if (linkCount > 0) {
            icon.style.display = 'block';
        } else {
            icon.style.display = 'none';
        }
    }

    // Handle Select All button
    document.getElementById('select-all-btn').addEventListener('click', () => {
        document.querySelectorAll('.magnet-checkbox').forEach(cb => cb.checked = true);
    });

    // Handle Select None button
    document.getElementById('select-none-btn').addEventListener('click', () => {
        document.querySelectorAll('.magnet-checkbox').forEach(cb => cb.checked = false);
    });

    // Copy selected magnets
    document.getElementById('copy-selected-btn').addEventListener('click', () => {
        const selectedLinks = Array.from(document.querySelectorAll('.magnet-checkbox:checked'))
            .map(cb => cb.dataset.magnet)
            .join('\n');

        if (selectedLinks) {
            navigator.clipboard.writeText(selectedLinks).then(() => {
                const feedback = document.getElementById('copy-feedback');
                feedback.style.display = 'block';
                setTimeout(() => feedback.style.display = 'none', 2500);
            }).catch(err => {
                console.error('Failed to copy text: ', err);
            });
        }
    });

    // 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();
            // Default to select all links and copy them
            document.querySelectorAll('.magnet-checkbox').forEach(cb => cb.checked = true);
            document.getElementById('copy-selected-btn').click();
        }
    });

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

    icon.addEventListener('mousedown', (e) => {
        isDragging = true;
        offsetX = e.clientX - parseInt(window.getComputedStyle(icon).left || '0');
        offsetY = e.clientY - parseInt(window.getComputedStyle(icon).top || '0');
        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();
})();