Debrid Download Helper (Multi-Provider)

Download files with a single click without visiting the debrid providers' website.

// ==UserScript==
// @name         Debrid Download Helper (Multi-Provider)
// @namespace    http://tampermonkey.net/
// @version      1.12
// @author       Superflyin
// @match        *://*/*
// @grant        GM_addStyle
// @grant        GM_xmlhttpRequest
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_registerMenuCommand
// @require      https://code.jquery.com/jquery-3.6.0.min.js
// @connect      api.real-debrid.com
// @connect      api.alldebrid.com
// @connect      www.premiumize.me
// @connect      api.linksnappy.com
// @connect      api.torbox.io
// @description Download files with a single click without visiting the debrid providers' website.
// ==/UserScript==

(function($) {
    'use strict';
    console.log('Debrid Download Helper: Script started! (v26.5)');

    // IMPORTANT: Ensure the script only runs in the top-level window (not in iframes)
    if (window.top !== window.self) {
        console.log('Debrid Download Helper: Script detected running in an iframe. Exiting.');
        return; // Exit if we are in an iframe
    }

    // --- Provider configuration ---
    const providers = {
        "real-debrid": {
            name: "Real-Debrid",
            icon: "https://i.ibb.co/6RW9YRnw/realdebrid-116047.webp", // Updated icon URL
            apiUrl: "https://api.real-debrid.com/rest/1.0/unrestrict/link",
            apiMagnetUrl: "https://api.real-debrid.com/rest/1.0/torrents/addMagnet",
            method: "POST",
            headers: apiKey => ({
                'Authorization': `Bearer ${apiKey}`,
                'Content-Type': 'application/x-www-form-urlencoded'
            }),
            data: url => `link=${encodeURIComponent(url)}`,
            magnetData: magnetUrl => `magnet=${encodeURIComponent(magnetUrl)}`,
            parseResponse: resp => { // For direct links
                try { return JSON.parse(resp).download || null; } catch { return null; }
            },
            parseMagnetResponse: resp => { // For magnet links
                try {
                    const data = JSON.parse(resp);
                    return data && data.id ? true : false; // Return true on successful ID, then user goes to torrents page
                } catch (e) {
                    return false;
                }
            }
        },
        "alldebrid": {
            name: "AllDebrid",
            icon: "https://addons.mozilla.org/user-media/addon_icons/662/662954-64.png?modified=1590413336",
            apiUrl: "https://api.alldebrid.com/v4/link/unlock",
            apiMagnetUrl: "https://api.alldebrid.com/v4/magnet/upload",
            method: "GET", // AllDebrid link unlock uses GET
            headers: () => ({}), // No custom headers for AllDebrid GET
            buildUrl: (apiKey, url) => `https://api.alldebrid.com/v4/link/unlock?agent=userscript&apikey=${apiKey}&link=${encodeURIComponent(url)}`,
            parseResponse: resp => {
                try { let j = JSON.parse(resp); return (j.data && j.data.link) || null; } catch { return null; }
            }
        },
        "premiumize": {
            name: "Premiumize",
            icon: "https://www.premiumize.me/favicon.ico",
            apiUrl: "https://www.premiumize.me/api/unrestrict/link",
            method: "GET",
            headers: () => ({}),
            buildUrl: (apiKey, url) => `https://www.premiumize.me/api/unrestrict/link?link=${encodeURIComponent(url)}&auth=${apiKey}`,
            parseResponse: resp => {
                try { let j = JSON.parse(resp); return (j.status === "success" && j.download) || null; } catch { return null; }
            }
        },
        "linksnappy": {
            name: "LinkSnappy",
            icon: "https://lh3.googleusercontent.com/pCVuXSx1pjsSR5Kzh98zJmRKiB1e1b_p7uKJ7s-5YQ8lpnO7SHtYksHcGpuvoxX4j5ZyVaF31URB3sbHMteygmyF=s120",
            apiUrl: "https://api.linksnappy.com/file/unlock",
            method: "POST",
            headers: apiKey => ({
                'Authorization': `Bearer ${apiKey}`,
                'Content-Type': 'application/x-www-form-urlencoded'
            }),
            data: url => `url=${encodeURIComponent(url)}`,
            parseResponse: resp => {
                try { let j = JSON.parse(resp); return (j.result && j.result.url) || null; } catch { return null; }
            }
        },
        "torbox": {
            name: "TorBox",
            icon: "https://downloads.intercomcdn.com/i/o/570645/98aded70c10f217bd63035f7/80582a6c13665edcd8ddb65505b883fb.png", // Updated TorBox icon link
            apiUrl: "https://api.torbox.io/v1/unrestrict",
            method: "POST",
            headers: apiKey => ({
                'Authorization': `Bearer ${apiKey}`,
                'Content-Type': 'application/x-www-form-urlencoded'
            }),
            data: url => `link=${encodeURIComponent(url)}`,
            parseResponse: resp => {
                try { return JSON.parse(resp).download || null; } catch { return null; }
            }
        }
    };

    // --- Supported hosts and file link patterns (for direct links only) ---
    const supportedPatterns = [
        { host: "1fichier.com",     pattern: /^\/\?[a-zA-Z0-9]{20,}\/?$/ },
        { host: "dailyuploads.net", pattern: /^\/[a-zA-Z0-9]{12,}$/ },
        { host: "ddownload.com",    pattern: /^\/[a-zA-Z0-9]{10,}/ },
        { host: "dropbox.com",      pattern: /^\/s\/[a-zA-Z0-9]+\/.+/ },
        { host: "filefactory.com",  pattern: /^\/file\/[a-zA-Z0-9]+/ },
        { host: "filenext.com",     pattern: /^\/[a-zA-Z0-9]{12,}/ },
        { host: "filespace.com",    pattern: /^\/[a-zA-Z0-9]{12,}/ },
        { host: "gigapeta.com",     pattern: /^\/file\/[a-zA-Z0-9]+/ },
        { host: "hitfile.net",      pattern: /^\/[a-zA-Z0-9]{12,}/ },
        { host: "katfile.com",      pattern: /^\/(file\/)?(?:view\/)?[a-zA-Z0-9]{10,}(\/.*)?$/ },
        { host: "mediafire.com",    pattern: /^\/file\/[a-zA-Z0-9_]+\/.+/ },
        { host: "mega.nz",          pattern: /^\/file\/[a-zA-Z0-9!#]+/ },
        { host: "nitroflare.com",   pattern: /^\/view\/[A-Za-z0-9]+(\/[^\/]+)?$/ },
        { host: "prefiles.com",     pattern: /^\/f\/[a-z0-9]+\/?/ },
        { host: "rapidgator.net",   pattern: /^\/file\/[a-fA-F0-9]{32}/ },
        { host: "turbobit.net",     pattern: /[a-z0-9]{15,}\.html$/ },
        { host: "uploady.io",       pattern: /^\/[A-Za-z0-9]{12,}/ },
        { host: "userscloud.com",   pattern: /^\/[a-z0-9]{12,}$/ },
        { host: "usersdrive.com",   pattern: /^\/file\/[a-z0-9]+/ },
        { host: "userupload.net",   pattern: /^\/[a-zA-Z0-9]{12,}/ },
        { host: "vidoza.net",       pattern: /^\/[a-z0-9]{12,}/ },
        { host: "vipfile.com",      pattern: /^\/d\/[a-z0-9]+\/?/ },
        { host: "worldbytez.com",   pattern: /^\/[a-z0-9]+\.html$/ },
        { host: "wupfile.com",      pattern: /^\/file\/[a-z0-9]+/ },
        // Added hosts from new hosts.txt with common patterns (adjust if specific patterns are known)
        { host: "4shared.com",      pattern: /^\/file\/.+/ },
        { host: "alfafile.net",     pattern: /^\/file\/.+/ },
        { host: "apkadmin.com",     pattern: /^\/file\/.+/ },
        { host: "clicknupload.cc",  pattern: /^\/file\/.+/ },
        { host: "cloudvideo.tv",    pattern: /^\/[a-zA-Z0-9]{12,}$/ },
        { host: "drop.download",    pattern: /^\/[a-zA-Z0-9]{12,}/ },
        { host: "dropgalaxy.com",   pattern: /^\/file\/.+/ },
        { host: "exload.pro",       pattern: /^\/[a-zA-Z0-9]{12,}/ },
        { host: "fastbit.cc",       pattern: /^\/file\/.+/ },
        { host: "fikper.com",       pattern: /^\/file\/.+/ },
        { host: "file.al",          pattern: /^\/[a-zA-Z0-9]{12,}/ },
        { host: "filedot.xyz",      pattern: /^\/[a-zA-Z0-9]{12,}/ },
        { host: "filerio.in",       pattern: /^\/[a-zA-Z0-9]{12,}/ },
        { host: "filestore.to",     pattern: /^\/file\/.+/ },
        { host: "filextras.com",    pattern: /^\/file\/.+/ },
        { host: "filezip.cc",       pattern: /^\/file\/.+/ },
        { host: "flashbit.cc",       pattern: /^\/file\/.+/ },
        { host: "hexupload.net",    pattern: /^\/[a-zA-Z0-9]{12,}/ },
        { host: "hot4share.com",    pattern: /^\/file\/.+/ },
        { host: "indishare.org",    pattern: /^\/file\/.+/ },
        { host: "isra.cloud",       pattern: /^\/file\/.+/ },
        { host: "mexashare.com",    pattern: /^\/file\/.+/ },
        { host: "mixdrop.ag",       pattern: /^\/f\/.+/ },
        { host: "modsbase.com",     pattern: /^\/download\/.+/ },
        { host: "mp4upload.com",    pattern: /^\/embed-[a-zA-Z0-9]+\.html/ },
        { host: "send.cm",          pattern: /^\/[a-zA-Z0-9]{12,}/ },
        { host: "sendit.cloud",     pattern: /^\/[a-zA-Z0-9]{12,}/ },
        { host: "sendspace.com",    pattern: /^\/file\/.+/ },
        { host: "sharemods.com",    pattern: /^\/download\/.+/ },
        { host: "simfileshare.net", pattern: /^\/download\/.+/ },
        { host: "terabytez.net",    pattern: /^\/file\/.+/ },
        { host: "turbobit.net",     pattern: /[a-z0-9]{15,}\.html$/ },
        { host: "upload42.com",     pattern: /^\/file\/.+/ },
        { host: "uploadbank.com",   pattern: /^\/file\/.+/ },
        { host: "uploadbox.com",    pattern: /^\/file\/.+/ },
        { host: "uploadboy.com",   pattern: /^\/file\/.+/ },
        { host: "uploadev.org",     pattern: /^\/[a-zA-Z0-9]{12,}/ },
        { host: "uploadrar.com",    pattern: /^\/file\/.+/ },
        { host: "voe.sx",           pattern: /^\/e\/.+/ },
        { host: "wayupload.com",    pattern: /^\/file\/.+/ },
        { host: "wipfiles.net",     pattern: /^\/file\/.+/ }
    ];

    // Function to check if a URL is a magnet link OR a supported direct file link
    function isSupportedLink(url) {
        if (url.startsWith('magnet:?')) {
            return true;
        }
        try {
            const parsedUrl = new URL(url);
            const host = parsedUrl.hostname.replace(/^www\./, '');
            const fullPathForMatch = parsedUrl.pathname + parsedUrl.search;
            const isMatch = supportedPatterns.some(h => {
                const hostMatch = h.host === host;
                const patternMatch = h.pattern.test(fullPathForMatch);
                return hostMatch && patternMatch;
            });
            return isMatch;
        } catch (e) {
            console.log(`Debrid Download Helper: Skipping invalid URL: ${href}`);
            return false; // Invalid URL
        }
    }

    // Function to check if the current page's URL matches a supported file hoster page (for top bar display)
    function isFileHosterPage(url) {
        try {
            const parsedUrl = new URL(url);
            const host = parsedUrl.hostname.replace(/^www\./, '');
            const fullPathForMatch = parsedUrl.pathname + parsedUrl.search;
            const isMatch = supportedPatterns.some(h => h.host === host && h.pattern.test(fullPathForMatch));
            console.log(`Debrid Download Helper (v${GM_info.script.version}): isFileHosterPage(${url}) -> ${isMatch}. Host: ${host}, Full path for check: ${fullPathForMatch}`);
            return isMatch;
        } catch (e) {
            console.error(`Debrid Download Helper (v${GM_info.script.version}): Error in isFileHosterPage for URL ${url}`, e);
            return false;
        }
    }

    // --- Settings ---
    let currentProvider = GM_getValue('currentProvider', 'real-debrid');
    let apiKeys = GM_getValue('debridApiKeys', {});
    let apiKey = apiKeys[currentProvider] || '';

    // --- Provider selection menu ---
    function showProviderMenu(event) {
        $('#debrid-provider-menu').remove(); // Remove any existing menu

        const $menu = $('<div id="debrid-provider-menu"></div>').css({
            position: 'fixed',
            // Adjusted positioning for mobile-friendliness
            top: '50%',
            left: '50%',
            transform: 'translate(-50%, -50%)', // Center both horizontally and vertically
            background: '#222',
            border: '1px solid #555',
            padding: '8px 12px',
            borderRadius: '6px',
            zIndex: 999999, // Ensure it's on top
            display: 'flex',
            flexWrap: 'wrap', // Allow items to wrap on smaller screens
            justifyContent: 'center', // Center buttons horizontally
            gap: '12px',
            boxShadow: '0 0 15px rgba(0,0,0,0.8)',
            color: '#eee',
            fontFamily: 'Arial, sans-serif',
            fontSize: '14px',
            userSelect: 'none',
            maxWidth: '90%', // Limit width on mobile
            boxSizing: 'border-box' // Include padding in width
        });

        Object.entries(providers).forEach(([key, provider]) => {
            const isSelected = (key === currentProvider);
            const $btn = $('<button></button>').css({
                background: isSelected ? '#4CAF50' : 'transparent',
                border: isSelected ? '2px solid #4CAF50' : '1px solid #555',
                cursor: 'pointer',
                padding: '10px 15px', // Increased padding for touch targets
                borderRadius: '5px',
                display: 'flex',
                flexDirection: 'column', // Stack icon and text vertically
                alignItems: 'center',
                gap: '5px', // Reduced gap for stacked elements
                color: isSelected ? '#fff' : '#ccc',
                fontWeight: isSelected ? 'bold' : 'normal',
                transition: 'background-color 0.3s, border-color 0.3s',
                minWidth: '100px' // Ensure buttons are large enough for touch
            }).attr('title', provider.name);

            $btn.append($(`<img src="${provider.icon}" width="32" height="32" alt="${provider.name} icon">`).css({ flexShrink: 0 })); // Slightly larger icons for touch
            $btn.append($('<span></span>').text(provider.name));

            $btn.on('click', () => {
                currentProvider = key;
                GM_setValue('currentProvider', currentProvider);
                apiKey = apiKeys[currentProvider] || '';
                $menu.remove();
                console.log(`Debrid Download Helper: Provider switched to ${currentProvider}. Reloading page.`);
                location.reload(); // Auto page refresh implemented here
            });
            $menu.append($btn);
        });
        $('body').append($menu);

        // Close menu on outside click
        $(document).on('click.debridMenu', (e) => {
            if (!$(e.target).closest('#debrid-provider-menu').length) {
                $menu.remove();
                $(document).off('click.debridMenu'); // Deregister handler
            }
        });
    }

    // --- API Key Modal ---
    function showApiKeyModal(providerName, currentKey, callback) {
        $('#debrid-apikey-modal-overlay').remove(); // Remove existing if any

        const $overlay = $('<div id="debrid-apikey-modal-overlay"></div>').css({
            position: 'fixed',
            top: 0,
            left: 0,
            width: '100%',
            height: '100%',
            background: 'rgba(0, 0, 0, 0.7)',
            zIndex: 999998, // Below provider menu, above everything else
            display: 'flex',
            alignItems: 'center',
            justifyContent: 'center'
        });

        const $modal = $('<div id="debrid-apikey-modal"></div>').css({
            background: '#333',
            border: '1px solid #666',
            borderRadius: '8px',
            padding: '25px',
            boxShadow: '0 0 20px rgba(0,0,0,0.9)',
            color: '#eee',
            fontFamily: 'Arial, sans-serif',
            fontSize: '15px',
            textAlign: 'center',
            maxWidth: '90%', // Make modal responsive
            width: '400px', // Max width for larger screens
            boxSizing: 'border-box'
        });

        $modal.html(`
            <h3 style="margin-top: 0; color: #4CAF50;">${providerName} API Key Required</h3>
            <p style="margin-bottom: 20px;">Please enter your API key for ${providerName}.</p>
            <input type="text" id="debrid-api-key-input" value="${currentKey || ''}" placeholder="Enter API Key here" style="
                width: calc(100% - 20px);
                padding: 12px; /* Increased padding for touch */
                margin-bottom: 15px;
                border: 1px solid #555;
                border-radius: 4px;
                background: #444;
                color: #eee;
                font-size: 1.1em; /* Slightly larger font */
            ">
            <button id="debrid-api-key-save" style="
                background: #4CAF50;
                color: white;
                border: none;
                padding: 12px 25px; /* Increased padding for touch */
                border-radius: 5px;
                cursor: pointer;
                font-size: 1.1em; /* Slightly larger font */
                margin-right: 10px;
                transition: background-color 0.2s;
            ">Save</button>
            <button id="debrid-api-key-cancel" style="
                background: #777;
                color: white;
                border: none;
                padding: 12px 25px; /* Increased padding for touch */
                border-radius: 5px;
                cursor: pointer;
                font-size: 1.1em; /* Slightly larger font */
                transition: background-color 0.2s;
            ">Cancel</button>
        `);

        $('body').append($overlay.append($modal));
        $('#debrid-api-key-input').focus();

        $('#debrid-api-key-save').on('click', () => {
            const newKey = $('#debrid-api-key-input').val().trim();
            apiKeys[currentProvider] = newKey;
            GM_setValue('debridApiKeys', apiKeys);
            apiKey = newKey;
            $overlay.remove();
            if (callback) callback(true); // Indicate key was saved
        }).hover(function() { $(this).css('background-color', '#5cb85c'); }, function() { $(this).css('background-color', '#4CAF50'); });

        $('#debrid-api-key-cancel').on('click', () => {
            $overlay.remove();
            if (callback) callback(false); // Indicate user cancelled
        }).hover(function() { $(this).css('background-color', '#888'); }, function() { $(this).css('background-color', '#777'); });

        // Allow closing with Escape key
        $(document).on('keydown.debridApiModal', function(e) {
            if (e.key === "Escape") {
                $overlay.remove();
                $(document).off('keydown.debridApiModal');
                if (callback) callback(false);
            }
        });
    }


    // --- Tampermonkey menu commands ---
    GM_registerMenuCommand("Switch Debrid Provider", () => {
        // When triggered from menu, pass event object for potential positioning,
        // though our updated CSS centers it regardless of event clientX/Y
        showProviderMenu(event);
    });
    GM_registerMenuCommand("Set Debrid API Key (current provider)", () => {
        showApiKeyModal(providers[currentProvider].name, apiKeys[currentProvider] || '');
    });


    // Create a debrid download button (icon only, transparent background, sized, placed after hosting link)
    function createDebridButton(url, $link) {
        const provider = providers[currentProvider];
        // Base width/height for all icons
        const baseIconWidth = 18;
        const baseIconHeight = 18;
        let imgHtml = `<img src="${provider.icon}" width="${baseIconWidth}" height="${baseIconHeight}" alt="${provider.name}">`;
        let inlineStyle = "";

        // Apply specific styles
        if (currentProvider === "real-debrid") {
            inlineStyle += "opacity: 0.8 !important;"; // 20% transparent (80% opacity)
        } else if (currentProvider === "alldebrid") {
            // Make AllDebrid icon a bit bigger (e.g., 20x20px)
            inlineStyle += `width: 20px !important; height: 20px !important;`;
        }

        // Apply inline style if any
        if (inlineStyle) {
            imgHtml = `<img src="${provider.icon}" width="${baseIconWidth}" height="${baseIconHeight}" alt="${provider.name}" style="${inlineStyle}">`;
        }


        const $btn = $('<button>')
            .addClass('debrid-btn')
            .attr('title', `Download with ${provider.name}`)
            .html(imgHtml) // Use the constructed imgHtml
            .on('click', function(e) {
                // If Alt key is pressed, show provider menu instead of downloading
                if (e.altKey) {
                    e.preventDefault();
                    e.stopPropagation(); // Stop propagation to prevent default link click
                    showProviderMenu(e);
                    return false;
                }
                handleDebridDownload(url, $(this));
            });

        $link.after($btn);
        $link.data('debrid-btn-added', true); // Mark link as processed
        return $btn;
    }

    // Add debrid buttons next to supported links and magnet links
    function addIconsToLinks(context = document) {
        let buttonsAddedCount = 0;
        $(context).find('a[href]').each(function() {
            const $link = $(this);
            const href = $link.attr('href');

            // Skip if already processed, no href, or internal fragment link
            if ($link.data('debrid-btn-added') || !href || href.startsWith('#')) {
                return;
            }

            // Resolve relative URLs
            let fullUrl;
            try {
                fullUrl = new URL(href, window.location.href).href;
            } catch (e) {
                console.log(`Debrid Download Helper: Skipping invalid URL: ${href}`);
                return; // Invalid URL
            }

            if (isSupportedLink(fullUrl)) {
                createDebridButton(fullUrl, $link);
                buttonsAddedCount++;
            }
        });
    }

    // --- Add top download bar on supported file pages ---
    function addDownloadBar() {
        console.log(`Debrid Download Helper: Running addDownloadBar`);
        // Remove existing bar if any, to prevent duplicates
        $('#debrid-download-bar').remove();

        const $bar = $(`
            <div id="debrid-download-bar">
                <span>Download this file with ${providers[currentProvider].name}:</span>
                <button class="debrid-bar-action-button" title="Download current page with ${providers[currentProvider].name}">
                    <img src="${providers[currentProvider].icon}" alt="${providers[currentProvider].name}" width="22" height="22">
                </button>
            </div>
        `);

        $bar.find('.debrid-bar-action-button').on('click', function(e) {
            e.preventDefault();
            handleDebridDownload(location.href, $(this));
        });
        $('body').prepend($bar);
        console.log(`Debrid Download Helper: Bar appended to body.`);
    }

    // --- Handle the display logic based on the current URL ---
    function refreshDisplayBasedOnUrl() {
        console.log(`Debrid Download Helper: Running refreshDisplayBasedOnUrl for URL:`, location.href);
        const currentPageIsFileHoster = isFileHosterPage(location.href);

        // Remove all previous indicators to ensure clean state before re-rendering
        $('#debrid-download-bar').remove();
        $('.debrid-btn').remove();
        // Clear the data attribute from ALL links that might have been processed
        // This is crucial for re-processing elements on DOM changes or provider switch
        $('a[data-debrid-btn-added="true"]').each(function() {
            $(this).removeData('debrid-btn-added');
        });


        if (currentPageIsFileHoster) {
            // On a file hoster page, only show the top bar
            addDownloadBar();
            // Disconnect MutationObserver as it's not needed for static file pages
            observer.disconnect();
            console.log(`Debrid Download Helper: Current page is file hoster. Displaying download bar.`);
        } else {
            // On other pages, show icons next to links and observe for new content
            addIconsToLinks();
            // Use MutationObserver to add icons to dynamically loaded content
            observer.observe(document.body, { childList: true, subtree: true });
            console.log(`Debrid Download Helper: Current page is NOT file hoster. Displaying link icons.`);
        }
    }

    // --- Main Download Handler for direct links and magnets ---
    function handleDebridDownload(url, $button) {
        // Check for API key *before* attempting download
        if (!apiKey) {
            showApiKeyModal(providers[currentProvider].name, '', (keySaved) => {
                if (keySaved && apiKey) { // If key was saved successfully, try download again
                    handleDebridDownload(url, $button);
                } else {
                    $button.prop('disabled', false); // Re-enable button if cancelled or not saved
                    $button.html($button.data('original-content')); // Restore original icon
                }
            });
            return; // Stop current download attempt
        }

        const provider = providers[currentProvider];
        $button.prop('disabled', true);
        // Store original content (either img tag or plain text like '✔️')
        $button.data('original-content', $button.html());

        // Use a <span> for the loading spinner and checkmark to avoid issues with img src
        $button.html('<span class="debrid-loader"></span>'); // This will be the spinner

        // Function to show checkmark and keep it permanent
        function showCheckmarkPermanent() {
            $button.html('<span class="debrid-checkmark">✔️</span>'); // Checkmark emoji
            $button.prop('disabled', true); // Keep button disabled after successful download
            // No setTimeout to revert, so it stays
        }

        const isMagnet = url.startsWith('magnet:?');

        if (isMagnet) {
            // --- Magnet Link Handling ---
            if (!provider.apiMagnetUrl) {
                alert(`Magnet support is not implemented for ${provider.name} in this script version.`);
                $button.prop('disabled', false);
                $button.html($button.data('original-content'));
                return;
            }

            let magnetReqUrl, magnetReqMethod, magnetReqHeaders, magnetReqData;

            if (currentProvider === "real-debrid") {
                magnetReqUrl = provider.apiMagnetUrl;
                magnetReqMethod = provider.method; // POST
                magnetReqHeaders = provider.headers(apiKey);
                magnetReqData = provider.magnetData(url);
            } else if (currentProvider === "alldebrid") {
                // AllDebrid magnet upload is a GET request with magnet in URL params
                magnetReqUrl = `https://api.alldebrid.com/v4/magnet/upload?agent=userscript&apikey=${apiKey}&magnet=${encodeURIComponent(url)}`;
                magnetReqMethod = "GET";
                magnetReqHeaders = {};
                magnetReqData = null;
            } else {
                alert(`Magnet support is not implemented for ${provider.name} in this script version.`);
                $button.prop('disabled', false);
                $button.html($button.data('original-content'));
                return;
            }

            GM_xmlhttpRequest({
                method: magnetReqMethod,
                url: magnetReqUrl,
                headers: magnetReqHeaders,
                data: magnetReqData,
                onload: function(response) {
                    console.log(`Debrid Download Helper: API Response for magnet link (${currentProvider}):`, response.responseText);
                    try {
                        const data = JSON.parse(response.responseText);
                        let success = false;
                        if (currentProvider === "real-debrid") {
                            if (provider.parseMagnetResponse(response.responseText)) {
                                console.log("Debrid Download Helper: Real-Debrid magnet added successfully.");
                                window.open("https://real-debrid.com/torrents", "_blank");
                                success = true;
                            } else {
                                alert("Failed to add magnet link to Real-Debrid. Check your API key and magnet link.");
                                console.error(`Debrid Download Helper: Real-Debrid magnet add failed. Response:`, response.responseText);
                            }
                        } else if (currentProvider === "alldebrid") {
                            if (data.status === "success" && data.data.magnets && data.data.magnets.length > 0) {
                                const magnet = data.data.magnets[0];
                                console.log("Debrid Download Helper: AllDebrid magnet data:", magnet);
                                if (magnet.ready && magnet.links && magnet.links.length > 0) {
                                    const downloadUrl = magnet.links[0];
                                    console.log("Debrid Download Helper: AllDebrid magnet direct download URL:", downloadUrl);
                                    const a = document.createElement('a');
                                    a.href = downloadUrl;
                                    a.download = '';
                                    a.style.display = 'none';
                                    document.body.appendChild(a);
                                    a.click();
                                    setTimeout(() => document.body.removeChild(a), 1000);
                                    success = true;
                                } else if (magnet.id) {
                                    console.log("Debrid Download Helper: AllDebrid magnet processing. Opening torrents page.");
                                    window.open(`https://alldebrid.com/magnets/?id=${magnet.id}`, "_blank");
                                    success = true;
                                } else {
                                    alert("Magnet added to AllDebrid, but not ready yet. Check your magnets page manually.");
                                    console.warn("Debrid Download Helper: AllDebrid magnet added but no direct link or ID for opening page.", response.responseText);
                                }
                            } else {
                                alert("Failed to add magnet link to AllDebrid. Check your API key and magnet link.");
                                console.error(`Debrid Download Helper: AllDebrid magnet add failed. Response:`, response.responseText);
                            }
                        }
                        if (success) {
                            showCheckmarkPermanent(); // Changed to permanent checkmark
                        } else {
                            $button.prop('disabled', false);
                            $button.html($button.data('original-content'));
                        }
                    } catch (e) {
                        alert(`Error parsing response from ${provider.name} for magnet link: ` + e.message);
                        console.error(`Debrid Download Helper: Error parsing magnet response from ${provider.name}:`, e, 'Response:', response.responseText);
                        $button.prop('disabled', false);
                        $button.html($button.data('original-content'));
                    }
                },
                onerror: function(response) {
                    alert(`Network error contacting ${provider.name} for magnet link.`);
                    console.error(`Debrid Download Helper: Network error for magnet link with ${provider.name}:`, response);
                    $button.prop('disabled', false);
                    $button.html($button.data('original-content'));
                }
            });
        } else {
            // --- Direct Link Handling ---
            let reqUrl, reqOpts;
            if (provider.method === "GET") {
                reqUrl = provider.buildUrl(apiKey, url);
                reqOpts = { method: "GET", url: reqUrl, headers: provider.headers(apiKey) };
            } else { // POST method
                reqUrl = provider.apiUrl;
                reqOpts = { method: provider.method, url: reqUrl, headers: provider.headers(apiKey), data: provider.data(url) };
            }

            GM_xmlhttpRequest({
                method: reqOpts.method,
                url: reqOpts.url,
                headers: reqOpts.headers,
                data: reqOpts.data,
                onload: function(response) {
                    console.log(`Debrid Download Helper: API Response for direct link (${currentProvider}):`, response.responseText);
                    try {
                        const downloadUrl = provider.parseResponse(response.responseText);
                        console.log(`Debrid Download Helper: Parsed Download URL:`, downloadUrl);
                        if (downloadUrl) {
                            const a = document.createElement('a');
                            a.href = downloadUrl;
                            a.download = '';
                            a.style.display = 'none';
                            document.body.appendChild(a);
                            a.click();
                            setTimeout(() => document.body.removeChild(a), 1000);
                            showCheckmarkPermanent(); // Changed to permanent checkmark
                        } else {
                            alert('Failed to get direct download link. Check your API key and link, or the file might be offline.');
                            console.error(`Debrid Download Helper: Failed to parse download URL from response:`, response.responseText);
                            $button.prop('disabled', false);
                            $button.html($button.data('original-content')); // Revert on failure
                        }
                    } catch (e) {
                        alert('Error parsing response from debrid provider for direct link.');
                        console.error('Debrid Download Helper: Error during response parsing:', e, 'Response:', response.responseText);
                        $button.prop('disabled', false);
                        $button.html($button.data('original-content')); // Revert on failure
                    }
                },
                onerror: function(response) {
                    alert('Network error contacting debrid provider for direct link.');
                    console.error('Debrid Download Helper: Network error for direct link:', response);
                    $button.prop('disabled', false);
                    $button.html($button.data('original-content')); // Revert on network error
                }
            });
        }
    }

    // --- MutationObserver for dynamic content (only active on non-file-hoster pages) ---
    const observer = new MutationObserver(mutations => {
        mutations.forEach(mutation => {
            if (mutation.type === 'childList' && mutation.addedNodes.length > 0) {
                mutation.addedNodes.forEach(node => {
                    if (node.nodeType === 1) { // Element node
                        addIconsToLinks(node);
                    }
                });
            }
        });
    });

    // --- Init ---
    $(document).ready(function() {
        console.log(`Debrid Download Helper (v${GM_info.script.version}): Document ready. Initializing script.`);
        refreshDisplayBasedOnUrl(); // Initial check and display

        GM_addStyle(`
            /* General button/icon styles */
            .debrid-btn {
                background: transparent !important;
                border: none !important;
                padding: 0 !important;
                margin: 0 0 0 5px !important;
                cursor: pointer !important;
                display: inline-flex !important;
                align-items: center !important;
                justify-content: center !important;
                width: 22px !important;
                height: 22px !important;
                vertical-align: middle !important;
                box-sizing: border-box !important;
            }
            .debrid-btn img {
                display: block !important;
                width: 18px !important;
                height: 18px !important;
                opacity: 1 !important; /* Base opacity, overridden by inline style for RD and Alldebrid */
                transition: opacity 0.2s !important;
                border-radius: 3px !important;
            }
            .debrid-btn:hover img {
                opacity: 1 !important; /* Remain fully visible on hover */
            }

            /* Spinner and Checkmark styles */
            .debrid-loader {
                display: inline-block;
                width: 18px;
                height: 18px;
                border: 2px solid #f3f3f3; /* Light grey */
                border-top: 2px solid #4CAF50; /* Green */
                border-radius: 50%;
                animation: spin 1s linear infinite;
            }
            .debrid-checkmark {
                font-size: 16.2px; /* 10% smaller than 18px */
                line-height: 1; /* Ensure vertical alignment */
                color: #4CAF50; /* Green color for checkmark */
                opacity: 0.8; /* 20% transparent */
            }
            @keyframes spin {
                0% { transform: rotate(0deg); }
                100% { transform: rotate(360deg); }
            }

            /* Provider menu styles */
            #debrid-provider-menu {
                /* Existing styles */
                /* Overrides for mobile responsiveness */
                padding: 15px !important; /* More padding for touch */
                max-width: 90vw !important; /* Max width 90% of viewport width */
                min-width: 280px; /* Minimum width to ensure readability */
                box-sizing: border-box; /* Include padding in width calculation */
            }

            #debrid-provider-menu button {
                width: calc(50% - 12px); /* Two buttons per row with gap */
                margin: 6px; /* Spacing between buttons */
            }

            @media (max-width: 480px) { /* Adjust for very small screens if needed */
                #debrid-provider-menu button {
                    width: calc(100% - 12px); /* One button per row on very small screens */
                }
            }


            /* Download bar styles */
            #debrid-download-bar {
                position: fixed;
                top: 0;
                left: 50%;
                transform: translateX(-50%);
                background: #282c34;
                color: white;
                padding: 8px 15px;
                border-radius: 0 0 8px 8px;
                box-shadow: 0 4px 8px rgba(0,0,0,0.3);
                z-index: 100000;
                display: flex;
                align-items: center;
                gap: 10px;
                font-family: Arial, sans-serif;
                font-size: 14px;
                max-width: 95vw; /* Limit width for mobile */
                box-sizing: border-box;
            }
            #debrid-download-bar .debrid-bar-action-button {
                background: transparent;
                border: none;
                padding: 0;
                cursor: pointer;
            }
            #debrid-download-bar .debrid-bar-action-button img {
                width: 22px;
                height: 22px;
                vertical-align: middle;
                opacity: 1 !important; /* MADE FULLY OPAQUE AS REQUESTED */
                border-radius: 3px;
                /* Removed transition and hover opacity rule for simplicity since it's always 1 now */
            }

            /* API Key Modal Styles */
            #debrid-apikey-modal-overlay {
                /* Styles defined inline in showApiKeyModal for dynamic positioning */
            }
            #debrid-apikey-modal {
                /* Styles defined inline in showApiKeyModal for dynamic positioning */
            }
            #debrid-apikey-modal button:hover {
                filter: brightness(1.1); /* Slight brightness change on hover */
            }
            /* Specific styles for mobile modal input/buttons */
            #debrid-api-key-input {
                font-size: 1.1em !important;
                padding: 12px !important;
            }
            #debrid-apikey-modal button {
                font-size: 1.1em !important;
                padding: 12px 25px !important;
            }
        `);
    });
})(jQuery);

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址