From bb8fd0646aefd8f3203214aad984ed0abb973ac3 Mon Sep 17 00:00:00 2001 From: Sucukdeluxe Date: Wed, 4 Mar 2026 03:35:11 +0100 Subject: [PATCH] Release v1.5.93 Co-Authored-By: Claude Opus 4.6 --- package.json | 2 +- src/main/download-manager.ts | 50 +++++++++++++++++++++++++++++++++--- src/main/storage.ts | 4 +++ tests/auto-rename.test.ts | 41 +++++++++++++++++++++++++++++ 4 files changed, 93 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index dcba8f7..5695cfc 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "real-debrid-downloader", - "version": "1.5.92", + "version": "1.5.93", "description": "Real-Debrid Downloader Desktop (Electron + React + TypeScript)", "main": "build/main/main/main.js", "author": "Sucukdeluxe", diff --git a/src/main/download-manager.ts b/src/main/download-manager.ts index 49d3d5c..1b755a4 100644 --- a/src/main/download-manager.ts +++ b/src/main/download-manager.ts @@ -705,6 +705,23 @@ export function buildAutoRenameBaseNameFromFoldersWithOptions( return sanitizeFilename(target); } + // Last-resort fallback: if no scene-group-suffix folder was found but a folder + // has a season token and the source has an episode token, inject the episode anyway. + // This handles user-renamed packages like "Mystery Road S02" where the folder has + // no scene group suffix but still contains enough info for a useful rename. + if (resolvedEpisode && forceEpisodeForSeasonFolder) { + for (const folderName of ordered) { + if (!SCENE_SEASON_ONLY_RE.test(folderName) || extractEpisodeToken(folderName)) { + continue; + } + let target = applyEpisodeTokenToFolderName(folderName, resolvedEpisode.token); + if (globalRepackHint) { + target = ensureRepackToken(removeRpTokens(target)); + } + return sanitizeFilename(target); + } + } + return null; } @@ -2029,7 +2046,7 @@ export class DownloadManager extends EventEmitter { return next; } - private async autoRenameExtractedVideoFiles(extractDir: string): Promise { + private async autoRenameExtractedVideoFiles(extractDir: string, pkg?: PackageEntry): Promise { if (!this.settings.autoRename4sf4sj) { return 0; } @@ -2037,6 +2054,25 @@ export class DownloadManager extends EventEmitter { const videoFiles = await this.collectVideoFiles(extractDir); let renamed = 0; + // Collect additional folder candidates from package metadata (outputDir, item filenames) + const packageExtraCandidates: string[] = []; + if (pkg) { + const outputBase = path.basename(pkg.outputDir || ""); + if (outputBase) { + packageExtraCandidates.push(outputBase); + } + for (const itemId of pkg.itemIds) { + const item = this.session.items[itemId]; + if (item?.fileName) { + const itemBase = path.basename(item.fileName, path.extname(item.fileName)); + const stripped = itemBase.replace(/\.part\d+$/i, "").replace(/\.vol\d+[+\d]*$/i, ""); + if (stripped) { + packageExtraCandidates.push(stripped); + } + } + } + } + for (const sourcePath of videoFiles) { const sourceName = path.basename(sourcePath); const sourceExt = path.extname(sourceName); @@ -2051,6 +2087,14 @@ export class DownloadManager extends EventEmitter { } currentDir = parent; } + // Append package-level candidates that aren't already present + const seen = new Set(folderCandidates.map(c => c.toLowerCase())); + for (const extra of packageExtraCandidates) { + if (!seen.has(extra.toLowerCase())) { + seen.add(extra.toLowerCase()); + folderCandidates.push(extra); + } + } const targetBaseName = buildAutoRenameBaseNameFromFoldersWithOptions(folderCandidates, sourceBaseName, { forceEpisodeForSeasonFolder: true }); @@ -5871,7 +5915,7 @@ export class DownloadManager extends EventEmitter { logger.info(`Hybrid-Extract Ende: pkg=${pkg.name}, extracted=${result.extracted}, failed=${result.failed}`); if (result.extracted > 0) { - await this.autoRenameExtractedVideoFiles(pkg.extractDir); + await this.autoRenameExtractedVideoFiles(pkg.extractDir, pkg); } if (result.failed > 0) { logger.warn(`Hybrid-Extract: ${result.failed} Archive fehlgeschlagen, wird beim finalen Durchlauf erneut versucht`); @@ -6151,7 +6195,7 @@ export class DownloadManager extends EventEmitter { } else { const hasExtractedOutput = await this.directoryHasAnyFiles(pkg.extractDir); if (result.extracted > 0 || hasExtractedOutput) { - await this.autoRenameExtractedVideoFiles(pkg.extractDir); + await this.autoRenameExtractedVideoFiles(pkg.extractDir, pkg); } const sourceExists = await this.existsAsync(pkg.outputDir); let finalStatusText = ""; diff --git a/src/main/storage.ts b/src/main/storage.ts index 82ed5bb..5436e23 100644 --- a/src/main/storage.ts +++ b/src/main/storage.ts @@ -257,6 +257,9 @@ function normalizeLoadedSession(raw: unknown): SessionState { const status: DownloadStatus = VALID_DOWNLOAD_STATUSES.has(statusRaw) ? statusRaw : "queued"; const providerRaw = asText(item.provider) as DebridProvider; + const onlineStatusRaw = asText(item.onlineStatus); + const validOnlineStatuses = new Set(["online", "offline", "checking"]); + itemsById[id] = { id, packageId, @@ -274,6 +277,7 @@ function normalizeLoadedSession(raw: unknown): SessionState { attempts: clampNumber(item.attempts, 0, 0, 10_000), lastError: asText(item.lastError), fullStatus: asText(item.fullStatus), + onlineStatus: validOnlineStatuses.has(onlineStatusRaw) ? onlineStatusRaw as "online" | "offline" | "checking" : undefined, createdAt: clampNumber(item.createdAt, now, 0, Number.MAX_SAFE_INTEGER), updatedAt: clampNumber(item.updatedAt, now, 0, Number.MAX_SAFE_INTEGER) }; diff --git a/tests/auto-rename.test.ts b/tests/auto-rename.test.ts index c49f247..f147529 100644 --- a/tests/auto-rename.test.ts +++ b/tests/auto-rename.test.ts @@ -620,4 +620,45 @@ describe("buildAutoRenameBaseNameFromFolders", () => { ); expect(result).toBe("Mammon.S01E05E06.German.1080P.Bluray.x264-SMAHD"); }); + + // Last-resort fallback: folder has season but no scene group suffix (user-renamed packages) + it("renames when folder has season but no scene group suffix (Mystery Road case)", () => { + const result = buildAutoRenameBaseNameFromFoldersWithOptions( + ["Mystery Road S02"], + "myst.road.de.dl.hdtv.7p-s02e05", + { forceEpisodeForSeasonFolder: true } + ); + expect(result).toBe("Mystery Road S02E05"); + }); + + it("renames with season-only folder and custom name without dots", () => { + const result = buildAutoRenameBaseNameFromFoldersWithOptions( + ["Meine Serie S03"], + "meine-serie-s03e10-720p", + { forceEpisodeForSeasonFolder: true } + ); + expect(result).toBe("Meine Serie S03E10"); + }); + + it("prefers scene-group folder over season-only fallback", () => { + const result = buildAutoRenameBaseNameFromFoldersWithOptions( + [ + "Mystery Road S02", + "Mystery.Road.S02.GERMAN.DL.AC3.720p.HDTV.x264-hrs" + ], + "myst.road.de.dl.hdtv.7p-s02e05", + { forceEpisodeForSeasonFolder: true } + ); + // Should use the scene-group folder (hrs), not the custom one + expect(result).toBe("Mystery.Road.S02E05.GERMAN.DL.AC3.720p.HDTV.x264-hrs"); + }); + + it("does not use season-only fallback when forceEpisodeForSeasonFolder is false", () => { + const result = buildAutoRenameBaseNameFromFoldersWithOptions( + ["Mystery Road S02"], + "myst.road.de.dl.hdtv.7p-s02e05", + { forceEpisodeForSeasonFolder: false } + ); + expect(result).toBeNull(); + }); });