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",
"version": "1.1.27",
"version": "1.1.28",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "real-debrid-downloader",
"version": "1.1.27",
"version": "1.1.28",
"license": "MIT",
"dependencies": {
"adm-zip": "^0.5.16",

View File

@ -1,6 +1,6 @@
{
"name": "real-debrid-downloader",
"version": "1.1.27",
"version": "1.1.28",
"description": "Real-Debrid Downloader Desktop (Electron + React + TypeScript)",
"main": "build/main/main/main.js",
"author": "Sucukdeluxe",

View File

@ -3,7 +3,7 @@ import os from "node:os";
import { AppSettings } from "../shared/types";
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 DCRYPT_UPLOAD_URL = "https://dcrypt.it/decrypt/upload";

View File

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

View File

@ -6,6 +6,8 @@ import { CleanupMode, ConflictMode } from "../shared/types";
import { logger } from "./logger";
import { removeDownloadLinkArtifacts, removeSampleArtifacts } from "./cleanup";
const DEFAULT_ARCHIVE_PASSWORDS = ["", "serienfans.org", "serienjunkies.org"];
export interface ExtractOptions {
packageDir: string;
targetDir: string;
@ -39,39 +41,74 @@ function effectiveConflictMode(conflictMode: ConflictMode): "overwrite" | "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 lower = command.toLowerCase();
if (lower.includes("unrar")) {
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";
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> {
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) => {
const tryExec = (idx: number): void => {
if (idx >= candidates.length) {
reject(new Error("Kein 7z/unrar gefunden"));
let lastError = "";
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;
}
const cmd = candidates[idx];
const args = buildExternalExtractArgs(cmd, archivePath, targetDir, conflictMode);
fs.mkdirSync(targetDir, { recursive: true });
const cmd = candidates[cmdIdx];
const password = passwords[passwordIdx] || "";
const args = buildExternalExtractArgs(cmd, archivePath, targetDir, conflictMode, password);
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) => {
if (code === 0 || code === 1) {
resolve();
} 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);
if (candidates.length === 0) {
return { extracted: 0, failed: 0 };
return { extracted: 0, failed: 0, lastError: "" };
}
let extracted = 0;
let failed = 0;
let lastError = "";
const extractedArchives: string[] = [];
for (const archivePath of candidates) {
try {
const ext = path.extname(archivePath).toLowerCase();
if (ext === ".zip") {
try {
extractZipArchive(archivePath, options.targetDir, options.conflictMode);
} catch {
await runExternalExtract(archivePath, options.targetDir, options.conflictMode);
}
} else {
await runExternalExtract(archivePath, options.targetDir, options.conflictMode);
}
@ -140,7 +182,9 @@ export async function extractPackageArchives(options: ExtractOptions): Promise<{
extractedArchives.push(archivePath);
} catch (error) {
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",
"-y",
"-aoa",
"-p",
"archive.rar",
"-oC:\\target"
]);
@ -26,6 +27,15 @@ describe("extractor", () => {
"x",
"-y",
"-aos",
"-p",
"archive.rar",
"-oC:\\target"
]);
expect(buildExternalExtractArgs("7z", "archive.rar", "C:\\target", "ask", "serienfans.org")).toEqual([
"x",
"-y",
"-aos",
"-pserienfans.org",
"archive.rar",
"-oC:\\target"
]);
@ -33,7 +43,8 @@ describe("extractor", () => {
const unrarRename = buildExternalExtractArgs("unrar", "archive.rar", "C:\\target", "rename");
expect(unrarRename[0]).toBe("x");
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 () => {