// ==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
};