// ==UserScript==
// @name Pixeldrain DL Bypass
// @namespace https://greasyfork.org/users/821661
// @version 1.4.4
// @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", ""),
preferences: {
"scrollbar-names": {
state: GM_getValue("scrollbar-names", false),
effect: () => {
GM_addStyle(".container-names { overflow: scroll; } .file-name { overflow: initial }");
},
},
},
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 createElementWithListener(html, type, listener) {
if (html instanceof Element) {
html.addEventListener(type, listener);
return html;
}
const div = wrapHTMLToElement(html);
div.addEventListener(type, listener);
return div;
}
function wrapHTMLToElement(html) {
const div = document.createElement("div");
div.style.setProperty("display", "contents", "important");
div.innerHTML = html;
return div;
}
function createButtonHTML(iconName, title, text, type = "") {
return `
<button class="toolbar_button svelte-jngqwx" title="${title}" type="${type}">
<i class="icon">${iconName}</i>
<span class="svelte-jngqwx">${text}</span>
</button>
`;
}
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 modalElement = createElementWithListener(MODAL_HTML, "click", (event) => {
if (event.target.matches(".background") || event.target.closest(".close-button")) {
modalElement.remove();
}
});
document.body.insertAdjacentElement("afterbegin", modalElement);
}
function downloadFile(fileName, fileID, el) {
return new Promise((resolve, reject) => {
const url = `https://${selectedProxy}/${fileID}`;
GM_xmlhttpRequest({
url,
responseType: "blob",
onload(event) {
resolve(event);
if (event.status !== 200) {
showModal("Download error", "The server probably blocked this download.");
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 {
await downloadFile(file.name, file.id, el);
} 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."));
}
function handleSingleFile(separator, fileData) {
const { name, id } = fileData;
const downloadButtonHTML = createButtonHTML("download", "Bypass download", "Download bypass");
const copyButtonHTML = createButtonHTML("content_copy", "Copy bypass url", "Copy bypass");
const downloadButton = createElementWithListener(downloadButtonHTML, "click", () => downloadFile(name, id, downloadButton.firstElementChild));
const copyButton = createElementWithListener(copyButtonHTML, "click", () => copyBypassURL(id));
const sendToJDButton = createJDownloaderForm(fileData, "Add link to JDownloader", "Add link to JDownloader");
separator.insertAdjacentElement("afterend", downloadButton);
downloadButton.insertAdjacentElement("afterend", copyButton);
copyButton.insertAdjacentElement("afterend", sendToJDButton);
sendToJDButton.insertAdjacentElement("afterend", separator.cloneNode());
}
function handleFileList(separator, listData) {
const availableFiles = listData.files.filter((file) => file.availability === "");
const fileUrls = availableFiles
.map((file) => `<a class="file-name" target="_blank" rel="noopener noreferrer" title="${file.name}" href="https://${selectedProxy}/${file.id}">${file.name}</a>`)
.join("<br>");
const containerFileUrls = `<div class="indent container-names" style="display: flex; flex-direction: column;justify-content: center;align-items: center;">${fileUrls}</div>`;
const bypassUrls = availableFiles.map((file) => `https://${selectedProxy}/${file.id}`);
const containerBypassUrls = `<pre class="indent" style="padding-inline: .5rem; overflow: initial;">${bypassUrls.join("\n")}</pre>`;
const dlSelectedButtonHTML = createButtonHTML("download", "Bypass download selected file", "Download selected");
const dlAllButtonHTML = createButtonHTML("download", "Bypass download all files", "Download all");
const copyButtonHTML = createButtonHTML("content_copy", "Copy bypass url", "Copy bypass");
const showUrlsButtonHTML = createButtonHTML("link", "Show bypass URLs", "Show bypass");
const sendSelectedToJDButtonHTML = createButtonHTML("add_link", "Add link to JDownloader", "Add link to JDownloader", "submit");
const sendToJDButton = createJDownloaderForm(availableFiles, "Add links to JDownloader", "Add links to JDownloader");
const dlAllButton = createElementWithListener(dlAllButtonHTML, "click", () => massiveDownload(availableFiles, dlAllButton.firstElementChild));
const showUrlsButton = createElementWithListener(showUrlsButtonHTML, "click", () =>
showModal("Bypass URLs", containerFileUrls + containerBypassUrls, sendToJDButton.outerHTML),
);
const copyButton = createElementWithListener(copyButtonHTML, "click", () => {
const selectedFile = listData.files.find((file) => file.selected);
copyBypassURL(selectedFile.id);
});
const dlSelectedButton = createElementWithListener(dlSelectedButtonHTML, "click", () => {
const selectedFile = listData.files.find((file) => file.selected);
if (selectedFile.availability !== "") {
showModal(selectedFile.availability, selectedFile.availability_message);
return;
}
downloadFile(selectedFile.name, selectedFile.id, dlSelectedButton.firstElementChild);
});
const sendSelectedToJDButton = createElementWithListener(sendSelectedToJDButtonHTML, "click", () => {
const selectedFile = listData.files.find((file) => file.selected);
if (selectedFile.availability !== "") {
showModal(selectedFile.availability, selectedFile.availability_message);
return;
}
const containerForm = createJDownloaderForm(selectedFile);
containerForm.style.display = "none";
document.body.appendChild(containerForm);
containerForm.firstElementChild.submit();
containerForm.remove();
});
separator.insertAdjacentElement("afterend", dlSelectedButton);
dlSelectedButton.insertAdjacentElement("afterend", dlAllButton);
dlAllButton.insertAdjacentElement("afterend", copyButton);
copyButton.insertAdjacentElement("afterend", showUrlsButton);
showUrlsButton.insertAdjacentElement("afterend", sendSelectedToJDButton);
sendSelectedToJDButton.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["scrollbar-names"].state ? "Hide" : "Show"} horizontal scrollbar for long names`, () => {
GM_setValue("scrollbar-names", !PDLB_CONFIG.preferences["scrollbar-names"].state);
unsafeWindow.location.reload();
});
}
function createJDownloaderForm(files, title = "", text = "") {
const urlsString = Array.isArray(files) ? files.map((file) => `https://${selectedProxy}/api/file/${file.id}`).join("\r\n") : `https://${selectedProxy}/api/file/${files.id}`;
const addLinkHTML = createButtonHTML("add_link", title, text, "submit");
const formHTML = `
<form action="http://127.0.0.1:9666/flash/add" target="hidden" method="POST">
<input type="hidden" name="urls" value="${urlsString}">
${addLinkHTML}
</form>
`;
return wrapHTMLToElement(formHTML);
}
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"]) {
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;
}
.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();