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