import type { Database } from '@tursodatabase/database'; import { mkdir, readFile, rm, writeFile } from 'node:fs/promises'; import path from 'node:path'; import sharp from 'sharp'; import { GAME_ICON_TYPES, type GameDataIconType } from './game-data-icons'; import { BackgroundRemovalError, removeBackgroundPreview } from './remove-background'; export const MANAGED_IMAGE_CATEGORIES = [ 'General', 'Interface', 'Gear', 'Weapons', 'Weapon Mods', 'Gear Mods', 'Talents', 'Brands', 'OS Protocols', 'Skill Chips', 'Specializations', 'System' ] as const; export const MAX_MANAGED_IMAGE_BYTES = 5 * 1024 * 1024; const MAX_PIXELS = 24_000_000; const ID_PATTERN = /^[0-9a-f-]{36}$/i; const KEY_PATTERN = /^(?:library|talent|brand_set|os_protocol|skill_chip|specialization|specialization_ability|specialization_node)-[0-9a-f-]{36}\.webp$/i; export class ManagedImageValidationError extends Error {} export class ManagedImageDeletionError extends Error {} export function managedImageStorageDirectory() { return path.resolve(process.env.TDR_GAME_ICON_STORAGE_PATH || path.join(process.cwd(), 'data', 'game-icons')); } const storagePath = (key: string) => { if (!KEY_PATTERN.test(key)) throw new Error('Invalid managed image storage key'); return path.join(managedImageStorageDirectory(), key); }; export function managedImageUrl(id: string, updatedAt?: unknown) { if (!ID_PATTERN.test(id)) return ''; const version = typeof updatedAt === 'string' ? `?v=${encodeURIComponent(updatedAt)}` : ''; return `/images/library/${id}${version}`; } function validateSvg(input: Buffer) { const source = input.toString('utf8'); if (/<(?:script|foreignObject|image|iframe|object|embed)\b/i.test(source) || / MAX_MANAGED_IMAGE_BYTES) { throw new ManagedImageValidationError('Choose an image no larger than 5 MiB.'); } const input = Buffer.from(await file.arrayBuffer()); try { const looksLikeSvg = file.type === 'image/svg+xml' || input.subarray(0, 512).toString('utf8').toLowerCase().includes(' undefined); throw error; } await rm(storagePath(image.storage_key), { force: true }); return { image, sourceIcon }; } export async function readManagedImage(database: Database, id: string) { if (!ID_PATTERN.test(id)) return null; const row = await (await database.prepare('SELECT storage_key FROM managed_images WHERE id=?')).get(id); if (typeof row?.storage_key !== 'string') return null; try { return await readFile(storagePath(row.storage_key)); } catch (error) { if ((error as NodeJS.ErrnoException).code === 'ENOENT') return null; throw error; } }