diff --git a/package.json b/package.json index 645c475..ba39211 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "real-debrid-downloader", - "version": "1.5.82", + "version": "1.5.83", "description": "Real-Debrid Downloader Desktop (Electron + React + TypeScript)", "main": "build/main/main/main.js", "author": "Sucukdeluxe", diff --git a/src/main/debrid.ts b/src/main/debrid.ts index b3cbbc2..3bfc490 100644 --- a/src/main/debrid.ts +++ b/src/main/debrid.ts @@ -226,7 +226,9 @@ function isRapidgatorLink(link: string): boolean { return hostname === "rapidgator.net" || hostname.endsWith(".rapidgator.net") || hostname === "rg.to" - || hostname.endsWith(".rg.to"); + || hostname.endsWith(".rg.to") + || hostname === "rapidgator.asia" + || hostname.endsWith(".rapidgator.asia"); } catch { return false; } @@ -460,6 +462,89 @@ async function resolveRapidgatorFilename(link: string, signal?: AbortSignal): Pr return ""; } +export interface RapidgatorCheckResult { + online: boolean; + fileName: string; + fileSize: string | null; +} + +const RG_FILE_ID_RE = /\/file\/([a-z0-9]{32}|\d+)/i; +const RG_FILE_NOT_FOUND_RE = />\s*404\s*File not found/i; +const RG_FILESIZE_RE = /File\s*size:\s*([^<>"]+)<\/strong>/i; + +export async function checkRapidgatorOnline( + link: string, + signal?: AbortSignal +): Promise { + if (!isRapidgatorLink(link)) { + return null; + } + + const fileIdMatch = link.match(RG_FILE_ID_RE); + if (!fileIdMatch) { + return null; + } + const fileId = fileIdMatch[1]; + + for (let attempt = 1; attempt <= REQUEST_RETRIES + 1; attempt += 1) { + try { + if (signal?.aborted) throw new Error("aborted:debrid"); + + const response = await fetch(link, { + method: "GET", + redirect: "follow", + headers: { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36", + Accept: "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", + "Accept-Language": "en-US,en;q=0.9,de;q=0.8" + }, + signal: withTimeoutSignal(signal, API_TIMEOUT_MS) + }); + + if (response.status === 404) { + return { online: false, fileName: "", fileSize: null }; + } + + if (!response.ok) { + if (shouldRetryStatus(response.status) && attempt <= REQUEST_RETRIES) { + await sleepWithSignal(retryDelayForResponse(response, attempt), signal); + continue; + } + return null; + } + + const finalUrl = response.url || link; + if (!finalUrl.includes(fileId)) { + return { online: false, fileName: "", fileSize: null }; + } + + const html = await readResponseTextLimited(response, RAPIDGATOR_SCAN_MAX_BYTES, signal); + + if (RG_FILE_NOT_FOUND_RE.test(html)) { + return { online: false, fileName: "", fileSize: null }; + } + + const fileName = extractRapidgatorFilenameFromHtml(html) || filenameFromRapidgatorUrlPath(link); + const sizeMatch = html.match(RG_FILESIZE_RE); + const fileSize = sizeMatch ? sizeMatch[1].trim() : null; + + return { online: true, fileName, fileSize }; + } catch (error) { + const errorText = compactErrorText(error); + if (/aborted/i.test(errorText)) throw error; + if (attempt > REQUEST_RETRIES || !isRetryableErrorText(errorText)) { + return null; + } + } + + if (attempt <= REQUEST_RETRIES) { + await sleepWithSignal(retryDelay(attempt), signal); + } + } + + return null; +} + function buildBestDebridRequests(link: string, token: string): BestDebridRequest[] { const linkParam = encodeURIComponent(link); const safeToken = String(token || "").trim(); diff --git a/src/main/download-manager.ts b/src/main/download-manager.ts index 5862137..aad7715 100644 --- a/src/main/download-manager.ts +++ b/src/main/download-manager.ts @@ -20,7 +20,7 @@ import { } from "../shared/types"; import { REQUEST_RETRIES, SAMPLE_VIDEO_EXTENSIONS } from "./constants"; import { cleanupCancelledPackageArtifactsAsync } from "./cleanup"; -import { DebridService, MegaWebUnrestrictor } from "./debrid"; +import { DebridService, MegaWebUnrestrictor, checkRapidgatorOnline } from "./debrid"; import { collectArchiveCleanupTargets, extractPackageArchives, findArchiveCandidates } from "./extractor"; import { validateFileAgainstManifest } from "./integrity"; import { logger } from "./logger"; @@ -1190,6 +1190,7 @@ export class DownloadManager extends EventEmitter { let addedPackages = 0; let addedLinks = 0; const unresolvedByLink = new Map(); + const newItemIds: string[] = []; for (const pkg of packages) { const links = pkg.links.filter((link) => !!link.trim()); if (links.length === 0) { @@ -1250,6 +1251,7 @@ export class DownloadManager extends EventEmitter { existing.push(itemId); unresolvedByLink.set(link, existing); } + newItemIds.push(itemId); addedLinks += 1; } @@ -1263,6 +1265,9 @@ export class DownloadManager extends EventEmitter { if (unresolvedByLink.size > 0) { void this.resolveQueuedFilenames(unresolvedByLink).catch((err) => logger.warn(`resolveQueuedFilenames Fehler (addPackages): ${compactErrorText(err)}`)); } + if (newItemIds.length > 0) { + void this.checkRapidgatorLinks(newItemIds).catch((err) => logger.warn(`checkRapidgatorLinks Fehler: ${compactErrorText(err)}`)); + } return { addedPackages, addedLinks }; } @@ -1515,6 +1520,62 @@ export class DownloadManager extends EventEmitter { } } + private async checkRapidgatorLinks(itemIds: string[]): Promise { + const checked = new Map>>(); + const itemsToCheck: Array<{ itemId: string; url: string }> = []; + + for (const itemId of itemIds) { + const item = this.session.items[itemId]; + if (!item || item.status !== "queued") continue; + itemsToCheck.push({ itemId, url: item.url }); + } + + const uniqueUrls = [...new Set(itemsToCheck.map(i => i.url))]; + const concurrency = 4; + const queue = [...uniqueUrls]; + const workers = Array.from({ length: Math.min(concurrency, queue.length) }, async () => { + while (queue.length > 0) { + const url = queue.shift()!; + const result = await checkRapidgatorOnline(url); + if (result !== null) checked.set(url, result); + } + }); + await Promise.all(workers); + + if (checked.size === 0) return; + + let changed = false; + for (const { itemId, url } of itemsToCheck) { + const result = checked.get(url); + if (!result) continue; + const item = this.session.items[itemId]; + if (!item || item.status !== "queued") continue; + + if (!result.online) { + item.status = "failed"; + item.fullStatus = "Offline"; + item.lastError = "Datei nicht gefunden auf Rapidgator"; + item.updatedAt = nowMs(); + changed = true; + } else { + if (result.fileName && looksLikeOpaqueFilename(item.fileName)) { + item.fileName = sanitizeFilename(result.fileName); + this.assignItemTargetPath(item, path.join(this.session.packages[item.packageId]?.outputDir || this.settings.outputDir, item.fileName)); + item.updatedAt = nowMs(); + changed = true; + } + item.fullStatus = "Online"; + item.updatedAt = nowMs(); + changed = true; + } + } + + if (changed) { + this.persistSoon(); + this.emitState(); + } + } + private resolveExistingQueuedOpaqueFilenames(): void { const unresolvedByLink = new Map(); for (const item of Object.values(this.session.items)) {