// ==UserScript==
// @name Pixeldrain DL Bypass
// @namespace https://greasyfork.org/users/821661
// @version 1.5.2
// @description Adds direct-download buttons and links for Pixeldrain files using an alternate proxy — inspired by 'Pixeldrain Download Bypass' by hhoneeyy and MegaLime0
// @author hdyzen
// @match https://pixeldrain.com/*
// @match https://pixeldrain.net/*
// @icon https://www.google.com/s2/favicons?domain=pixeldrain.com/&sz=64
// @grant GM_xmlhttpRequest
// @grant GM_registerMenuCommand
// @grant GM_addStyle
// @grant GM_getValue
// @grant GM_setValue
// @license GPL-3.0-only
// ==/UserScript==
const PDLB_CONFIG = {
defaultBypassURL: "pd.1drv.eu.org",
customBypassURL: GM_getValue("custom_proxy", ""),
jDownloaderURL: "http://127.0.0.1:9666/flash/addcnl",
preferences: {
scrollbarNames: {
state: GM_getValue("scrollbar-names", false),
effect: () => GM_addStyle(".container-names { overflow: scroll; } .file-name { overflow: initial }"),
},
directDownload: {
state: GM_getValue("direct-download", false),
},
},
viewer_data: unsafeWindow.viewer_data,
api_response: unsafeWindow.viewer_data.api_response,
dataType: unsafeWindow.viewer_data.type,
};
const selectedProxy = PDLB_CONFIG.customBypassURL || PDLB_CONFIG.defaultBypassURL;
function createButton(iconName, title, text) {
const template = document.createElement("template");
template.innerHTML = `
<button class="toolbar_button svelte-jngqwx" title="${title}">
<i class="icon">${iconName}</i>
<span class="svelte-jngqwx">${text}</span>
</button>
`;
return template.content.firstElementChild;
}
function showModal(title, content, extraContent = "") {
const MODAL_HTML = `
<div class="background svelte-1f8gt9n" style="z-index: 10001;" role="dialog">
<div class="window svelte-1f8gt9n" role="dialog" aria-modal="true" style="max-height: 80%; max-width: 80%; ">
<div class="header svelte-1f8gt9n">
<span class="title svelte-1f8gt9n" style="padding-inline: calc(2rem + 32px) 2rem;">${title}</span>
<button class="button svelte-1ef47mx round close-button">
<i class="icon">close</i>
</button>
</div>
<div class="body svelte-1f8gt9n" style="padding: 1rem;">
<div class="container svelte-1j8hfe6" style="display: flex; flex-direction: row;justify-content: center;align-items: center;">
${content}
</div>
<div class="container svelte-1j8hfe6" style="display: flex; flex-direction: row;justify-content: center;align-items: center;">
${extraContent}
</div>
</div>
</div>
</div>
`;
const template = document.createElement("template");
template.innerHTML = MODAL_HTML;
const modalElement = template.content.firstElementChild;
modalElement.addEventListener("click", (event) => {
if (event.target.matches(".background") || event.target.closest(".close-button")) {
modalElement.remove();
}
});
document.body.insertAdjacentElement("afterbegin", modalElement);
return () => modalElement.remove();
}
function downloadFile(fileName, fileID, el) {
return new Promise((resolve, reject) => {
const url = `https://${selectedProxy}/${fileID}`;
if (PDLB_CONFIG.preferences.directDownload.state) {
window.open(url, "_blank");
return resolve();
}
GM_xmlhttpRequest({
url,
responseType: "blob",
onload(event) {
resolve(event);
if (event.status !== 200) {
showModal("Download error", `The server probably blocked download of the file.`);
return;
}
const a = document.createElement("a");
a.target = "_blank";
a.href = URL.createObjectURL(event.response);
a.download = fileName;
a.click();
el.style.setProperty("--loaded", "0");
},
onerror(event) {
reject(event);
},
onprogress(event) {
el.style.setProperty("--loaded", `${(event.loaded * el.clientWidth) / event.total}px`);
},
});
});
}
async function massiveDownload(files, el) {
for (const file of files) {
try {
if (file.availability || file.availability_message) {
showModal(
file.availability,
`Error on download file: ${file.name}<br>Server availability message: ${file.availability_message}.<br>Trying download of others files.`,
);
continue;
}
const res = await downloadFile(file.name, file.id, el);
if (res.loaded === 0) {
throw new Error("0 bytes loaded from response.");
}
} catch (error) {
console.error(`Failed to download ${file.name}:`, error);
showModal("Download error", `Failed to download ${file.name}.`);
}
}
}
function copyBypassURL(fileID) {
const url = `https://${selectedProxy}/${fileID}`;
navigator.clipboard
.writeText(url)
.then(() => showModal("URL copied", "The bypass URL has been copied to your clipboard."))
.catch(() => showModal("Copy failed", "Could not copy the URL. Please copy it manually."));
}
async function openBypassURL(file) {
const w = window.open("about:blank", "_blank");
const finalURL = file.proxyFinalURL || (await getFinalURL(`https://${selectedProxy}/${file.id}`));
w.document.write(`
<html style="background: #000;">
<body style="margin:0">
<video src="${finalURL}" controls autoplay style="width:100%;height:100%"></video>
</body>
</html>
`);
w.document.close();
}
function handleSingleFile(separator, fileData) {
const { name, id } = fileData;
const downloadButton = createButton("download", "Bypass download", "Download bypass");
const openButton = createButton("open_in_new", "Open bypass URL", "Open bypass");
const copyButton = createButton("content_copy", "Copy bypass URL", "Copy bypass");
const sendToJDownloaderButton = createButton("add_link", "Add link to JDownloader", "Add link to JDownloader");
downloadButton.addEventListener("click", () => downloadFile(name, id, downloadButton));
openButton.addEventListener("click", async () => {
openBypassURL(fileData);
});
copyButton.addEventListener("click", () => copyBypassURL(id));
sendToJDownloaderButton.addEventListener("click", async () => {
sendToJDownloader(fileData.proxyFinalURL);
});
setBypassURL([fileData]).then(() => document.body.classList.add("final-urls-loaded"));
separator.insertAdjacentElement("afterend", downloadButton);
downloadButton.insertAdjacentElement("afterend", openButton);
openButton.insertAdjacentElement("afterend", copyButton);
copyButton.insertAdjacentElement("afterend", sendToJDownloaderButton);
sendToJDownloaderButton.insertAdjacentElement("afterend", separator.cloneNode());
}
function handleFileList(separator, listData) {
const availableFiles = [];
const bypassURLS = [];
let filesLinkHTML = "";
for (const file of listData.files) {
if (file.availability || file.availability_message) continue;
availableFiles.push(file);
bypassURLS.push(`https://${selectedProxy}/${file.id}`);
filesLinkHTML += `
<a class="file-name"
target="_blank"
rel="noopener noreferrer"
title="${file.name}"
href="https://${selectedProxy}/${file.id}">
${file.name}
</a>`;
}
const containerFileURLS = `<div class="indent container-names" style="display: flex; flex-direction: column;justify-content: center;align-items: center;">${filesLinkHTML}</div>`;
const containerBypassURLS = `<pre class="indent" style="padding-inline: .5rem; overflow: initial;">${bypassURLS.join("\n")}</pre>`;
const dlSelectedButton = createButton("download", "Bypass download selected file", "Download selected");
const dlAllButton = createButton("download", "Bypass download all files", "Download all");
const openButton = createButton("open_in_new", "Open bypass URL", "Open bypass");
const copyButton = createButton("content_copy", "Copy bypass url", "Copy bypass");
const showUrlsButton = createButton("link", "Show bypass URLs", "Show bypass");
const sendAllToJDButton = createButton("add_link", "Add links to JDownloader", "Add links to JDownloader");
const sendSelectedToJDButton = createButton("add_link", "Add link to JDownloader", "Add link to JDownloader");
dlSelectedButton.addEventListener("click", () => {
const selectedFile = listData.files.find((file) => file.selected);
if (selectedFile.availability || selectedFile.availability_message) {
showModal(selectedFile.availability, selectedFile.availability_message);
return;
}
downloadFile(selectedFile.name, selectedFile.id, dlSelectedButton);
});
dlAllButton.addEventListener("click", () => massiveDownload(listData.files, dlAllButton));
openButton.addEventListener("click", async () => {
const selectedFile = listData.files.find((file) => file.selected);
openBypassURL(selectedFile);
});
copyButton.addEventListener("click", () => {
const selectedFile = listData.files.find((file) => file.selected);
copyBypassURL(selectedFile.id);
});
showUrlsButton.addEventListener("click", () => showModal("Bypass URLs", containerFileURLS + containerBypassURLS));
sendAllToJDButton.addEventListener("click", async () => {
const urls = listData.files.map((file) => file.proxyFinalURL).join("\r\n");
sendToJDownloader(urls);
});
sendSelectedToJDButton.addEventListener("click", async () => {
const selectedFile = listData.files.find((file) => file.selected);
sendToJDownloader(selectedFile.proxyFinalURL);
});
setBypassURL(availableFiles).then(() => document.body.classList.add("final-urls-loaded"));
separator.insertAdjacentElement("afterend", dlSelectedButton);
dlSelectedButton.insertAdjacentElement("afterend", dlAllButton);
dlAllButton.insertAdjacentElement("afterend", openButton);
openButton.insertAdjacentElement("afterend", copyButton);
copyButton.insertAdjacentElement("afterend", showUrlsButton);
showUrlsButton.insertAdjacentElement("afterend", sendSelectedToJDButton);
sendSelectedToJDButton.insertAdjacentElement("afterend", sendAllToJDButton);
sendAllToJDButton.insertAdjacentElement("afterend", separator.cloneNode());
}
function registerCommands() {
GM_registerMenuCommand(`Current proxy: ${PDLB_CONFIG.customBypassURL || PDLB_CONFIG.defaultBypassURL}`, () => {});
GM_registerMenuCommand("Set custom proxy", () => {
const proxyDomain = prompt("Set your custom proxy", GM_getValue("custom_proxy", ""));
GM_setValue("custom_proxy", proxyDomain || "");
unsafeWindow.location.reload();
});
GM_registerMenuCommand(`${PDLB_CONFIG.preferences.directDownload.state ? "ON" : "OFF"}: Direct download`, () => {
GM_setValue("direct-download", !PDLB_CONFIG.preferences.directDownload.state);
unsafeWindow.location.reload();
});
GM_registerMenuCommand(`${PDLB_CONFIG.preferences.scrollbarNames.state ? "ON" : "OFF"}: Horizontal scrollbar for long names`, () => {
GM_setValue("scrollbar-names", !PDLB_CONFIG.preferences.scrollbarNames.state);
unsafeWindow.location.reload();
});
}
async function setBypassURL(availableFiles) {
const promises = availableFiles.map(async (file) => {
const finalURL = await getFinalURL(`https://${selectedProxy}/${file.id}`);
file.proxyFinalURL = finalURL.replace("?download", "");
});
await Promise.all(promises);
}
function getFinalURL(url) {
return new Promise((resolve, reject) => {
GM_xmlhttpRequest({
url,
method: "HEAD",
onload(event) {
resolve(event.finalUrl);
},
onerror: (ev) => {
console.error("Error getting final URL", ev);
showModal("Error getting final URL", "The proxy is probably down or blocking the request.");
reject(ev);
},
});
});
}
function sendToJDownloader(urls) {
const data = {
urls,
source: urls,
};
GM_xmlhttpRequest({
method: "POST",
url: PDLB_CONFIG.jDownloaderURL,
headers: {
"Content-Type": "application/json",
},
data: JSON.stringify(data),
onerror(ev) {
console.error("Error sending to jDownloader", ev);
showModal("Error sending to jDownloader", "jDownloader is probably closed or listening on another port.");
},
});
}
function init() {
if (!PDLB_CONFIG.viewer_data) {
console.warn("Viewer data not found. Script may not function correctly.");
return;
}
const separator = document.querySelector(".toolbar > .separator.svelte-jngqwx");
if (!separator) {
console.warn("Toolbar separator not found. Cannot add buttons.");
return;
}
GM_addStyle(`
.file_preview_row:has(.gallery) :where([title="Bypass download selected file"], [title="Copy bypass url"], [title="Add link to JDownloader"], [title="Open bypass URL"]) {
display: none !important;
}
[title="Bypass download"], [title="Bypass download selected file"], [title="Bypass download all files"] {
box-shadow: inset var(--highlight_background) var(--loaded, 0) 0;
transition: 0.3s;
}
[title="Add link to JDownloader"], [title="Add links to JDownloader"] {
display: none;
}
.final-urls-loaded :where([title="Add link to JDownloader"], [title="Add links to JDownloader"]) {
display: block;
}
.file-name {
max-width: 550px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
`);
switch (PDLB_CONFIG.dataType) {
case "file":
handleSingleFile(separator, PDLB_CONFIG.api_response);
break;
case "list":
handleFileList(separator, PDLB_CONFIG.api_response);
break;
default:
console.warn(`File type "${PDLB_CONFIG.dataType}" not supported.`);
}
registerCommands();
for (const key in PDLB_CONFIG.preferences) {
const state = PDLB_CONFIG.preferences[key].state;
if (state) PDLB_CONFIG.preferences[key].effect?.();
}
}
init();