Adds direct-download buttons and links for Pixeldrain files using an alternate proxy — inspired by 'Pixeldrain Download Bypass' by hhoneeyy and MegaLime0
// ==UserScript==
// @name Pixeldrain DL Bypass
// @namespace https://greatest.deepsurf.us/users/821661
// @version 1.5.6a
// @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/*
// @run-at document-start
// @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 CONFIG = {
defaultBypassURL: "pd.1drv.eu.org",
customBypassURL: GM_getValue("custom_proxy", ""),
jDownloaderURL: "http://127.0.0.1:9666/flash/addcnl",
commands: {
showMediaWithPoxy: {
label: "Load media files with proxy",
state: true,
effect: initMediaWithProxy,
},
scrollbarNames: {
label: "Horizontal scrollbar for long names",
state: false,
effect: () => GM_addStyle(".container-names { overflow: scroll; } .file-name { overflow: initial }"),
},
directDownload: {
label: "Direct download",
state: false,
},
},
};
const selectedProxy = CONFIG.customBypassURL || 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 (CONFIG.commands.directDownload.state) {
window.open(`${url}?download`, "_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 to download other 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(files) {
const url = Array.isArray(files) ? files.map((file) => `https://${selectedProxy}/${file.id}`).join("\n") : `https://${selectedProxy}/${files.id}`;
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", "DL 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", "JD (Link)");
downloadButton.addEventListener("click", () => downloadFile(name, id, downloadButton));
openButton.addEventListener("click", async () => {
openBypassURL(fileData);
});
copyButton.addEventListener("click", () => copyBypassURL(fileData));
sendToJDownloaderButton.addEventListener("click", async () => {
sendToJDownloader(fileData.proxyFinalURL);
});
setBypassURL([fileData]).then(() => document.body.classList.add("final-urls-loaded"));
separator.insertAdjacentElement("afterend", separator.cloneNode());
separator.insertAdjacentElement("afterend", sendToJDownloaderButton);
separator.insertAdjacentElement("afterend", copyButton);
separator.insertAdjacentElement("afterend", openButton);
separator.insertAdjacentElement("afterend", downloadButton);
}
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", "DL Selected");
const dlAllButton = createButton("download", "Bypass download all files", "DL All");
const openButton = createButton("open_in_new", "Open bypass URL", "Open bypass");
const copyButton = createButton("content_copy", "Copy bypass url", "Copy URL");
const copyAllButton = createButton("content_copy", "Copy all bypass url", "Copy All");
const showUrlsButton = createButton("link", "Show bypass URLs", "Show bypass");
const sendAllToJDButton = createButton("add_link", "Add links to JDownloader", "JD (All)");
const sendSelectedToJDButton = createButton("add_link", "Add link to JDownloader", "JD (Sel.)");
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);
});
copyAllButton.addEventListener("click", () => copyBypassURL(listData.files));
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", separator.cloneNode());
separator.insertAdjacentElement("afterend", sendAllToJDButton);
separator.insertAdjacentElement("afterend", showUrlsButton);
separator.insertAdjacentElement("afterend", copyAllButton);
separator.insertAdjacentElement("afterend", dlAllButton);
separator.insertAdjacentElement("afterend", separator.cloneNode());
separator.insertAdjacentElement("afterend", sendSelectedToJDButton);
separator.insertAdjacentElement("afterend", copyButton);
separator.insertAdjacentElement("afterend", openButton);
separator.insertAdjacentElement("afterend", dlSelectedButton);
}
function registerCommands() {
GM_registerMenuCommand(`Current proxy: ${CONFIG.customBypassURL || 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();
});
for (const key in CONFIG.commands) {
const command = CONFIG.commands[key];
const savedState = GM_getValue(key, command.state);
command.state = savedState;
if (command.state) command.effect?.();
GM_registerMenuCommand(`${savedState ? "⧯" : "⧮"} ${command.label || key}`, () => {
GM_setValue(key, !command.state);
unsafeWindow.location.reload();
});
}
}
function initMediaWithProxy() {
const callback = () => {
const nodes = document.querySelectorAll('[src^="/api/file"]:is(source, video, audio, img)');
for (const node of nodes) {
console.log("Modifying media src to use proxy:", node);
const src = node.getAttribute("src");
node.setAttribute("src", src.replace("/api/file", `https://${selectedProxy}`));
}
const bgkNodes = document.querySelectorAll('[style*="/api/file"]');
for (const node of bgkNodes) {
console.log("Modifying background image to use proxy:", node);
const style = node.getAttribute("style");
node.setAttribute("style", style.replaceAll("/api/file", `https://${selectedProxy}`));
}
};
const observer = new MutationObserver(callback);
observer.observe(document.documentElement, { childList: true, subtree: true });
}
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: 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 initOnDOM() {
CONFIG.viewer_data = unsafeWindow.viewer_data;
CONFIG.api_response = unsafeWindow.viewer_data.api_response;
CONFIG.dataType = unsafeWindow.viewer_data.type;
if (!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"], .toolbar .separator:nth-child(2)) {
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: flex;
}
.file-name {
max-width: 550px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
`);
switch (CONFIG.dataType) {
case "file":
handleSingleFile(separator, CONFIG.api_response);
break;
case "list":
handleFileList(separator, CONFIG.api_response);
break;
default:
console.warn(`File type "${CONFIG.dataType}" not supported.`);
}
}
function init() {
registerCommands();
document.addEventListener("DOMContentLoaded", initOnDOM);
}
init();