feat(images): add sha256-deduplicated image downloader

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-17 15:09:31 +02:00
parent 4c8f4da46c
commit 757b0f720e
2 changed files with 104 additions and 0 deletions

View File

@@ -0,0 +1,40 @@
import { createHash } from 'node:crypto';
import { existsSync } from 'node:fs';
import { mkdir, writeFile } from 'node:fs/promises';
import { join } from 'node:path';
import { fetchBuffer } from '../http';
const EXT_BY_CONTENT_TYPE: Record<string, string> = {
'image/jpeg': '.jpg',
'image/jpg': '.jpg',
'image/png': '.png',
'image/webp': '.webp',
'image/gif': '.gif',
'image/avif': '.avif'
};
function extensionFor(contentType: string | null): string {
if (!contentType) return '.bin';
const base = contentType.split(';')[0].trim().toLowerCase();
return EXT_BY_CONTENT_TYPE[base] ?? '.bin';
}
export async function downloadImage(
url: string,
targetDir: string
): Promise<string | null> {
try {
const { data, contentType } = await fetchBuffer(url, { maxBytes: 10 * 1024 * 1024 });
const hash = createHash('sha256').update(data).digest('hex');
const ext = extensionFor(contentType);
const filename = `${hash}${ext}`;
const target = join(targetDir, filename);
if (!existsSync(target)) {
await mkdir(targetDir, { recursive: true });
await writeFile(target, data);
}
return filename;
} catch {
return null;
}
}