export const EXTERNAL_IMAGE_API_CONFIG_KEY = "external_image_api_config"; export type ExternalImageApiAccessKey = { id: string; name: string; apiKey: string; enabled: boolean; notes: string; }; export type ExternalImageApiConfig = { enabled: boolean; defaultOwnerEmail: string; apiKeys: ExternalImageApiAccessKey[]; }; export const DEFAULT_EXTERNAL_IMAGE_API_OWNER_EMAIL = "external-image-api@system.local"; export const defaultExternalImageApiConfig: ExternalImageApiConfig = { enabled: false, defaultOwnerEmail: DEFAULT_EXTERNAL_IMAGE_API_OWNER_EMAIL, apiKeys: [], }; function normalizeText(value: unknown) { return String(value || "").replace(/[\u0000-\u001F\u007F]/g, "").trim(); } function normalizeEmail(value: unknown) { const normalized = normalizeText(value).toLowerCase(); if (!normalized) return DEFAULT_EXTERNAL_IMAGE_API_OWNER_EMAIL; return normalized; } function normalizeKeyId(value: unknown, fallbackIndex: number) { const normalized = normalizeText(value) .toLowerCase() .replace(/[^a-z0-9_-]+/g, "-") .replace(/-+/g, "-") .replace(/^-|-$/g, ""); return normalized || `external-key-${fallbackIndex + 1}`; } function normalizeAccessKey( value: unknown, index: number, ): ExternalImageApiAccessKey | null { if (!value || typeof value !== "object") return null; const raw = value as Partial; const apiKey = normalizeText(raw.apiKey); if (!apiKey) return null; return { id: normalizeKeyId(raw.id, index), name: normalizeText(raw.name) || `第三方调用 Key ${index + 1}`, apiKey, enabled: raw.enabled !== false, notes: normalizeText(raw.notes), }; } export function normalizeExternalImageApiConfig( value: unknown, ): ExternalImageApiConfig { if (!value || typeof value !== "object") { return defaultExternalImageApiConfig; } const raw = value as Partial & { apiKeys?: unknown[]; }; const seenIds = new Set(); const apiKeys = Array.isArray(raw.apiKeys) ? raw.apiKeys .map((item, index) => normalizeAccessKey(item, index)) .filter((item): item is ExternalImageApiAccessKey => Boolean(item)) .map((item, index) => { let nextId = item.id || `external-key-${index + 1}`; while (seenIds.has(nextId)) { nextId = `${nextId}-${seenIds.size + 1}`; } seenIds.add(nextId); return { ...item, id: nextId, }; }) : []; return { enabled: raw.enabled === true, defaultOwnerEmail: normalizeEmail(raw.defaultOwnerEmail), apiKeys, }; } export function maskExternalImageApiKey(apiKey: string) { const trimmed = normalizeText(apiKey); if (trimmed.length <= 8) return trimmed ? "********" : ""; return `${trimmed.slice(0, 4)}********${trimmed.slice(-4)}`; }