Switch Mega web fallback to real debrideur form flow and bump to 1.1.20
Build and Release / build (push) Has been cancelled
Build and Release / build (push) Has been cancelled
This commit is contained in:
+172
-105
@@ -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;
|
||||
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<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');
|
||||
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 = "";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user