refactor: enhance filename extraction and handling in download services

This commit is contained in:
Moyasee
2026-01-06 18:50:36 +02:00
parent 2b3a8bf6b6
commit ca2f70aede
2 changed files with 81 additions and 8 deletions

View File

@@ -454,34 +454,52 @@ export class DownloadManager {
const token = await GofileApi.authorize();
const downloadLink = await GofileApi.getDownloadLink(id!);
await GofileApi.checkDownloadUrl(downloadLink);
const filename =
this.extractFilename(download.uri, downloadLink) ||
this.extractFilename(downloadLink);
return {
url: downloadLink,
savePath: download.downloadPath,
filename: filename ? this.sanitizeFilename(filename) : undefined,
headers: { Cookie: `accountToken=${token}` },
};
}
case Downloader.PixelDrain: {
const id = download.uri.split("/").pop();
const downloadUrl = await PixelDrainApi.getDownloadUrl(id!);
const filename =
this.extractFilename(download.uri, downloadUrl) ||
this.extractFilename(downloadUrl);
return {
url: downloadUrl,
savePath: download.downloadPath,
filename: filename ? this.sanitizeFilename(filename) : undefined,
};
}
case Downloader.Qiwi: {
const downloadUrl = await QiwiApi.getDownloadUrl(download.uri);
const filename =
this.extractFilename(download.uri, downloadUrl) ||
this.extractFilename(downloadUrl);
return {
url: downloadUrl,
savePath: download.downloadPath,
filename: filename ? this.sanitizeFilename(filename) : undefined,
};
}
case Downloader.Datanodes: {
const downloadUrl = await DatanodesApi.getDownloadUrl(download.uri);
const filename =
this.extractFilename(download.uri, downloadUrl) ||
this.extractFilename(downloadUrl);
return {
url: downloadUrl,
savePath: download.downloadPath,
filename: filename ? this.sanitizeFilename(filename) : undefined,
};
}
case Downloader.Buzzheavier: {
@@ -516,18 +534,27 @@ export class DownloadManager {
}
case Downloader.Mediafire: {
const downloadUrl = await MediafireApi.getDownloadUrl(download.uri);
const filename =
this.extractFilename(download.uri, downloadUrl) ||
this.extractFilename(downloadUrl);
return {
url: downloadUrl,
savePath: download.downloadPath,
filename: filename ? this.sanitizeFilename(filename) : undefined,
};
}
case Downloader.RealDebrid: {
const downloadUrl = await RealDebridClient.getDownloadUrl(download.uri);
if (!downloadUrl) throw new Error(DownloadError.NotCachedOnRealDebrid);
const filename =
this.extractFilename(download.uri, downloadUrl) ||
this.extractFilename(downloadUrl);
return {
url: downloadUrl,
savePath: download.downloadPath,
filename: filename ? this.sanitizeFilename(filename) : undefined,
};
}
case Downloader.TorBox: {
@@ -545,10 +572,14 @@ export class DownloadManager {
download.uri
);
if (!downloadUrl) throw new Error(DownloadError.NotCachedOnHydra);
const filename =
this.extractFilename(download.uri, downloadUrl) ||
this.extractFilename(downloadUrl);
return {
url: downloadUrl,
savePath: download.downloadPath,
filename: filename ? this.sanitizeFilename(filename) : undefined,
};
}
case Downloader.VikingFile: {

View File

@@ -50,7 +50,7 @@ export class JsHttpDownloader {
this.isDownloading = true;
const { url, savePath, filename, headers = {} } = options;
const { filePath, startByte } = this.prepareDownloadPath(
const { filePath, startByte, usedFallback } = this.prepareDownloadPath(
savePath,
filename,
url
@@ -58,7 +58,14 @@ export class JsHttpDownloader {
const requestHeaders = this.buildRequestHeaders(headers, startByte);
try {
await this.executeDownload(url, requestHeaders, filePath, startByte);
await this.executeDownload(
url,
requestHeaders,
filePath,
startByte,
savePath,
usedFallback
);
} catch (err) {
this.handleDownloadError(err as Error);
} finally {
@@ -71,9 +78,10 @@ export class JsHttpDownloader {
savePath: string,
filename: string | undefined,
url: string
): { filePath: string; startByte: number } {
const resolvedFilename =
filename || this.extractFilename(url) || "download";
): { filePath: string; startByte: number; usedFallback: boolean } {
const extractedFilename = filename || this.extractFilename(url);
const usedFallback = !extractedFilename;
const resolvedFilename = extractedFilename || "download";
this.folderName = resolvedFilename;
const filePath = path.join(savePath, resolvedFilename);
@@ -90,7 +98,7 @@ export class JsHttpDownloader {
}
this.resetSpeedTracking();
return { filePath, startByte };
return { filePath, startByte, usedFallback };
}
private buildRequestHeaders(
@@ -130,7 +138,9 @@ export class JsHttpDownloader {
url: string,
requestHeaders: Record<string, string>,
filePath: string,
startByte: number
startByte: number,
savePath: string,
usedFallback: boolean
): Promise<void> {
const response = await fetch(url, {
headers: requestHeaders,
@@ -143,12 +153,25 @@ export class JsHttpDownloader {
this.parseFileSize(response, startByte);
// If we used "download" fallback, try to get filename from Content-Disposition
let actualFilePath = filePath;
if (usedFallback && startByte === 0) {
const headerFilename = this.parseContentDisposition(response);
if (headerFilename) {
actualFilePath = path.join(savePath, headerFilename);
this.folderName = headerFilename;
logger.log(
`[JsHttpDownloader] Using filename from Content-Disposition: ${headerFilename}`
);
}
}
if (!response.body) {
throw new Error("Response body is null");
}
const flags = startByte > 0 ? "a" : "w";
this.writeStream = fs.createWriteStream(filePath, { flags });
this.writeStream = fs.createWriteStream(actualFilePath, { flags });
const readableStream = this.createReadableStream(response.body.getReader());
await pipeline(readableStream, this.writeStream);
@@ -158,6 +181,25 @@ export class JsHttpDownloader {
logger.log("[JsHttpDownloader] Download complete");
}
private parseContentDisposition(response: Response): string | undefined {
const header = response.headers.get("content-disposition");
if (!header) return undefined;
// Try to extract filename from Content-Disposition header
// Formats: attachment; filename="file.zip" or attachment; filename=file.zip
const filenameMatch = /filename\*?=['"]?(?:UTF-8'')?([^"';\n]+)['"]?/i.exec(
header
);
if (filenameMatch?.[1]) {
try {
return decodeURIComponent(filenameMatch[1].trim());
} catch {
return filenameMatch[1].trim();
}
}
return undefined;
}
private createReadableStream(
reader: ReadableStreamDefaultReader<Uint8Array>
): Readable {