Real-Debrid Premium Link Converter

Convert standard links into premium links using Real-Debrid

// ==UserScript==
// @name         Real-Debrid Premium Link Converter
// @version      4
// @grant        GM.xmlHttpRequest
// @grant        GM_registerMenuCommand
// @grant        GM_setValue
// @grant        GM_getValue
// @namespace    https://gf.qytechs.cn/en/users/807108-jeremy-r
// @include      *://*
// @exclude      https://real-debrid.com/*
// @description  Convert standard links into premium links using Real-Debrid
// @icon         https://icons.duckduckgo.com/ip2/real-debrid.com.ico
// @author       JRem
// @license      MIT
// ==/UserScript==

let targetURLs = GM_getValue('targetURLs', []);
let token = GM_getValue('api_token', []);

// Register menu commands for the script
function createMenu() {
    GM_registerMenuCommand("Update API Token", updatetoken);
    GM_registerMenuCommand("Refresh DDL Domains", updateDDLDomains);
    console.log("Menu commands registered.");
}

// Ensure `updateDDLDomains` runs at least once every 24 hours
function ensureUpdateDDLDomains() {
    const lastRunTimestamp = GM_getValue('lastUpdateTimestamp', 0);
    const now = Date.now();

    if (now - lastRunTimestamp >= 24 * 60 * 60 * 1000) {
        console.log("More than 24 hours since last update. Running `updateDDLDomains`...");
        updateDDLDomains();
        GM_setValue('lastUpdateTimestamp', now);
    } else {
        console.log("`updateDDLDomains` already ran within the last 24 hours.");
    }
}

// Fetch the domains from Real-Debrid API and update the domain cache
function updateDDLDomains() {
    console.log("Updating DDL Domains...");

    GM.xmlHttpRequest({
        method: 'GET',
        url: 'https://api.real-debrid.com/rest/1.0/hosts/domains',
        headers: {
            'Authorization': `Bearer ${GM_getValue('api_token', '')}`
        },
        onload: function (response) {
            if (response.status === 200) {
                try {
                    const domains = JSON.parse(response.responseText);
                    if (domains.length > 0) {
                        targetURLs = domains; // Replace the array with the new list
                        GM_setValue('targetURLs', targetURLs);
                        GM_setValue('lastUpdateTimestamp', Date.now());
                        console.log('Fetched domains:', targetURLs);
                        showToast(`${domains.length} domains found and saved.`);
                    } else {
                        console.error('No domains found. Re-fetching...');
                        showToast('No domains found. Attempting re-fetch...');
                        updateDDLDomains();
                    }
                } catch (error) {
                    console.error('Failed to parse the API response:', error);
                }
            } else {
                console.error('Failed to fetch domains. Status:', response.status);
            }
        },
        onerror: function () {
            console.error('An error occurred while fetching DDL domains.');
        }
    });
}

// Update the API token
function updatetoken() {
    GM.xmlHttpRequest({
        method: 'GET',
        url: 'https://real-debrid.com/apitoken',
        onload: function (response) {
            if (response.status === 200) {
                const responseText = response.responseText;
                const tokenMatch = responseText.match(/document\.querySelectorAll\('input\[name=private_token\]'\)\[0\]\.value\s*=\s*'([^']+)'/);

                if (tokenMatch && tokenMatch[1]) {
                    const token = tokenMatch[1];
                    GM_setValue('api_token', token); // Save the token
                    console.log('API token updated successfully:', token); // Debug output
                    showToast('API token updated successfully.');
                } else {
                    console.error('Failed to extract the token. Prompting user...');
                    const manualToken = prompt('API token not found in the response. Please enter it manually:');
                    if (manualToken) {
                        GM_setValue('api_token', manualToken);
                        console.log('API token manually entered:', manualToken);
                        showToast('API token saved successfully.');
                    } else {
                        console.error('No token entered. Unable to proceed.');
                        showToast('Token update failed. Please try again.');
                    }
                }
            } else {
                console.error('Failed to fetch the API token. Status:', response.status);
            }
        },
        onerror: function () {
            console.error('An error occurred while updating the API token.');
        }
    });
}

// Display a toast message in the center of the screen
function showToast(message) {
    const toast = document.createElement('div');
    toast.textContent = message;
    toast.style.position = 'fixed';
    toast.style.top = '50%';
    toast.style.left = '50%';
    toast.style.transform = 'translate(-50%, -50%)';
    toast.style.backgroundColor = '#333';
    toast.style.color = '#fff';
    toast.style.padding = '10px 20px';
    toast.style.borderRadius = '5px';
    toast.style.boxShadow = '0px 0px 10px rgba(0, 0, 0, 0.5)';
    toast.style.zIndex = '9999';
    toast.style.fontSize = '16px';
    toast.style.textAlign = 'center';
    toast.style.animation = 'fadeOut 3s ease forwards';

    document.body.appendChild(toast);

    // Remove the toast after the animation
    setTimeout(() => {
        document.body.removeChild(toast);
    }, 3000);

    // Add fade-out animation
    const style = document.createElement('style');
    style.textContent = `
        @keyframes fadeOut {
            0% { opacity: 1; }
            80% { opacity: 0.8; }
            100% { opacity: 0; }
        }
    `;
    document.head.appendChild(style);
}

// Check if the token and domain list are set
function checkInitialization() {
    const token = GM_getValue('api_token', '');
    if (!token) {
        console.log('API token not found. Attempting to fetch...');
        updatetoken();
    }

    if (targetURLs.length === 0) {
        console.log('Domain list is empty. Fetching domains...');
        updateDDLDomains();
    }

    ensureUpdateDDLDomains();
}

const processedURLs = new Set(); // Track processed URLs

// Check if the current URL contains any of the target URLs
function isTargetURL() {
    const currentUrl = window.location.href;
    return targetURLs.some(target => currentUrl.includes(target));
}

// Generate a regex pattern based on targetURLs
function generateUrlRegex() {
    const domains = targetURLs.map(url => url.replace(/\./g, '\\.') + '[^\s"]*');
    const pattern = `https:\\/\\/(?:${domains.join('|')})`;
    return new RegExp(pattern, 'g');
}

function createFastDownloadButton(linkElement, fileURL) {
    let button = document.createElement('button');
    button.innerHTML = 'Send to RD';
    button.style.marginLeft = '5px'; // Add space to the left of the button
    button.style.padding = '2px 5px'; // Add space to the right of the button
    button.style.backgroundColor = 'black'; // Set button background color to red
    button.style.color = 'white'; // Optional: Set button text color to white for better contrast
    button.style.borderRadius = '5px'; // Add rounded corners
    button.onclick = () => {
        GM.xmlHttpRequest({
            method: 'POST',
            url: 'https://app.real-debrid.com/rest/1.0/unrestrict/link',
            headers: {
                "authorization": `Bearer ${token}`
            },
            data: `link=${encodeURIComponent(fileURL)}&password=`,
            onload: (response) => {
                const jsonData = JSON.parse(response.responseText);
                if (jsonData.download !== undefined) {
                    linkElement.href = jsonData.download;
                    linkElement.textContent = jsonData.filename;
                } else {
                    linkElement.textContent += ' - failed';
                }
                button.remove();
            }
        });
    };
    linkElement.setAttribute('realdebrid', 'true');
    linkElement.insertAdjacentElement('afterend', button);
}

// Create a button for magnet links
function createMagnetButton(linkElement, fileURL) {
    let button = document.createElement('button');
    button.innerHTML = 'Send Magnet to RD';
    button.style.marginLeft = '5px';
    button.style.padding = '2px 5px';
    button.style.backgroundColor = 'green';
    button.style.color = 'white';
    button.style.borderRadius = '5px';
    button.onclick = () => {
        GM.xmlHttpRequest({
            method: 'POST',
            url: 'https://api.real-debrid.com/rest/1.0/torrents/addMagnet',
            headers: {
                "authorization": `Bearer ${token}`
            },
            data: `magnet=${encodeURIComponent(fileURL)}`,
            onload: (response) => {
                const jsonData = JSON.parse(response.responseText);
                if (jsonData.id) {
                    showToast('Magnet successfully added to Real-Debrid.');
                } else {
                    showToast('Failed to add magnet link.');
                }
                button.remove();
            }
        });
    };
    linkElement.setAttribute('realdebrid-magnet', 'true');
    linkElement.insertAdjacentElement('afterend', button);
}

function processTextNode(textNode) {
    const textContent = textNode.textContent;
    const urlRegex = generateUrlRegex();
    let match;
    while ((match = urlRegex.exec(textContent)) !== null) {
        const url = match[0];
        if (processedURLs.has(url)) {
            continue; // Skip URLs that have already been processed
        }
        for (let targetURL of targetURLs) {
            if (url.includes(targetURL)) {
                const linkElement = document.createElement('a');
                linkElement.href = url;
                linkElement.textContent = url;
                linkElement.style.display = 'block'; // Ensure each link appears on a new line
                textNode.parentNode.insertBefore(linkElement, textNode);
                createFastDownloadButton(linkElement, url);
                processedURLs.add(url); // Mark URL as processed
            }
        }
    }
}

function processPreElements() {
    let preElements = document.getElementsByTagName('pre');
    for (let i = 0; i < preElements.length; i++) {
        let preElement = preElements[i];
        let textNodes = Array.from(preElement.childNodes).filter(node => node.nodeType === Node.TEXT_NODE);
        textNodes.forEach(node => processTextNode(node));
    }
}

// Process links
function processLinks() {
    const links = document.querySelectorAll('a[href]');
    links.forEach(link => {
        const href = link.href;
        if (href.startsWith('magnet:?')) {
            if (!link.hasAttribute('realdebrid-magnet')) {
                createMagnetButton(link, href);
            }
        } else if (targetURLs.some(domain => href.includes(domain))) {
            if (!link.hasAttribute('realdebrid')) {
                createFastDownloadButton(link, href);
            }
        }
    });
}

function observeLinks() {
    processLinks();
    if (!isTargetURL()) {
        // Process anchor tags
        let links = document.getElementsByTagName('a');
        for (let i = 0; i < links.length; i++) {
            let link = links[i];
            if (link.getAttribute('realdebrid')) {
                continue;
            }
            for (let targetURL of targetURLs) {
                if (link.href.includes(targetURL) && !processedURLs.has(link.href)) {
                    createFastDownloadButton(link, link.href);
                    processedURLs.add(link.href); // Mark URL as processed
                }
            }
        }

        // Process text nodes in <pre> elements
        processPreElements();

        // Process text nodes in the rest of the document
        const iterator = document.createNodeIterator(
            document.body,
            NodeFilter.SHOW_TEXT,
            null,
            false
        );

        let node;
        while ((node = iterator.nextNode())) {
            if (node.parentNode.tagName !== 'PRE' && !node.parentNode.querySelector('a[realdebrid]')) { // Skip nodes with processed children
                processTextNode(node);
            }
        }
    }}

// Debounce function to limit how often observeLinks is called
function debounce(func, wait) {
    let timeout;
    return function (...args) {
        clearTimeout(timeout);
        timeout = setTimeout(() => func.apply(this, args), wait);
    };
}

// Observe the document body for changes
const observer = new MutationObserver(debounce(observeLinks, 500));
observer.observe(document.body, { childList: true, subtree: true });

// Initialize the script when the page loads
window.onload = () => {
    createMenu(); // Register the menu commands
    checkInitialization(); // Ensure token and domain list are set
    observeLinks; //observe the links when the page first loads
};

QingJ © 2025

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