From 86d3eeb51fd78b7e3438468db018c49b744de6a3 Mon Sep 17 00:00:00 2001 From: Martin Porwoll Date: Sun, 1 Mar 2026 10:36:09 +0000 Subject: [PATCH] fix: infer MIME type from file extension for Telegram downloads MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Telegram's file API returns application/octet-stream instead of the actual image type. Added inferMimeType() that detects the real type from the URL extension (jpg→image/jpeg, png→image/png, etc.) when the server response Content-Type is generic. Co-Authored-By: Claude Opus 4.6 --- src/utils/download.ts | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/src/utils/download.ts b/src/utils/download.ts index 24239b0..0ec6351 100644 --- a/src/utils/download.ts +++ b/src/utils/download.ts @@ -3,6 +3,29 @@ import { createLogger } from './logger.js'; const log = createLogger('Download'); const MAX_FILE_SIZE = 20 * 1024 * 1024; // 20 MB +// Telegram's file API often returns application/octet-stream — infer from extension +const EXT_MIME_MAP: Record = { + jpg: 'image/jpeg', + jpeg: 'image/jpeg', + png: 'image/png', + gif: 'image/gif', + webp: 'image/webp', + avif: 'image/avif', + svg: 'image/svg+xml', + pdf: 'application/pdf', + mp4: 'video/mp4', +}; + +function inferMimeType(url: string, headerType: string): string { + // If server returned a specific image/video/pdf type, trust it + if (headerType && !headerType.startsWith('application/octet-stream') && headerType !== 'text/plain') { + return headerType.split(';')[0].trim(); + } + // Infer from URL extension + const ext = url.split(/[?#]/)[0].split('.').pop()?.toLowerCase() ?? ''; + return EXT_MIME_MAP[ext] ?? headerType ?? 'application/octet-stream'; +} + export async function downloadFile(url: string): Promise<{ buffer: Buffer; mimeType: string }> { const controller = new AbortController(); const timeout = setTimeout(() => controller.abort(), 30_000); @@ -23,8 +46,9 @@ export async function downloadFile(url: string): Promise<{ buffer: Buffer; mimeT throw new Error(`Datei zu groß (${(arrayBuffer.byteLength / 1024 / 1024).toFixed(1)} MB). Maximum: 20 MB`); } - const mimeType = response.headers.get('content-type') || 'application/octet-stream'; - log.debug(`Downloaded ${arrayBuffer.byteLength} bytes, type: ${mimeType}`); + const rawType = response.headers.get('content-type') || 'application/octet-stream'; + const mimeType = inferMimeType(url, rawType); + log.info(`Downloaded ${arrayBuffer.byteLength} bytes, header: ${rawType}, resolved: ${mimeType}`); return { buffer: Buffer.from(arrayBuffer), mimeType }; } catch (error) {