import { BadRequestException, Injectable, NotFoundException, } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; import { MinioClientService } from './asset-minio.service'; import type { BlogLive2DAssetResult, BlogLive2DRuntimeAssetPath, } from '../domain/blog-live2d-asset.types'; const DEFAULT_ALLOWED_ORIGINS = 'https://blog.kwitsukasa.top'; const DEFAULT_LIVE2D_BUCKET = 'kt-template-online'; const DEFAULT_LIVE2D_PREFIX = 'blog/live2d/pio'; const MAX_DECODE_DEPTH = 6; const ALLOWED_RUNTIME_FAMILIES = new Set(['moc', 'moc3']); /** * Detects MinIO/S3 object-missing errors from `statObject` and `getObject`. * @param error - Unknown failure thrown by the MinIO client while resolving a runtime object. * @returns `true` when the failure means the requested object key does not exist. */ function isMinioObjectNotFound(error: unknown): boolean { if (!error || typeof error !== 'object') { return false; } const candidate = error as { code?: unknown; message?: unknown }; return ( candidate.code === 'NotFound' || candidate.code === 'NoSuchKey' || candidate.message === 'Not Found' ); } @Injectable() export class BlogLive2DAssetService { /** * Creates the guarded Blog Live2D asset service. * @param minioClientService - Existing MinIO helper used to stream Pio runtime family files. * @param configService - Runtime config source for bucket, object prefix, and allowed browser origins. */ constructor( private readonly minioClientService: MinioClientService, private readonly configService: ConfigService, ) {} /** * Rejects browser asset requests that do not come from the configured Blog origins. * @param referer - Browser Referer header; used for normal `