const loadScript = (src: string, fallbackSrc?: string): Promise => { return new Promise((resolve, reject) => { if ( document.querySelector(`script[src="${src}"]`) || (fallbackSrc && document.querySelector(`script[src="${fallbackSrc}"]`)) ) { resolve(); return; } const script = document.createElement('script'); script.src = src; script.onload = () => resolve(); script.onerror = () => { if (fallbackSrc) { console.warn(`Failed to load script ${src}. Trying fallback: ${fallbackSrc}`); const fallbackScript = document.createElement('script'); fallbackScript.src = fallbackSrc; fallbackScript.onload = () => resolve(); fallbackScript.onerror = () => reject(new Error(`Failed to load script ${fallbackSrc}`)); document.head.appendChild(fallbackScript); } else { reject(new Error(`Failed to load script ${src}`)); } }; document.head.appendChild(script); }); }; const loadModerationLibraries = async () => { // Load TensorFlow first with fallback await loadScript( 'https://cdn.jsdelivr.net/npm/@tensorflow/tfjs', 'https://unpkg.com/@tensorflow/tfjs' ); // Load models after tfjs is available, with fallbacks await Promise.all([ loadScript( 'https://cdn.jsdelivr.net/npm/@tensorflow-models/blazeface', 'https://unpkg.com/@tensorflow-models/blazeface' ), loadScript( 'https://cdn.jsdelivr.net/npm/nsfwjs@2.4.0/dist/bundle.js', 'https://unpkg.com/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 }; } await loadModerationLibraries(); 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(); 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) { resolve({ file, blocked: true }); return; } } catch (e) { console.error('NSFW validation error:', e); } } let modified = false; if (blurFaces) { try { const blazefaceModel = await (window as any).blazeface.load(); 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.error('Face blur error:', 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 failed:', err); return { file, blocked: false }; } };