diff --git a/package-lock.json b/package-lock.json index 1ea49bd..7de7024 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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", diff --git a/package.json b/package.json index 7e08803..314bf43 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/scripts/mega_web_generate_download_test.mjs b/scripts/mega_web_generate_download_test.mjs new file mode 100644 index 0000000..0066daa --- /dev/null +++ b/scripts/mega_web_generate_download_test.mjs @@ -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(); diff --git a/src/main/constants.ts b/src/main/constants.ts index d8fada8..bc07863 100644 --- a/src/main/constants.ts +++ b/src/main/constants.ts @@ -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"; diff --git a/src/main/mega-web-fallback.ts b/src/main/mega-web-fallback.ts index 9b5fed5..2790bee 100644 --- a/src/main/mega-web-fallback.ts +++ b/src/main/mega-web-fallback.ts @@ -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 = /]*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(/

\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 = 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 { - 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 { + 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 { - 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; + 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" + }) + }); + + const html = await page.text(); + const code = pickCode(parseCodes(html), link); + if (!code) { + return null; } - 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 { - 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'); + 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" + }) }); - 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 text = (await res.text()).trim(); + if (text === "reload") { + await sleep(650); + continue; + } + if (text === "false") { + 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 }; - } + 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 = ""; } }