From e6ec1ed7556bb9944ce4599dba2c4991d553b780 Mon Sep 17 00:00:00 2001 From: Sucukdeluxe Date: Tue, 3 Mar 2026 14:06:19 +0100 Subject: [PATCH] Add Mega-Debrid account info check (web scraping) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Scrapes the Mega-Debrid profile page to display username, premium status, remaining days, and loyalty points. New "Account prüfen" button in Settings > Accounts. Co-Authored-By: Claude Opus 4.6 --- package.json | 2 +- src/main/app-controller.ts | 5 ++++ src/main/main.ts | 7 ++++++ src/main/mega-web-fallback.ts | 47 +++++++++++++++++++++++++++++++++++ src/preload/preload.ts | 2 ++ src/renderer/App.tsx | 20 +++++++++++++++ src/renderer/styles.css | 18 ++++++++++++++ src/shared/ipc.ts | 3 ++- src/shared/preload-api.ts | 2 ++ src/shared/types.ts | 9 +++++++ 10 files changed, 113 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 66c84e1..c100ab2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "real-debrid-downloader", - "version": "1.5.50", + "version": "1.5.51", "description": "Real-Debrid Downloader Desktop (Electron + React + TypeScript)", "main": "build/main/main/main.js", "author": "Sucukdeluxe", diff --git a/src/main/app-controller.ts b/src/main/app-controller.ts index 4770edb..1689733 100644 --- a/src/main/app-controller.ts +++ b/src/main/app-controller.ts @@ -7,6 +7,7 @@ import { DuplicatePolicy, HistoryEntry, ParsedPackageInput, + ProviderAccountInfo, SessionStats, StartConflictEntry, StartConflictResolutionResult, @@ -327,6 +328,10 @@ export class AppController { removeHistoryEntry(this.storagePaths, entryId); } + public async checkMegaAccount(): Promise { + return this.megaWebFallback.getAccountInfo(); + } + public addToHistory(entry: HistoryEntry): void { addHistoryEntry(this.storagePaths, entry); } diff --git a/src/main/main.ts b/src/main/main.ts index ea2a021..e516bc8 100644 --- a/src/main/main.ts +++ b/src/main/main.ts @@ -384,6 +384,13 @@ function registerIpcHandlers(): void { const result = mainWindow ? await dialog.showOpenDialog(mainWindow, options) : await dialog.showOpenDialog(options); return result.canceled ? [] : result.filePaths; }); + ipcMain.handle(IPC_CHANNELS.CHECK_ACCOUNT, async (_event: IpcMainInvokeEvent, provider: string) => { + validateString(provider, "provider"); + if (provider === "megadebrid") { + return controller.checkMegaAccount(); + } + return { provider, username: "", accountType: "", daysRemaining: null, loyaltyPoints: null, error: "Nicht unterstützt" }; + }); ipcMain.handle(IPC_CHANNELS.GET_SESSION_STATS, () => controller.getSessionStats()); ipcMain.handle(IPC_CHANNELS.RESTART, () => { diff --git a/src/main/mega-web-fallback.ts b/src/main/mega-web-fallback.ts index f9c1b04..8df9778 100644 --- a/src/main/mega-web-fallback.ts +++ b/src/main/mega-web-fallback.ts @@ -1,3 +1,4 @@ +import { ProviderAccountInfo } from "../shared/types"; import { UnrestrictedLink } from "./realdebrid"; import { compactErrorText, filenameFromUrl, sleep } from "./utils"; @@ -15,6 +16,7 @@ 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"; +const PROFILE_URL = "https://www.mega-debrid.eu/index.php?page=profil"; function normalizeLink(link: string): string { return link.trim().toLowerCase(); @@ -264,6 +266,51 @@ export class MegaWebFallback { }, signal); } + public async getAccountInfo(): Promise { + return this.runExclusive(async () => { + const creds = this.getCredentials(); + if (!creds.login.trim() || !creds.password.trim()) { + return { provider: "megadebrid", username: "", accountType: "", daysRemaining: null, loyaltyPoints: null, error: "Login/Passwort nicht konfiguriert" }; + } + + try { + if (!this.cookie || Date.now() - this.cookieSetAt > 20 * 60 * 1000) { + await this.login(creds.login, creds.password); + } + + const res = await fetch(PROFILE_URL, { + method: "GET", + headers: { + "User-Agent": "Mozilla/5.0", + Cookie: this.cookie, + Referer: DEBRID_REFERER + }, + signal: AbortSignal.timeout(30000) + }); + const html = await res.text(); + + const usernameMatch = html.match(/]*id=["']user_link["'][^>]*>([^<]+)<\/span>/i); + const username = usernameMatch?.[1]?.trim() || ""; + + const typeMatch = html.match(/(Premiumuser|Freeuser)\s*-\s*(\d+)\s*Tag/i); + const accountType = typeMatch?.[1] || "Unbekannt"; + const daysRemaining = typeMatch?.[2] ? parseInt(typeMatch[2], 10) : null; + + const pointsMatch = html.match(/(\d+)\s*Treuepunkte/i); + const loyaltyPoints = pointsMatch?.[1] ? parseInt(pointsMatch[1], 10) : null; + + if (!username && !typeMatch) { + this.cookie = ""; + return { provider: "megadebrid", username: "", accountType: "", daysRemaining: null, loyaltyPoints: null, error: "Profil konnte nicht gelesen werden (Session ungültig?)" }; + } + + return { provider: "megadebrid", username, accountType, daysRemaining, loyaltyPoints }; + } catch (err) { + return { provider: "megadebrid", username: "", accountType: "", daysRemaining: null, loyaltyPoints: null, error: compactErrorText(err) }; + } + }); + } + public invalidateSession(): void { this.cookie = ""; this.cookieSetAt = 0; diff --git a/src/preload/preload.ts b/src/preload/preload.ts index 120a325..751339b 100644 --- a/src/preload/preload.ts +++ b/src/preload/preload.ts @@ -4,6 +4,7 @@ import { AppSettings, DuplicatePolicy, HistoryEntry, + ProviderAccountInfo, SessionStats, StartConflictEntry, StartConflictResolutionResult, @@ -53,6 +54,7 @@ const api: ElectronApi = { getHistory: (): Promise => ipcRenderer.invoke(IPC_CHANNELS.GET_HISTORY), clearHistory: (): Promise => ipcRenderer.invoke(IPC_CHANNELS.CLEAR_HISTORY), removeHistoryEntry: (entryId: string): Promise => ipcRenderer.invoke(IPC_CHANNELS.REMOVE_HISTORY_ENTRY, entryId), + checkAccount: (provider: string): Promise => ipcRenderer.invoke(IPC_CHANNELS.CHECK_ACCOUNT, provider), onStateUpdate: (callback: (snapshot: UiSnapshot) => void): (() => void) => { const listener = (_event: unknown, snapshot: UiSnapshot): void => callback(snapshot); ipcRenderer.on(IPC_CHANNELS.STATE_UPDATE, listener); diff --git a/src/renderer/App.tsx b/src/renderer/App.tsx index c07ce19..3320ab7 100644 --- a/src/renderer/App.tsx +++ b/src/renderer/App.tsx @@ -10,6 +10,7 @@ import type { DuplicatePolicy, HistoryEntry, PackageEntry, + ProviderAccountInfo, StartConflictEntry, UiSnapshot, UpdateCheckResult, @@ -442,6 +443,8 @@ export function App(): ReactElement { const latestStateRef = useRef(null); const stateFlushTimerRef = useRef | null>(null); const toastTimerRef = useRef | null>(null); + const [megaAccountInfo, setMegaAccountInfo] = useState(null); + const [megaAccountLoading, setMegaAccountLoading] = useState(false); const [dragOver, setDragOver] = useState(false); const [editingPackageId, setEditingPackageId] = useState(null); const [editingName, setEditingName] = useState(""); @@ -2390,6 +2393,23 @@ export function App(): ReactElement { setText("megaLogin", e.target.value)} /> setText("megaPassword", e.target.value)} /> + + {megaAccountInfo && !megaAccountInfo.error && ( +
✓ {megaAccountInfo.username} — {megaAccountInfo.accountType}{megaAccountInfo.daysRemaining !== null ? ` — ${megaAccountInfo.daysRemaining} Tage` : ""}{megaAccountInfo.loyaltyPoints !== null ? ` — ${megaAccountInfo.loyaltyPoints} Treuepunkte` : ""}
+ )} + {megaAccountInfo?.error && ( +
✗ {megaAccountInfo.error}
+ )} setText("bestToken", e.target.value)} /> diff --git a/src/renderer/styles.css b/src/renderer/styles.css index 7359290..ed26aec 100644 --- a/src/renderer/styles.css +++ b/src/renderer/styles.css @@ -1465,6 +1465,24 @@ td { } } +.account-info { + font-size: 13px; + padding: 6px 10px; + border-radius: 8px; + border: 1px solid var(--border); + background: var(--field); +} + +.account-info-success { + color: #4ade80; + border-color: rgba(74, 222, 128, 0.35); +} + +.account-info-error { + color: var(--danger); + border-color: rgba(244, 63, 94, 0.35); +} + .schedule-row { display: grid; grid-template-columns: 56px auto 56px auto 92px auto auto auto; diff --git a/src/shared/ipc.ts b/src/shared/ipc.ts index 5fe7792..f22b202 100644 --- a/src/shared/ipc.ts +++ b/src/shared/ipc.ts @@ -36,5 +36,6 @@ export const IPC_CHANNELS = { EXTRACT_NOW: "queue:extract-now", GET_HISTORY: "history:get", CLEAR_HISTORY: "history:clear", - REMOVE_HISTORY_ENTRY: "history:remove-entry" + REMOVE_HISTORY_ENTRY: "history:remove-entry", + CHECK_ACCOUNT: "app:check-account" } as const; diff --git a/src/shared/preload-api.ts b/src/shared/preload-api.ts index 665c914..83241ba 100644 --- a/src/shared/preload-api.ts +++ b/src/shared/preload-api.ts @@ -3,6 +3,7 @@ import type { AppSettings, DuplicatePolicy, HistoryEntry, + ProviderAccountInfo, SessionStats, StartConflictEntry, StartConflictResolutionResult, @@ -48,6 +49,7 @@ export interface ElectronApi { getHistory: () => Promise; clearHistory: () => Promise; removeHistoryEntry: (entryId: string) => Promise; + checkAccount: (provider: string) => Promise; onStateUpdate: (callback: (snapshot: UiSnapshot) => void) => () => void; onClipboardDetected: (callback: (links: string[]) => void) => () => void; onUpdateInstallProgress: (callback: (progress: UpdateInstallProgress) => void) => () => void; diff --git a/src/shared/types.ts b/src/shared/types.ts index 9093cdc..8e776aa 100644 --- a/src/shared/types.ts +++ b/src/shared/types.ts @@ -269,6 +269,15 @@ export interface HistoryEntry { outputDir: string; } +export interface ProviderAccountInfo { + provider: DebridProvider; + username: string; + accountType: string; + daysRemaining: number | null; + loyaltyPoints: number | null; + error?: string; +} + export interface HistoryState { entries: HistoryEntry[]; maxEntries: number;