Add archive password fallback and release v1.1.28
Some checks are pending
Build and Release / build (push) Waiting to run

This commit is contained in:
Sucukdeluxe 2026-02-27 12:38:42 +01:00
parent 88eb6dff5d
commit 741a0d67cc
6 changed files with 78 additions and 22 deletions

4
package-lock.json generated
View File

@ -1,12 +1,12 @@
{ {
"name": "real-debrid-downloader", "name": "real-debrid-downloader",
"version": "1.1.27", "version": "1.1.28",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "real-debrid-downloader", "name": "real-debrid-downloader",
"version": "1.1.27", "version": "1.1.28",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"adm-zip": "^0.5.16", "adm-zip": "^0.5.16",

View File

@ -1,6 +1,6 @@
{ {
"name": "real-debrid-downloader", "name": "real-debrid-downloader",
"version": "1.1.27", "version": "1.1.28",
"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",

View File

@ -3,7 +3,7 @@ import os from "node:os";
import { AppSettings } from "../shared/types"; import { AppSettings } from "../shared/types";
export const APP_NAME = "Debrid Download Manager"; export const APP_NAME = "Debrid Download Manager";
export const APP_VERSION = "1.1.27"; export const APP_VERSION = "1.1.28";
export const API_BASE_URL = "https://api.real-debrid.com/rest/1.0"; export const API_BASE_URL = "https://api.real-debrid.com/rest/1.0";
export const DCRYPT_UPLOAD_URL = "https://dcrypt.it/decrypt/upload"; export const DCRYPT_UPLOAD_URL = "https://dcrypt.it/decrypt/upload";

View File

@ -1241,8 +1241,9 @@ export class DownloadManager extends EventEmitter {
removeSamples: this.settings.removeSamplesAfterExtract removeSamples: this.settings.removeSamplesAfterExtract
}); });
if (result.failed > 0) { if (result.failed > 0) {
const reason = compactErrorText(result.lastError || "Entpacken fehlgeschlagen");
for (const entry of completedItems) { for (const entry of completedItems) {
entry.fullStatus = "Entpack-Fehler"; entry.fullStatus = `Entpack-Fehler: ${reason}`;
entry.updatedAt = nowMs(); entry.updatedAt = nowMs();
} }
pkg.status = "failed"; pkg.status = "failed";

View File

@ -6,6 +6,8 @@ import { CleanupMode, ConflictMode } from "../shared/types";
import { logger } from "./logger"; import { logger } from "./logger";
import { removeDownloadLinkArtifacts, removeSampleArtifacts } from "./cleanup"; import { removeDownloadLinkArtifacts, removeSampleArtifacts } from "./cleanup";
const DEFAULT_ARCHIVE_PASSWORDS = ["", "serienfans.org", "serienjunkies.org"];
export interface ExtractOptions { export interface ExtractOptions {
packageDir: string; packageDir: string;
targetDir: string; targetDir: string;
@ -39,39 +41,74 @@ function effectiveConflictMode(conflictMode: ConflictMode): "overwrite" | "skip"
return "skip"; return "skip";
} }
export function buildExternalExtractArgs(command: string, archivePath: string, targetDir: string, conflictMode: ConflictMode): string[] { export function buildExternalExtractArgs(
command: string,
archivePath: string,
targetDir: string,
conflictMode: ConflictMode,
password = ""
): string[] {
const mode = effectiveConflictMode(conflictMode); const mode = effectiveConflictMode(conflictMode);
const lower = command.toLowerCase(); const lower = command.toLowerCase();
if (lower.includes("unrar")) { if (lower.includes("unrar")) {
const overwrite = mode === "overwrite" ? "-o+" : mode === "rename" ? "-or" : "-o-"; const overwrite = mode === "overwrite" ? "-o+" : mode === "rename" ? "-or" : "-o-";
return ["x", overwrite, archivePath, `${targetDir}${path.sep}`]; const pass = password ? `-p${password}` : "-p-";
return ["x", overwrite, pass, archivePath, `${targetDir}${path.sep}`];
} }
const overwrite = mode === "overwrite" ? "-aoa" : mode === "rename" ? "-aou" : "-aos"; const overwrite = mode === "overwrite" ? "-aoa" : mode === "rename" ? "-aou" : "-aos";
return ["x", "-y", overwrite, archivePath, `-o${targetDir}`]; const pass = password ? `-p${password}` : "-p";
return ["x", "-y", overwrite, pass, archivePath, `-o${targetDir}`];
} }
function runExternalExtract(archivePath: string, targetDir: string, conflictMode: ConflictMode): Promise<void> { function runExternalExtract(archivePath: string, targetDir: string, conflictMode: ConflictMode): Promise<void> {
const candidates = ["7z", "C:\\Program Files\\7-Zip\\7z.exe", "C:\\Program Files (x86)\\7-Zip\\7z.exe", "unrar"]; const candidates = ["7z", "C:\\Program Files\\7-Zip\\7z.exe", "C:\\Program Files (x86)\\7-Zip\\7z.exe", "unrar"];
const passwords = Array.from(new Set(DEFAULT_ARCHIVE_PASSWORDS));
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const tryExec = (idx: number): void => { let lastError = "";
if (idx >= candidates.length) {
reject(new Error("Kein 7z/unrar gefunden")); const cleanErrorText = (text: string): string => text.replace(/\s+/g, " ").trim().slice(0, 240);
const tryExec = (cmdIdx: number, passwordIdx: number): void => {
if (cmdIdx >= candidates.length) {
reject(new Error(lastError || "Kein 7z/unrar gefunden"));
return; return;
} }
const cmd = candidates[idx]; fs.mkdirSync(targetDir, { recursive: true });
const args = buildExternalExtractArgs(cmd, archivePath, targetDir, conflictMode); const cmd = candidates[cmdIdx];
const password = passwords[passwordIdx] || "";
const args = buildExternalExtractArgs(cmd, archivePath, targetDir, conflictMode, password);
const child = spawn(cmd, args, { windowsHide: true }); const child = spawn(cmd, args, { windowsHide: true });
child.on("error", () => tryExec(idx + 1)); let output = "";
child.stdout.on("data", (chunk) => {
output += String(chunk || "");
});
child.stderr.on("data", (chunk) => {
output += String(chunk || "");
});
child.on("error", (error) => {
lastError = cleanErrorText(String(error));
tryExec(cmdIdx + 1, 0);
});
child.on("close", (code) => { child.on("close", (code) => {
if (code === 0 || code === 1) { if (code === 0 || code === 1) {
resolve(); resolve();
} else { } else {
tryExec(idx + 1); const cleaned = cleanErrorText(output);
if (cleaned) {
lastError = cleaned;
} else {
lastError = `Exit Code ${String(code ?? "?")}`;
}
if (passwordIdx + 1 < passwords.length) {
tryExec(cmdIdx, passwordIdx + 1);
return;
}
tryExec(cmdIdx + 1, 0);
} }
}); });
}; };
tryExec(0); tryExec(0, 0);
}); });
} }
@ -119,20 +156,25 @@ function cleanupArchives(sourceFiles: string[], cleanupMode: CleanupMode): void
} }
} }
export async function extractPackageArchives(options: ExtractOptions): Promise<{ extracted: number; failed: number }> { export async function extractPackageArchives(options: ExtractOptions): Promise<{ extracted: number; failed: number; lastError: string }> {
const candidates = findArchiveCandidates(options.packageDir); const candidates = findArchiveCandidates(options.packageDir);
if (candidates.length === 0) { if (candidates.length === 0) {
return { extracted: 0, failed: 0 }; return { extracted: 0, failed: 0, lastError: "" };
} }
let extracted = 0; let extracted = 0;
let failed = 0; let failed = 0;
let lastError = "";
const extractedArchives: string[] = []; const extractedArchives: string[] = [];
for (const archivePath of candidates) { for (const archivePath of candidates) {
try { try {
const ext = path.extname(archivePath).toLowerCase(); const ext = path.extname(archivePath).toLowerCase();
if (ext === ".zip") { if (ext === ".zip") {
extractZipArchive(archivePath, options.targetDir, options.conflictMode); try {
extractZipArchive(archivePath, options.targetDir, options.conflictMode);
} catch {
await runExternalExtract(archivePath, options.targetDir, options.conflictMode);
}
} else { } else {
await runExternalExtract(archivePath, options.targetDir, options.conflictMode); await runExternalExtract(archivePath, options.targetDir, options.conflictMode);
} }
@ -140,7 +182,9 @@ export async function extractPackageArchives(options: ExtractOptions): Promise<{
extractedArchives.push(archivePath); extractedArchives.push(archivePath);
} catch (error) { } catch (error) {
failed += 1; failed += 1;
logger.error(`Entpack-Fehler ${path.basename(archivePath)}: ${String(error)}`); const errorText = String(error);
lastError = errorText;
logger.error(`Entpack-Fehler ${path.basename(archivePath)}: ${errorText}`);
} }
} }
@ -162,5 +206,5 @@ export async function extractPackageArchives(options: ExtractOptions): Promise<{
} }
} }
return { extracted, failed }; return { extracted, failed, lastError };
} }

View File

@ -19,6 +19,7 @@ describe("extractor", () => {
"x", "x",
"-y", "-y",
"-aoa", "-aoa",
"-p",
"archive.rar", "archive.rar",
"-oC:\\target" "-oC:\\target"
]); ]);
@ -26,6 +27,15 @@ describe("extractor", () => {
"x", "x",
"-y", "-y",
"-aos", "-aos",
"-p",
"archive.rar",
"-oC:\\target"
]);
expect(buildExternalExtractArgs("7z", "archive.rar", "C:\\target", "ask", "serienfans.org")).toEqual([
"x",
"-y",
"-aos",
"-pserienfans.org",
"archive.rar", "archive.rar",
"-oC:\\target" "-oC:\\target"
]); ]);
@ -33,7 +43,8 @@ describe("extractor", () => {
const unrarRename = buildExternalExtractArgs("unrar", "archive.rar", "C:\\target", "rename"); const unrarRename = buildExternalExtractArgs("unrar", "archive.rar", "C:\\target", "rename");
expect(unrarRename[0]).toBe("x"); expect(unrarRename[0]).toBe("x");
expect(unrarRename[1]).toBe("-or"); expect(unrarRename[1]).toBe("-or");
expect(unrarRename[2]).toBe("archive.rar"); expect(unrarRename[2]).toBe("-p-");
expect(unrarRename[3]).toBe("archive.rar");
}); });
it("deletes only successfully extracted archives", async () => { it("deletes only successfully extracted archives", async () => {