const loadScript = (src: string, fallbackSrcs?: string[]): Promise => { return new Promise((resolve, reject) => { const allSrcs = [src, ...(fallbackSrcs || [])]; // Check if any of the scripts are already loaded if (allSrcs.some(s => document.querySelector(`script[src="${s}"]`))) { resolve(); return; } const tryLoadScript = (index: number) => { if (index >= allSrcs.length) { reject(new Error(`Failed to load script from any source: ${allSrcs.join(', ')}`)); return; } const currentSrc = allSrcs[index]; const script = document.createElement('script'); script.src = currentSrc; script.onload = () => resolve(); script.onerror = () => { console.warn(`Failed to load script ${currentSrc}. Trying next fallback...`); const nextIndex = index + 1; if (nextIndex < allSrcs.length) { tryLoadScript(nextIndex); } else { reject(new Error(`Failed to load script from all sources: ${allSrcs.join(', ')}`)); } }; document.head.appendChild(script); }; tryLoadScript(0); }); }; const loadModerationLibraries = async () => { // Load TensorFlow first with fallbacks await loadScript( 'https://cdn.jsdelivr.net/npm/@tensorflow/tfjs', ['https://unpkg.com/@tensorflow/tfjs', 'https://esm.sh/@tensorflow/tfjs'] ); // Load models after tfjs is available, with multiple fallbacks await Promise.all([ loadScript( 'https://cdn.jsdelivr.net/npm/@tensorflow-models/blazeface', ['https://unpkg.com/@tensorflow-models/blazeface', 'https://esm.sh/@tensorflow-models/blazeface'] ), // NSFWJS with 3 CDN fallbacks loadScript( 'https://cdn.jsdelivr.net/npm/nsfwjs@2.4.0/dist/bundle.js', [ 'https://unpkg.com/nsfwjs@2.4.0/dist/bundle.js', 'https://esm.sh/nsfwjs@2.4.0/dist/bundle.js' ] ) ]); }; export const processImageModeration = async (file: File): Promise<{ file: File; blocked: boolean }> => { try { const settingsRes = await fetch('/api/v1/moderation/settings'); if (!settingsRes.ok) return { file, blocked: false }; const settings = await settingsRes.json(); const { blockNsfw, blurFaces } = settings; if (!blockNsfw && !blurFaces) { return { file, blocked: false }; } // Try to load moderation libraries, but don't fail if they're unavailable try { await loadModerationLibraries(); } catch (libLoadErr) { console.warn('Moderation libraries failed to load, proceeding without NSFW/Face blur checks:', libLoadErr); return { file, blocked: false }; } return new Promise((resolve) => { const img = new Image(); img.onload = async () => { const canvas = document.createElement('canvas'); canvas.width = img.width; canvas.height = img.height; const ctx = canvas.getContext('2d'); if (!ctx) { resolve({ file, blocked: false }); return; } ctx.drawImage(img, 0, 0); if (blockNsfw) { try { const nsfwModel = await (window as any).nsfwjs?.load(); if (nsfwModel) { const predictions = await nsfwModel.classify(canvas); const pornOrHentai = predictions.find((p: any) => p.className === 'Porn' || p.className === 'Hentai'); const pornProb = pornOrHentai ? pornOrHentai.probability : 0; if (pornProb > 0.5) { console.warn(`Image blocked by NSFW filter (probability: ${pornProb})`); resolve({ file, blocked: true }); return; } } } catch (e) { console.warn('NSFW validation error (will allow upload):', e); } } let modified = false; if (blurFaces) { try { const blazefaceModel = await (window as any).blazeface?.load(); if (blazefaceModel) { const predictions = await blazefaceModel.estimateFaces(canvas, false); if (predictions && predictions.length > 0) { modified = true; predictions.forEach((prediction: any) => { const startX = prediction.topLeft[0]; const startY = prediction.topLeft[1]; const endX = prediction.bottomRight[0]; const endY = prediction.bottomRight[1]; const width = endX - startX; const height = endY - startY; const faceCanvas = document.createElement('canvas'); faceCanvas.width = width; faceCanvas.height = height; const faceCtx = faceCanvas.getContext('2d'); if (faceCtx) { faceCtx.drawImage(canvas, startX, startY, width, height, 0, 0, width, height); ctx.filter = 'blur(15px)'; ctx.drawImage(faceCanvas, startX, startY, width, height); ctx.filter = 'none'; } }); } } } catch (e) { console.warn('Face blur error (will skip face detection):', e); } } if (modified) { canvas.toBlob((blob) => { if (blob) { const processedFile = new File([blob], file.name, { type: file.type }); resolve({ file: processedFile, blocked: false }); } else { resolve({ file, blocked: false }); } }, file.type); } else { resolve({ file, blocked: false }); } }; img.onerror = () => { resolve({ file, blocked: false }); }; img.src = URL.createObjectURL(file); }); } catch (err) { console.error('Image moderation process failed:', err); return { file, blocked: false }; } };