Switch Mega web fallback to real debrideur form flow and bump to 1.1.20
Some checks are pending
Build and Release / build (push) Waiting to run

This commit is contained in:
Sucukdeluxe 2026-02-27 05:47:19 +01:00
parent 0e898733d6
commit b1b8ed4180
5 changed files with 315 additions and 109 deletions

4
package-lock.json generated
View File

@ -1,12 +1,12 @@
{
"name": "real-debrid-downloader",
"version": "1.1.19",
"version": "1.1.20",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "real-debrid-downloader",
"version": "1.1.19",
"version": "1.1.20",
"license": "MIT",
"dependencies": {
"adm-zip": "^0.5.16",

View File

@ -1,6 +1,6 @@
{
"name": "real-debrid-downloader",
"version": "1.1.19",
"version": "1.1.20",
"description": "Real-Debrid Downloader Desktop (Electron + React + TypeScript)",
"main": "build/main/main/main.js",
"author": "Sucukdeluxe",

View File

@ -0,0 +1,139 @@
const LOGIN = process.env.MEGA_LOGIN || "";
const PASSWORD = process.env.MEGA_PASSWORD || "";
const LINKS = [
"https://rapidgator.net/file/90b5397dfc3e1a0e561db7d6b89d5604/scnb-rrw7-S08E01.part1.rar.html",
"https://rapidgator.net/file/8ddf856dc833310c5cae9db82caf9682/scnb-rrw7-S08E01.part2.rar.html",
"https://rapidgator.net/file/440eed67d266476866332ae224c3fad5/scnb-rrw7-S08E01.part3.rar.html"
];
if (!LOGIN || !PASSWORD) {
throw new Error("Set MEGA_LOGIN and MEGA_PASSWORD env vars");
}
function sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
function cookieFrom(headers) {
const raw = headers.get("set-cookie") || "";
return raw.split(",").map((x) => x.split(";")[0].trim()).filter(Boolean).join("; ");
}
function parseDebridCodes(html) {
const re = /processDebrid\((\d+),'([^']+)',0\)/g;
const out = [];
let m;
while ((m = re.exec(html)) !== null) {
out.push({ id: Number(m[1]), code: m[2] });
}
return out;
}
async function resolveCode(cookie, code) {
for (let attempt = 1; attempt <= 50; attempt += 1) {
const res = await fetch("https://www.mega-debrid.eu/index.php?ajax=debrid&json", {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
"User-Agent": "Mozilla/5.0",
Cookie: cookie,
Referer: "https://www.mega-debrid.eu/index.php?page=debrideur&lang=de"
},
body: new URLSearchParams({
code,
autodl: "0"
})
});
const text = (await res.text()).trim();
if (text === "reload") {
await sleep(800);
continue;
}
if (text === "false") {
return { ok: false, reason: "false" };
}
try {
const parsed = JSON.parse(text);
if (parsed?.link) {
return { ok: true, link: String(parsed.link), text: String(parsed.text || "") };
}
return { ok: false, reason: text };
} catch {
return { ok: false, reason: text };
}
}
return { ok: false, reason: "timeout" };
}
async function probeDownload(url) {
const res = await fetch(url, {
method: "GET",
headers: {
Range: "bytes=0-4095",
"User-Agent": "Mozilla/5.0"
},
redirect: "manual"
});
return {
status: res.status,
location: res.headers.get("location") || "",
contentType: res.headers.get("content-type") || "",
contentLength: res.headers.get("content-length") || ""
};
}
async function main() {
const loginRes = await fetch("https://www.mega-debrid.eu/index.php?form=login", {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
"User-Agent": "Mozilla/5.0"
},
body: new URLSearchParams({
login: LOGIN,
password: PASSWORD,
remember: "on"
}),
redirect: "manual"
});
const cookie = cookieFrom(loginRes.headers);
console.log("login", loginRes.status, loginRes.headers.get("location") || "");
const debridRes = await fetch("https://www.mega-debrid.eu/index.php?form=debrid", {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
"User-Agent": "Mozilla/5.0",
Cookie: cookie,
Referer: "https://www.mega-debrid.eu/index.php?page=debrideur&lang=de"
},
body: new URLSearchParams({
links: LINKS.join("\n"),
password: "",
showLinks: "1"
})
});
const html = await debridRes.text();
const codes = parseDebridCodes(html);
console.log("codes", codes.length);
if (codes.length === 0) {
throw new Error("No processDebrid codes found");
}
for (let i = 0; i < Math.min(3, codes.length); i += 1) {
const c = codes[i];
const resolved = await resolveCode(cookie, c.code);
if (!resolved.ok) {
console.log(`[FAIL] code ${c.code}: ${resolved.reason}`);
continue;
}
console.log(`[OK] code ${c.code} -> ${resolved.link}`);
const probe = await probeDownload(resolved.link);
console.log(` probe status=${probe.status} type=${probe.contentType} len=${probe.contentLength} loc=${probe.location}`);
}
}
await main();

View File

@ -3,7 +3,7 @@ import os from "node:os";
import { AppSettings } from "../shared/types";
export const APP_NAME = "Debrid Download Manager";
export const APP_VERSION = "1.1.19";
export const APP_VERSION = "1.1.20";
export const API_BASE_URL = "https://api.real-debrid.com/rest/1.0";
export const DCRYPT_UPLOAD_URL = "https://dcrypt.it/decrypt/upload";

View File

@ -1,4 +1,3 @@
import { BrowserWindow } from "electron";
import { UnrestrictedLink } from "./realdebrid";
import { compactErrorText, filenameFromUrl, sleep } from "./utils";
@ -7,18 +6,83 @@ type MegaCredentials = {
password: string;
};
type MegaWebResult = {
directUrl: string;
fileName: string;
type CodeEntry = {
code: string;
linkHint: string;
};
export class MegaWebFallback {
private browser: BrowserWindow | null = null;
const LOGIN_URL = "https://www.mega-debrid.eu/index.php?form=login";
const DEBRID_URL = "https://www.mega-debrid.eu/index.php?form=debrid";
const DEBRID_AJAX_URL = "https://www.mega-debrid.eu/index.php?ajax=debrid&json";
const DEBRID_REFERER = "https://www.mega-debrid.eu/index.php?page=debrideur&lang=de";
function normalizeLink(link: string): string {
return link.trim().toLowerCase();
}
function parseSetCookie(raw: string): string {
return raw
.split(",")
.map((chunk) => chunk.split(";")[0].trim())
.filter(Boolean)
.join("; ");
}
function parseCodes(html: string): CodeEntry[] {
const entries: CodeEntry[] = [];
const cardRegex = /<div[^>]*class=['"][^'"]*acp-box[^'"]*['"][^>]*>[\s\S]*?<\/div>/gi;
let cardMatch: RegExpExecArray | null;
while ((cardMatch = cardRegex.exec(html)) !== null) {
const block = cardMatch[0];
const linkTitle = (block.match(/<h3>\s*Link:\s*([^<]+)<\/h3>/i)?.[1] || "").trim();
const code = block.match(/processDebrid\(\d+,'([^']+)',0\)/i)?.[1] || "";
if (!code) {
continue;
}
entries.push({ code, linkHint: normalizeLink(linkTitle) });
}
if (entries.length === 0) {
const fallbackRegex = /processDebrid\(\d+,'([^']+)',0\)/gi;
let m: RegExpExecArray | null;
while ((m = fallbackRegex.exec(html)) !== null) {
entries.push({ code: m[1], linkHint: "" });
}
}
return entries;
}
function pickCode(entries: CodeEntry[], link: string): string {
if (entries.length === 0) {
return "";
}
const target = normalizeLink(link);
const match = entries.find((entry) => entry.linkHint && entry.linkHint.includes(target));
return (match?.code || entries[0].code || "").trim();
}
function parseDebridJson(text: string): { link: string; text: string } | null {
try {
const parsed = JSON.parse(text) as { link?: string; text?: string };
return {
link: String(parsed.link || ""),
text: String(parsed.text || "")
};
} catch {
return null;
}
}
export class MegaWebFallback {
private queue: Promise<unknown> = Promise.resolve();
private getCredentials: () => MegaCredentials;
private cookie = "";
private cookieSetAt = 0;
public constructor(getCredentials: () => MegaCredentials) {
this.getCredentials = getCredentials;
}
@ -30,20 +94,29 @@ export class MegaWebFallback {
return null;
}
const browser = await this.ensureBrowser();
const authOk = await this.login(browser, creds.login, creds.password);
if (!authOk) {
throw new Error("Mega-Web-Login fehlgeschlagen");
if (!this.cookie || Date.now() - this.cookieSetAt > 20 * 60 * 1000) {
await this.login(creds.login, creds.password);
}
const data = await this.generateLink(browser, link);
if (!data?.directUrl) {
throw new Error("Mega-Web konnte keinen Downloadlink erzeugen");
const generated = await this.generate(link);
if (!generated) {
this.cookie = "";
await this.login(creds.login, creds.password);
const retry = await this.generate(link);
if (!retry) {
return null;
}
return {
directUrl: retry.directUrl,
fileName: retry.fileName || filenameFromUrl(link),
fileSize: null,
retriesUsed: 0
};
}
return {
directUrl: data.directUrl,
fileName: data.fileName || filenameFromUrl(link),
directUrl: generated.directUrl,
fileName: generated.fileName || filenameFromUrl(link),
fileSize: null,
retriesUsed: 0
};
@ -56,112 +129,106 @@ export class MegaWebFallback {
return run;
}
private async ensureBrowser(): Promise<BrowserWindow> {
if (this.browser && !this.browser.isDestroyed()) {
return this.browser;
}
this.browser = new BrowserWindow({
show: false,
webPreferences: {
sandbox: true,
contextIsolation: true,
nodeIntegration: false,
partition: "persist:mega-web"
}
private async login(login: string, password: string): Promise<void> {
const response = await fetch(LOGIN_URL, {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
"User-Agent": "Mozilla/5.0"
},
body: new URLSearchParams({
login,
password,
remember: "on"
}),
redirect: "manual"
});
return this.browser;
const cookie = parseSetCookie(response.headers.get("set-cookie") || "");
if (!cookie) {
throw new Error("Mega-Web Login liefert kein Session-Cookie");
}
this.cookie = cookie;
this.cookieSetAt = Date.now();
}
private async login(browser: BrowserWindow, login: string, password: string): Promise<boolean> {
await browser.loadURL("https://www.mega-debrid.eu/index.php?page=login&lang=en");
await sleep(600);
const result = await browser.webContents.executeJavaScript(`(async () => {
const hasLogout = Boolean(document.querySelector('a[href*="logout"], a[href*="debrideur"], a[href*="debrid"]'));
if (hasLogout) return { ok: true };
const form = document.querySelector('#formulaire_login') || document.querySelector('form[action*="form=login"]') || document.querySelector('form');
if (!form) return { ok: false, reason: 'Login-Form nicht gefunden' };
const loginInput = form.querySelector('input[name="login"], #user_login');
const passInput = form.querySelector('input[name="password"], #user_password');
if (!loginInput || !passInput) return { ok: false, reason: 'Login-Felder fehlen' };
loginInput.value = ${JSON.stringify(login)};
passInput.value = ${JSON.stringify(password)};
const submit = form.querySelector('button[type="submit"], input[type="submit"], #user_submit');
if (submit) { submit.click(); } else { form.submit(); }
return { ok: true };
})();`, true);
if (!result?.ok) {
return false;
}
for (let i = 0; i < 30; i += 1) {
await sleep(350);
const url = browser.webContents.getURL();
if (url.includes("page=debrideur") || url.includes("page=debrid")) {
return true;
}
const logged = await browser.webContents.executeJavaScript(
"Boolean(document.querySelector('a[href*=\"debrideur\"], a[href*=\"debrid\"], a[href*=\"logout\"]'))",
true
).catch(() => false);
if (logged) {
return true;
}
}
return false;
}
private async generateLink(browser: BrowserWindow, link: string): Promise<MegaWebResult | null> {
await browser.loadURL("https://www.mega-debrid.eu/index.php?page=debrideur&lang=de");
await sleep(800);
const start = await browser.webContents.executeJavaScript(`(async () => {
const textarea = document.querySelector('textarea');
if (!textarea) return { ok: false, reason: 'Textarea fehlt' };
textarea.value = ${JSON.stringify(link)};
textarea.dispatchEvent(new Event('input', { bubbles: true }));
const controls = Array.from(document.querySelectorAll('button, input[type="submit"], a.btn'));
const trigger = controls.find((el) => {
const text = (el.textContent || el.value || '').toLowerCase();
return text.includes('erzeugen') || text.includes('generate') || text.includes('générer') || text.includes('downloadlink');
private async generate(link: string): Promise<{ directUrl: string; fileName: string } | null> {
const page = await fetch(DEBRID_URL, {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
"User-Agent": "Mozilla/5.0",
Cookie: this.cookie,
Referer: DEBRID_REFERER
},
body: new URLSearchParams({
links: link,
password: "",
showLinks: "1"
})
});
if (!trigger) return { ok: false, reason: 'Generate-Button fehlt' };
trigger.click();
return { ok: true };
})();`, true);
if (!start?.ok) {
throw new Error(start?.reason || "Mega-Web konnte Request nicht starten");
const html = await page.text();
const code = pickCode(parseCodes(html), link);
if (!code) {
return null;
}
const linkHash = link.toLowerCase();
for (let i = 0; i < 80; i += 1) {
await sleep(500);
const result = await browser.webContents.executeJavaScript(`(() => {
const cards = Array.from(document.querySelectorAll('.acp-box.card, .acp-box, .card'));
for (const card of cards) {
const title = (card.querySelector('.title')?.textContent || '').trim();
const href = card.querySelector('a[href*="unrestrict.link/download/file/"]')?.getAttribute('href') || '';
const fileName = (card.querySelector('.filename')?.textContent || '').trim();
if (!href) continue;
const lowTitle = title.toLowerCase();
if (lowTitle.includes(${JSON.stringify(linkHash)}) || lowTitle.includes(${JSON.stringify(link.toLowerCase())}) || !title) {
return { directUrl: href, fileName };
for (let attempt = 1; attempt <= 60; attempt += 1) {
const res = await fetch(DEBRID_AJAX_URL, {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
"User-Agent": "Mozilla/5.0",
Cookie: this.cookie,
Referer: DEBRID_REFERER
},
body: new URLSearchParams({
code,
autodl: "0"
})
});
const text = (await res.text()).trim();
if (text === "reload") {
await sleep(650);
continue;
}
if (text === "false") {
return null;
}
const parsed = parseDebridJson(text);
if (!parsed) {
return null;
}
if (!parsed.link) {
if (/hoster does not respond correctly|could not be done for this moment/i.test(parsed.text || "")) {
await sleep(1200);
continue;
}
return null;
})();`, true).catch(() => null);
if (result?.directUrl) {
return result;
}
const fromText = parsed.text
.replace(/<[^>]*>/g, " ")
.replace(/\s+/g, " ")
.trim();
const nameMatch = fromText.match(/([\w .\-\[\]\(\)]+\.(?:rar|r\d{2}|zip|7z|mkv|mp4|avi|mp3|flac))/i);
const fileName = (nameMatch?.[1] || filenameFromUrl(link)).trim();
return {
directUrl: parsed.link,
fileName
};
}
return null;
}
public dispose(): void {
if (this.browser && !this.browser.isDestroyed()) {
this.browser.destroy();
}
this.browser = null;
this.cookie = "";
}
}