Release v1.5.85
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
5574b50d20
commit
15d0969cd9
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "real-debrid-downloader",
|
"name": "real-debrid-downloader",
|
||||||
"version": "1.5.84",
|
"version": "1.5.85",
|
||||||
"description": "Real-Debrid Downloader Desktop (Electron + React + TypeScript)",
|
"description": "Real-Debrid Downloader Desktop (Electron + React + TypeScript)",
|
||||||
"main": "build/main/main/main.js",
|
"main": "build/main/main/main.js",
|
||||||
"author": "Sucukdeluxe",
|
"author": "Sucukdeluxe",
|
||||||
|
|||||||
@ -485,7 +485,57 @@ export async function checkRapidgatorOnline(
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const fileId = fileIdMatch[1];
|
const fileId = fileIdMatch[1];
|
||||||
|
const 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"
|
||||||
|
};
|
||||||
|
|
||||||
|
// Fast path: HEAD request (no body download, much faster)
|
||||||
|
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: "HEAD",
|
||||||
|
redirect: "follow",
|
||||||
|
headers,
|
||||||
|
signal: withTimeoutSignal(signal, 15000)
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.status === 404) {
|
||||||
|
return { online: false, fileName: "", fileSize: null };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
const finalUrl = response.url || link;
|
||||||
|
if (!finalUrl.includes(fileId)) {
|
||||||
|
return { online: false, fileName: "", fileSize: null };
|
||||||
|
}
|
||||||
|
// HEAD 200 + URL still contains file ID → online
|
||||||
|
const fileName = filenameFromRapidgatorUrlPath(link);
|
||||||
|
return { online: true, fileName, fileSize: null };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Non-OK, non-404: retry or give up
|
||||||
|
if (shouldRetryStatus(response.status) && attempt <= REQUEST_RETRIES) {
|
||||||
|
await sleepWithSignal(retryDelayForResponse(response, attempt), signal);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// HEAD inconclusive — fall through to GET
|
||||||
|
break;
|
||||||
|
} catch (error) {
|
||||||
|
const errorText = compactErrorText(error);
|
||||||
|
if (/aborted/i.test(errorText)) throw error;
|
||||||
|
if (attempt > REQUEST_RETRIES || !isRetryableErrorText(errorText)) {
|
||||||
|
break; // fall through to GET
|
||||||
|
}
|
||||||
|
await sleepWithSignal(retryDelay(attempt), signal);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Slow path: GET request (downloads HTML, more thorough)
|
||||||
for (let attempt = 1; attempt <= REQUEST_RETRIES + 1; attempt += 1) {
|
for (let attempt = 1; attempt <= REQUEST_RETRIES + 1; attempt += 1) {
|
||||||
try {
|
try {
|
||||||
if (signal?.aborted) throw new Error("aborted:debrid");
|
if (signal?.aborted) throw new Error("aborted:debrid");
|
||||||
@ -493,11 +543,7 @@ export async function checkRapidgatorOnline(
|
|||||||
const response = await fetch(link, {
|
const response = await fetch(link, {
|
||||||
method: "GET",
|
method: "GET",
|
||||||
redirect: "follow",
|
redirect: "follow",
|
||||||
headers: {
|
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)
|
signal: withTimeoutSignal(signal, API_TIMEOUT_MS)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -838,6 +838,7 @@ export class DownloadManager extends EventEmitter {
|
|||||||
void this.recoverRetryableItems("startup").catch((err) => logger.warn(`recoverRetryableItems Fehler (startup): ${compactErrorText(err)}`));
|
void this.recoverRetryableItems("startup").catch((err) => logger.warn(`recoverRetryableItems Fehler (startup): ${compactErrorText(err)}`));
|
||||||
this.recoverPostProcessingOnStartup();
|
this.recoverPostProcessingOnStartup();
|
||||||
this.resolveExistingQueuedOpaqueFilenames();
|
this.resolveExistingQueuedOpaqueFilenames();
|
||||||
|
this.checkExistingRapidgatorLinks();
|
||||||
void this.cleanupExistingExtractedArchives().catch((err) => logger.warn(`cleanupExistingExtractedArchives Fehler (constructor): ${compactErrorText(err)}`));
|
void this.cleanupExistingExtractedArchives().catch((err) => logger.warn(`cleanupExistingExtractedArchives Fehler (constructor): ${compactErrorText(err)}`));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1534,49 +1535,28 @@ export class DownloadManager extends EventEmitter {
|
|||||||
this.emitState();
|
this.emitState();
|
||||||
}
|
}
|
||||||
|
|
||||||
const uniqueUrls = [...new Set(itemsToCheck.map(i => i.url))];
|
// Check links one by one (sequentially) so the user sees dots change progressively
|
||||||
const concurrency = 4;
|
const checkedUrls = new Map<string, Awaited<ReturnType<typeof checkRapidgatorOnline>>>();
|
||||||
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) {
|
for (const { itemId, url } of itemsToCheck) {
|
||||||
const result = checked.get(url);
|
|
||||||
if (!result) continue;
|
|
||||||
const item = this.session.items[itemId];
|
const item = this.session.items[itemId];
|
||||||
if (!item || item.status !== "queued") continue;
|
if (!item) continue;
|
||||||
|
|
||||||
if (!result.online) {
|
// Reuse result if same URL was already checked
|
||||||
item.status = "failed";
|
if (checkedUrls.has(url)) {
|
||||||
item.fullStatus = "Offline";
|
const cached = checkedUrls.get(url);
|
||||||
item.lastError = "Datei nicht gefunden auf Rapidgator";
|
this.applyRapidgatorCheckResult(item, cached);
|
||||||
item.onlineStatus = "offline";
|
this.emitState();
|
||||||
item.updatedAt = nowMs();
|
continue;
|
||||||
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.onlineStatus = "online";
|
|
||||||
item.updatedAt = nowMs();
|
|
||||||
changed = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (changed) {
|
const result = await checkRapidgatorOnline(url);
|
||||||
this.persistSoon();
|
checkedUrls.set(url, result);
|
||||||
|
this.applyRapidgatorCheckResult(item, result);
|
||||||
this.emitState();
|
this.emitState();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.persistSoon();
|
||||||
}
|
}
|
||||||
|
|
||||||
private resolveExistingQueuedOpaqueFilenames(): void {
|
private resolveExistingQueuedOpaqueFilenames(): void {
|
||||||
@ -1602,6 +1582,43 @@ export class DownloadManager extends EventEmitter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private applyRapidgatorCheckResult(item: DownloadItem, result: Awaited<ReturnType<typeof checkRapidgatorOnline>>): void {
|
||||||
|
if (!result) {
|
||||||
|
if (item.onlineStatus === "checking") {
|
||||||
|
item.onlineStatus = undefined;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (item.status !== "queued") return;
|
||||||
|
|
||||||
|
if (!result.online) {
|
||||||
|
item.status = "failed";
|
||||||
|
item.fullStatus = "Offline";
|
||||||
|
item.lastError = "Datei nicht gefunden auf Rapidgator";
|
||||||
|
item.onlineStatus = "offline";
|
||||||
|
item.updatedAt = nowMs();
|
||||||
|
} 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.onlineStatus = "online";
|
||||||
|
item.updatedAt = nowMs();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private checkExistingRapidgatorLinks(): void {
|
||||||
|
const uncheckedIds: string[] = [];
|
||||||
|
for (const item of Object.values(this.session.items)) {
|
||||||
|
if (item.status !== "queued") continue;
|
||||||
|
if (item.onlineStatus) continue; // already checked
|
||||||
|
uncheckedIds.push(item.id);
|
||||||
|
}
|
||||||
|
if (uncheckedIds.length > 0) {
|
||||||
|
void this.checkRapidgatorLinks(uncheckedIds).catch((err) => logger.warn(`checkRapidgatorLinks Fehler (startup): ${compactErrorText(err)}`));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private async cleanupExistingExtractedArchives(): Promise<void> {
|
private async cleanupExistingExtractedArchives(): Promise<void> {
|
||||||
if (this.settings.cleanupMode === "none") {
|
if (this.settings.cleanupMode === "none") {
|
||||||
return;
|
return;
|
||||||
@ -2803,10 +2820,14 @@ export class DownloadManager extends EventEmitter {
|
|||||||
// Clear stale transient status texts from previous session
|
// Clear stale transient status texts from previous session
|
||||||
if (item.status === "queued") {
|
if (item.status === "queued") {
|
||||||
const fs = (item.fullStatus || "").trim();
|
const fs = (item.fullStatus || "").trim();
|
||||||
if (fs !== "Wartet" && fs !== "Paket gestoppt") {
|
if (fs !== "Wartet" && fs !== "Paket gestoppt" && fs !== "Online") {
|
||||||
item.fullStatus = "Wartet";
|
item.fullStatus = "Wartet";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Reset stale "checking" status from interrupted checks
|
||||||
|
if (item.onlineStatus === "checking") {
|
||||||
|
item.onlineStatus = undefined;
|
||||||
|
}
|
||||||
if (item.status === "completed") {
|
if (item.status === "completed") {
|
||||||
const fs = (item.fullStatus || "").trim();
|
const fs = (item.fullStatus || "").trim();
|
||||||
if (fs && !isExtractedLabel(fs) && !/^Fertig\b/i.test(fs)) {
|
if (fs && !isExtractedLabel(fs) && !/^Fertig\b/i.test(fs)) {
|
||||||
|
|||||||
@ -1335,11 +1335,6 @@ td {
|
|||||||
.link-status-dot.checking {
|
.link-status-dot.checking {
|
||||||
background: #f59e0b;
|
background: #f59e0b;
|
||||||
box-shadow: 0 0 4px #f59e0b80;
|
box-shadow: 0 0 4px #f59e0b80;
|
||||||
animation: pulse-dot 1s ease-in-out infinite;
|
|
||||||
}
|
|
||||||
@keyframes pulse-dot {
|
|
||||||
0%, 100% { opacity: 1; }
|
|
||||||
50% { opacity: 0.4; }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.item-remove {
|
.item-remove {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user