Greasy Fork is available in English.

Magnet Link Handler for qBittorrent WebUI

Intercept magnet links and open qBittorrent download dialog in a popup.

// ==UserScript==
// @name         Magnet Link Handler for qBittorrent WebUI
// @namespace    http://tampermonkey.net/
// @version      1.4
// @author       down_to_earth
// @description  Intercept magnet links and open qBittorrent download dialog in a popup.
// @match        *://*/*
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_registerMenuCommand
// @license      MIT
// ==/UserScript==


(async function() {
    'use strict';

    async function sleep(ms) {
        return new Promise(resolve => setTimeout(resolve, ms));
    }

    function setValue(node, value) {
        if (!node || value === undefined) {
            return;
        }

        function triggerChangeEvent(node) {
            const event = new Event('change', { bubbles: true });
            node.dispatchEvent(event);
        }

        if (node.type === 'checkbox' || node.type === 'radio') {
            if (node.checked !== value) {
                node.checked = value;
                triggerChangeEvent(node);
            }
        } else {
            if (node.value !== value) {
                node.value = value;
                triggerChangeEvent(node);
            }
        }
    }

    function saveSettings(sourceHostname) {
        const settings = {};

        // Save all <select> elements
        document.querySelectorAll('select:not([disabled]):not([hidden])').forEach(select => {
            const key = select.name || select.id;
            settings[key] = select.value;
        });

        // Save all <input> elements
        document.querySelectorAll('input:not([disabled]):not([hidden])').forEach(input => {
            const key = input.name ||input.id;
            if (input.type === 'checkbox' || input.type === 'radio') {
                settings[key] = input.checked;
            } else {
                settings[key] = input.value;
            }
        });

        // Save to localStorage
        localStorage.setItem(`settings_${sourceHostname}`, JSON.stringify(settings));
    }

    async function restoreSettings(sourceHostname) {
        const settings = JSON.parse(localStorage.getItem(`settings_${sourceHostname}`) || 'null');
        if (!settings) {
            return;
        }

        const categoryValue = settings.categorySelect;
        const categorySelect = document.querySelector('#categorySelect');
        if (categoryValue && categorySelect) {
            // Wait for categories to load
            while (!Array.from(categorySelect.options).some(option => option.value === categoryValue)) {
                await sleep(10);
            }
            setValue(categorySelect, categoryValue);
        }

        // Restore all <select> elements
        document.querySelectorAll('select:not([disabled]):not([hidden])').forEach(select => {
            const key = select.name || select.id;
            if (key === 'categorySelect') {
                return;
            }
            setValue(select, settings[key]);
        });

        // Restore all <input> elements
        document.querySelectorAll('input:not([disabled]):not([hidden])').forEach(input => {
            const key = input.name || input.id;
            setValue(input, settings[key]);
        });
    }

    // Define the default qBittorrent WebUI URL
    const defaultQbWebUIHost = 'http://localhost:8080';

    // Retrieve the saved qBittorrent WebUI URL or use the default
    let qbWebUIHost = GM_getValue('qbWebUIHost', defaultQbWebUIHost);

    // Add a menu command to set the qBittorrent WebUI URL
    GM_registerMenuCommand('Set qBittorrent WebUI URL', () => {
        const newUrl = prompt('Enter the qBittorrent WebUI URL:', qbWebUIHost);
        if (newUrl) {
            GM_setValue('qbWebUIHost', newUrl); // Save the new URL
            qbWebUIHost = newUrl; // Update the script's URL
            alert(`qBittorrent WebUI URL set to: ${newUrl}`);
        }
    });

    // Check if the current page is the qBittorrent WebUI's
    const isMainPage = window.location.href.startsWith(qbWebUIHost) && window.location.pathname === '/';
    const isDownloadPage = window.location.href.startsWith(qbWebUIHost) && window.location.pathname === '/download.html';

    if (isMainPage) {
        const showDownloadPopup = async () => {
            const urlParams = new URLSearchParams(window.parent.location.search);
            const encodedSource = urlParams.get('source');
            if (!encodedSource) {
                // Do nothing if it's a normal WebUI open
                return;
            }

            const downloadIframe = document.createElement('iframe');
            const downloadUrl = new URL(window.location.href);
            downloadUrl.pathname = '/download.html';

            downloadIframe.src = downloadUrl.toString();
            downloadIframe.style.position = 'fixed';
            downloadIframe.style.top = '0';
            downloadIframe.style.left = '0';
            downloadIframe.style.width = '100%';
            downloadIframe.style.height = '100%';
            downloadIframe.style.border = 'none';
            downloadIframe.style.zIndex = '9999';
            downloadIframe.style.backgroundColor = 'white';

            // Append the iframe to the document body
            document.body.appendChild(downloadIframe);
        };
        if (document.readyState == 'complete') {
            await showDownloadPopup();
        } else {
            window.addEventListener("load", showDownloadPopup);
        }
    } else if (isDownloadPage) {
        // Check if the URL contains the `?source=` parameter
        const urlParams = new URLSearchParams(window.parent.location.search);
        const encodedSource = urlParams.get('source');
        if (!encodedSource) {
            return;
        }

        // Wait for the preferences to load
        const savePath = document.querySelector('#savepath');
        while (!savePath.value) {
            await sleep(10);
        }

        const sourceHostname = decodeURIComponent(encodedSource);

        // Register close handler
        const downloadFrame = document.querySelector('#download_frame');
        downloadFrame.addEventListener("load", function() {
            saveSettings(sourceHostname);
            window.parent.close();
        });

        await restoreSettings(sourceHostname);

        const autoDownload = urlParams.get('autoDownload') === 'true';
        if (autoDownload) {
            document.querySelector('#submitButton')?.click();
        }
    } else {
        // Function to handle magnet link clicks
        function handleMagnetLinkClick(event) {
            if ((event.type === 'click' && event.button !== 0) ||
                (event.type === 'mouseup' && event.button !== 1)) {
                return;
            }

            let target = event.target;
            while (target && target.tagName !== 'A') {
                target = target.parentElement;
            }

            // Check if the clicked element is a magnet link
            if (target?.href.startsWith('magnet:')) {
                event.preventDefault(); // Prevent the default behavior

                // Encode the magnet link for use in a URL
                const encodedMagnetLink = encodeURIComponent(target.href);

                // Get the source website hostname
                const sourceHostname = window.location.hostname;

                // Trigger auto download if clicked with Ctrl or Middle mouse click
                const autoDownload = event.ctrlKey || event.button === 1;

                // Open the qBittorrent WebUI's magnet handler in a popup
                const popupUrl = `${qbWebUIHost}/?urls=${encodedMagnetLink}&source=${sourceHostname}&autoDownload=${autoDownload}`

                // Define the popup dimensions
                const popupWidth = 500;
                const popupHeight = window.screen.height;

                // Calculate the position to center the popup
                const left = (window.screen.width - popupWidth) / 2;
                const top = (window.screen.height - popupHeight) / 2;

                // Open the popup with centered position
                let popupName = 'qBittorrentAddMagnet';
                if (autoDownload) {
                    popupName += crypto.randomUUID()
                }
                window.open(
                    popupUrl,
                    popupName,
                    `width=${popupWidth},height=${popupHeight},left=${left},top=${top}`
                );
            }
        }

        // Attach the event listeners to the document
        document.addEventListener('click', handleMagnetLinkClick, true);
        document.addEventListener('mouseup', handleMagnetLinkClick, true);
    }
})();