// Sistema de Moderação de Conteúdo class ContentModeration { constructor() { this.blockedWords = [ // URLs e Links suspeitos '.gg/HpZzvY5W', '.gg/TZsbat4tw6', 'discord.gg/doncommunity', 'discord.gg/P93HBWRp', 'bit.ly', 'encurtador.com.br', 'is.gd', 'kurl.ru', 'l1nk.dev', 'n9.cl', 'rb.gy', 'shorturl.at', 'shre.ink', 'surl.li', 't.ly', 't.me', 'tinyurl.com', 'u.to', 'urlzs.com', 'zzb.bz', 'steamcommunity', 'steamgift', 'steamrconmmunity', 'steamscommunuty', 'steamshort', 'steanmecomnmunity', 'store-steaempowered', 'share.blood-strike.com', 'casumonster.top', 'abre.ai', 'abrir.link', 'open.ai', 'open.link', // Palavrões e termos ofensivos 'arromb', 'asshole', 'babac', 'bastard', 'bct', 'boceta', 'bocetas', 'boquete', 'bosta', 'bostinha', 'buceta', 'bucetas', 'burro', 'cacete', 'caralh', 'caralho', 'corno', 'corna', 'crlh', 'cu', 'cuckold', 'cum', 'cumshot', 'cunt', 'cunts', 'cuz', 'desgraça', 'desgraçado', 'dick', 'escrot', 'fdp', 'foda', 'fuck', 'gay', 'idiota', 'imbecil', 'merda', 'otario', 'otário', 'pau', 'pinto', 'porra', 'puta', 'putas', 'puto', 'quenga', 'quengo', 'retardado', 'safado', 'shit', 'shitty', 'viad', 'viado', 'xereca', 'xoxota', 'xvideos', 'xxxvideos', // Novos termos ofensivos 'arrobado', 'vadia', 'vadio', 'vagabunda', 'vagabundo', 'piranha', 'prostituta', 'prostituto', 'putinha', 'putinho', 'viadinho', 'viadinha', 'bocetinha', 'bucetinha', 'cuzinho', 'cuzinha', 'caralhinho', 'caralhinha', 'pauzinho', 'pauzinha', 'pintinho', 'pintinha', 'merdinha', 'merdinho', 'bostinha', 'bostinho', 'fodinha', 'fodinho', 'putinha', 'putinho', // Termos NSFW e conteúdo adulto '🔞', '🍆', '🍑', '🥒', '🥵', 'PORN', 'Pornografia', 'pornografía', 'pornography', 'nude', 'nudes', 'Onlyfans', 'OnlyFans', 'Leaks', 'Hentai', 'Teen Porn', 'E-Girls Porn', 'Latina Nudes', 'xnudes', 'xvideos', 'pornhub', 'xhamster', 'redtube', 'sexy', 'sexy girl', 'sexo', 'sex', // Spam e golpes '$100', '$20 gift', '20$ gift', 'Bilhão de reais', 'Billion Dollars', 'Billion of reais', 'Billion reais', 'buy now', 'cheap', 'click here', 'follow me', 'free gift', 'free nudes', 'gift from steam', 'gifts', 'here', 'Hot', 'HOT', 'prize', 'vote for me', 'withdrew $15647', 'Milhão de reais', 'Million Dollars', 'Million reais', // Outros termos ofensivos 'dirty black', 'negro sujo', 'worm', 'verme', 'trash', 'worthless', 'idiot', 'imbecile', 'shut up', 'cala a boca' ]; // Termos explícitos this.explicitTerms = [ 'porn', 'sex', 'xxx', 'adult', 'nude', 'naked', 'nsfw', '18+', 'pornografia', 'sexo', 'adulto', 'nu', 'nua', 'nudez', 'erótico', 'onlyfans', 'leaks', 'hentai', 'pussy', 'buceta', 'xereca', 'xereka', 'chereca', 'hentai', 'pornhub', 'xhamster', 'redtube', 'sexy', 'sexy girl', 'sexo', 'sex', 'porn', 'pornografia', 'erótico', 'erótica', ]; // Termos ofensivos this.offensiveTerms = [ 'arromb', 'asshole', 'babac', 'bastard', 'bct', 'boceta', 'boquete', 'burro', 'cacete', 'caralh', 'corno', 'corna', 'crlh', 'cu', 'puta' ]; // Termos de golpe this.scamTerms = [ 'hack', 'crack', 'pirata', 'gratis', 'free', 'win', 'premio', 'prêmio', 'ganhou', 'bitcoin', 'crypto', 'investment', 'investimento', 'money' ]; this.spamPatterns = [ /(.)\1{10,}/, // Caracteres repetidos (aumentado para 10+ repetições) /(.){1000,}/, // Textos muito longos (aumentado para 1000+ caracteres) /(.){1,10}\1{5,}/, // Padrões repetitivos /(.){1,5}\1{10,}/, // Caracteres repetidos em sequência /(.){1,3}\1{15,}/ // Caracteres muito repetidos ]; // Mapeamento de substituições comuns this.characterSubstitutions = { 'a': ['4', '@', 'α', 'а'], 'o': ['0', '@', 'о', 'ο'], 'e': ['3', 'ε', 'е'], 'i': ['1', '!', 'і', 'ι'], 's': ['$', '5', 'ѕ'], 't': ['7', 'т'], 'b': ['8', 'в'], 'g': ['9', 'ɡ'], 'l': ['1', '|', 'ł'], 'z': ['2', 'з'] }; // Caracteres cirílicos que podem ser usados para enganar this.cyrillicChars = { 'а': 'a', // cirílico 'а' vs latino 'a' 'е': 'e', // cirílico 'е' vs latino 'e' 'о': 'o', // cirílico 'о' vs latino 'o' 'с': 'c', // cirílico 'с' vs latino 'c' 'р': 'p', // cirílico 'р' vs latino 'p' 'у': 'y', // cirílico 'у' vs latino 'y' 'х': 'x', // cirílico 'х' vs latino 'x' 'і': 'i', // cirílico 'і' vs latino 'i' 'ѕ': 's', // cirílico 'ѕ' vs latino 's' 'т': 't', // cirílico 'т' vs latino 't' 'в': 'b', // cirílico 'в' vs latino 'b' 'ɡ': 'g', // cirílico 'ɡ' vs latino 'g' 'з': 'z', // cirílico 'з' vs latino 'z' 'м': 'm', // cirílico 'м' vs latino 'm' 'н': 'h', // cirílico 'н' vs latino 'h' 'к': 'k', // cirílico 'к' vs latino 'k' 'л': 'l', // cirílico 'л' vs latino 'l' 'д': 'd', // cirílico 'д' vs latino 'd' 'ф': 'f', // cirílico 'ф' vs latino 'f' 'ц': 'c', // cirílico 'ц' vs latino 'c' 'ч': 'ch', // cirílico 'ч' vs latino 'ch' 'ш': 'sh', // cirílico 'ш' vs latino 'sh' 'щ': 'sch', // cirílico 'щ' vs latino 'sch' 'ъ': '', // cirílico 'ъ' (não tem equivalente) 'ь': '', // cirílico 'ь' (não tem equivalente) 'ю': 'yu', // cirílico 'ю' vs latino 'yu' 'я': 'ya' // cirílico 'я' vs latino 'ya' }; // Múltiplos modelos NSFW this.nsfwModels = { default: null, mobilenet: null, inception: null, resnet: null }; // Status de carregamento dos modelos this.modelLoading = false; // URLs dos modelos this.modelUrls = { default: 'https://cdn.jsdelivr.net/npm/nsfwjs@2.4.0/dist/model/', mobilenet: 'https://cdn.jsdelivr.net/npm/@tensorflow-models/mobilenet@2.1.0/dist/model.json', inception: 'https://storage.googleapis.com/tfjs-models/tfjs/inception_v3/2/model.json', resnet: 'https://cdn.jsdelivr.net/npm/@tensorflow-models/toxicity@1.2.2/dist/model.json' }; // APIs externas de moderação this.externalApis = { deepai: 'https://api.deepai.org/api/nsfw-detector', sightengine: 'https://api.sightengine.com/1.0/check.json', moderatecontent: 'https://api.moderatecontent.com/moderate/', imagga: 'https://api.imagga.com/v2/categories/nsfw_beta', cloudmersive: 'https://api.cloudmersive.com/image/nsfw/classify' }; // Inicializa todos os modelos this.loadAllModels(); } async loadAllModels() { if (this.modelLoading) return; this.modelLoading = true; try { console.log('Carregando múltiplos modelos NSFW...'); // Carrega modelo principal do NSFWJS this.nsfwModels.default = await nsfwjs.load(this.modelUrls.default); console.log('Modelo NSFWJS principal carregado'); // Carrega MobileNet para detecção adicional this.nsfwModels.mobilenet = await tf.loadLayersModel(this.modelUrls.mobilenet); console.log('Modelo MobileNet carregado'); // Carrega Inception para classificação avançada this.nsfwModels.inception = await tf.loadLayersModel(this.modelUrls.inception); console.log('Modelo Inception carregado'); // Carrega ResNet para detecção de características this.nsfwModels.resnet = await tf.loadLayersModel(this.modelUrls.resnet); console.log('Modelo ResNet carregado'); } catch (error) { console.error('Erro ao carregar modelos:', error); } this.modelLoading = false; } // Normaliza o texto removendo substituições de caracteres normalizeText(text) { let normalized = text.toLowerCase(); // Substitui caracteres especiais de volta para suas formas normais for (const [normal, substitutes] of Object.entries(this.characterSubstitutions)) { for (const substitute of substitutes) { normalized = normalized.replace(new RegExp(substitute, 'g'), normal); } } return normalized; } // Verifica se o texto contém palavras bloqueadas mesmo com substituições hasBlockedWordsWithSubstitutions(text) { const normalized = this.normalizeText(text); return this.blockedWords.some(word => normalized.includes(word.toLowerCase())); } // Verifica se o arquivo é uma imagem ou vídeo isMediaFile(file) { return file.type.startsWith('image/') || file.type.startsWith('video/'); } // Verifica se o conteúdo é NSFW async checkNSFW(file) { if (!this.isMediaFile(file)) return false; try { console.log('Iniciando verificação NSFW completa para:', file.name); // Verifica o nome do arquivo primeiro const fileName = file.name.toLowerCase(); if (this.blockedWords.some(word => fileName.includes(word.toLowerCase()))) { console.log('Nome do arquivo contém palavras bloqueadas'); return { isNSFW: true, confidence: 1.0, reason: 'Nome do arquivo bloqueado' }; } // Verifica metadados do arquivo if (file.type.includes('image') || file.type.includes('video')) { const metadata = await this.extractMetadata(file); if (metadata.isNSFW) { return { isNSFW: true, confidence: metadata.confidence, reason: 'Metadados suspeitos' }; } } // Garante que os modelos estão carregados if (!this.nsfwModels.default) { await this.loadAllModels(); } let isNSFW = false; let confidence = 0; let modelResults = {}; if (file.type.startsWith('image/')) { const results = await this.checkImageWithAllModels(file); isNSFW = results.isNSFW; confidence = results.confidence; modelResults = results.modelResults; } else if (file.type.startsWith('video/')) { const results = await this.checkVideoWithAllModels(file); isNSFW = results.isNSFW; confidence = results.confidence; modelResults = results.modelResults; } // Verifica APIs externas se necessário if (confidence > 0.1) { // Se houver alguma suspeita const externalResults = await this.checkExternalApis(file); if (externalResults.isNSFW) { isNSFW = true; confidence = Math.max(confidence, externalResults.confidence); } } // Se o conteúdo for NSFW, aplica blur automaticamente if (isNSFW) { const element = document.querySelector(`[data-file-id="${file.name}"]`); if (element) { this.applyBlurAndOverlay(element, 'explicit'); } } return { isNSFW, confidence, modelResults, fileType: file.type.startsWith('image/') ? 'image' : 'video' }; } catch (error) { console.error('Erro na verificação NSFW:', error); return { isNSFW: false, confidence: 0, modelResults: {}, error: error.message }; } } // Extrai metadados do arquivo async extractMetadata(file) { return new Promise((resolve) => { if (file.type.startsWith('image/')) { const img = new Image(); img.onload = () => { // Verifica dimensões suspeitas const isSuspiciousSize = img.width > 2000 || img.height > 2000; // Verifica proporção suspeita const ratio = img.width / img.height; const isSuspiciousRatio = ratio < 0.5 || ratio > 2; resolve({ isNSFW: isSuspiciousSize || isSuspiciousRatio, confidence: (isSuspiciousSize || isSuspiciousRatio) ? 0.3 : 0, width: img.width, height: img.height, ratio: ratio }); }; img.src = URL.createObjectURL(file); } else { resolve({ isNSFW: false, confidence: 0 }); } }); } // Verifica APIs externas async checkExternalApis(file) { try { const formData = new FormData(); formData.append('file', file); // Tenta cada API em sequência for (const [name, url] of Object.entries(this.externalApis)) { try { const response = await fetch(url, { method: 'POST', body: formData }); if (response.ok) { const result = await response.json(); if (result.isNSFW || result.nsfw_score > 0.15) { return { isNSFW: true, confidence: result.nsfw_score || 0.5, api: name }; } } } catch (error) { console.error(`Erro na API ${name}:`, error); } } return { isNSFW: false, confidence: 0 }; } catch (error) { console.error('Erro ao verificar APIs externas:', error); return { isNSFW: false, confidence: 0 }; } } async checkImageWithAllModels(file) { return new Promise(async (resolve) => { const img = new Image(); img.crossOrigin = 'anonymous'; img.onload = async () => { try { console.log('Analisando imagem com múltiplos modelos...'); // Resultados de cada modelo const modelResults = { nsfwjs: null, mobilenet: null, inception: null, resnet: null }; // NSFWJS const nsfwPredictions = await this.nsfwModels.default.classify(img); modelResults.nsfwjs = nsfwPredictions; // MobileNet const mobilenetPredictions = await this.nsfwModels.mobilenet.classify(img); modelResults.mobilenet = mobilenetPredictions; // Inception const inceptionPredictions = await this.nsfwModels.inception.classify(img); modelResults.inception = inceptionPredictions; // ResNet const resnetPredictions = await this.nsfwModels.resnet.classify(img); modelResults.resnet = resnetPredictions; // Análise ponderada dos resultados let nsfwScore = 0; let totalConfidence = 0; // NSFWJS (peso 2) const nsfwjsScore = nsfwPredictions.find(p => p.className === 'Porn' || p.className === 'Hentai'); if (nsfwjsScore) { nsfwScore += nsfwjsScore.probability * 2; totalConfidence += 2; } // MobileNet (peso 1) const mobilenetScore = mobilenetPredictions.find(p => p.className.toLowerCase().includes('explicit')); if (mobilenetScore) { nsfwScore += mobilenetScore.probability; totalConfidence += 1; } // Inception (peso 1.5) const inceptionScore = inceptionPredictions.find(p => p.className.toLowerCase().includes('adult')); if (inceptionScore) { nsfwScore += inceptionScore.probability * 1.5; totalConfidence += 1.5; } // ResNet (peso 1.5) const resnetScore = resnetPredictions.find(p => p.className.toLowerCase().includes('nsfw')); if (resnetScore) { nsfwScore += resnetScore.probability * 1.5; totalConfidence += 1.5; } // Calcula a média ponderada const finalScore = nsfwScore / totalConfidence; const isNSFW = finalScore > 0.15; // Threshold reduzido para 15% console.log('Resultado final NSFW:', { isNSFW, confidence: finalScore, modelResults }); resolve({ isNSFW, confidence: finalScore, modelResults }); } catch (error) { console.error('Erro ao analisar imagem:', error); resolve({ isNSFW: false, confidence: 0, modelResults: {}, error: error.message }); } }; img.onerror = () => { console.error('Erro ao carregar imagem para análise'); resolve({ isNSFW: false, confidence: 0, modelResults: {}, error: 'Erro ao carregar imagem' }); }; img.src = URL.createObjectURL(file); }); } async checkVideoWithAllModels(file) { return new Promise((resolve) => { const video = document.createElement('video'); video.preload = 'metadata'; video.muted = true; video.onloadedmetadata = async () => { try { console.log('Analisando vídeo com múltiplos modelos...'); const canvas = document.createElement('canvas'); const ctx = canvas.getContext('2d'); // Configurações para análise de frames const frameInterval = 1000; // 1 frame por segundo const maxFrames = Math.min(10, Math.floor(video.duration)); // Máximo 10 frames let framesAnalyzed = 0; let totalNSFWScore = 0; // Array para armazenar resultados de cada frame const frameResults = []; // Função para analisar um frame específico const analyzeFrame = async (time) => { video.currentTime = time; await new Promise(resolve => video.onseeked = resolve); canvas.width = video.videoWidth; canvas.height = video.videoHeight; ctx.drawImage(video, 0, 0, canvas.width, canvas.height); // Análise com múltiplos modelos const [nsfwResults, mobilenetResults, inceptionResults] = await Promise.all([ this.nsfwModels.default.classify(canvas), this.nsfwModels.mobilenet.classify(canvas), this.nsfwModels.inception.classify(canvas) ]); // Calcula score NSFW para o frame let frameScore = 0; let totalWeight = 0; // NSFWJS (peso 2) const nsfwScore = nsfwResults.find(p => p.className === 'Porn' || p.className === 'Hentai'); if (nsfwScore) { frameScore += nsfwScore.probability * 2; totalWeight += 2; } // MobileNet (peso 1) const mobilenetScore = mobilenetResults.find(p => p.className.toLowerCase().includes('explicit')); if (mobilenetScore) { frameScore += mobilenetScore.probability; totalWeight += 1; } // Inception (peso 1.5) const inceptionScore = inceptionResults.find(p => p.className.toLowerCase().includes('adult')); if (inceptionScore) { frameScore += inceptionScore.probability * 1.5; totalWeight += 1.5; } return { time, score: frameScore / totalWeight, results: { nsfwjs: nsfwResults, mobilenet: mobilenetResults, inception: inceptionResults } }; }; // Analisa frames em intervalos regulares for (let i = 0; i < maxFrames; i++) { const time = i * (video.duration / maxFrames); const frameResult = await analyzeFrame(time); frameResults.push(frameResult); totalNSFWScore += frameResult.score; framesAnalyzed++; } // Calcula média final const averageScore = totalNSFWScore / framesAnalyzed; const isNSFW = averageScore > 0.15; // Threshold reduzido para 15% console.log('Resultado final vídeo:', { isNSFW, confidence: averageScore, frameResults }); resolve({ isNSFW, confidence: averageScore, modelResults: frameResults }); } catch (error) { console.error('Erro ao analisar vídeo:', error); resolve({ isNSFW: false, confidence: 0, modelResults: {}, error: error.message }); } }; video.onerror = () => { console.error('Erro ao carregar vídeo para análise'); resolve({ isNSFW: false, confidence: 0, modelResults: {}, error: 'Erro ao carregar vídeo' }); }; video.src = URL.createObjectURL(file); }); } // Verifica se uma URL contém caracteres cirílicos hasCyrillicChars(url) { const cyrillicPattern = /[\u0400-\u04FF]/; return cyrillicPattern.test(url); } // Normaliza uma URL removendo caracteres cirílicos normalizeUrl(url) { let normalized = url.toLowerCase(); for (const [cyrillic, latin] of Object.entries(this.cyrillicChars)) { normalized = normalized.replace(new RegExp(cyrillic, 'g'), latin); } return normalized; } // Verifica se uma URL é suspeita isSuspiciousUrl(url) { // Verifica se contém caracteres cirílicos if (this.hasCyrillicChars(url)) { return true; } // Verifica se a URL normalizada contém palavras bloqueadas const normalizedUrl = this.normalizeUrl(url); return this.blockedWords.some(word => normalizedUrl.includes(word.toLowerCase())); } // Verifica se é spam ou contém palavras bloqueadas isSpam(text, file) { if (!text) return { isSpam: false, contentType: null }; // Normaliza o texto para comparação const normalizedText = this.normalizeText(text.toLowerCase()); // Sistema de pontuação para determinar o tipo de conteúdo let spamScore = 0; let offensiveScore = 0; let explicitScore = 0; let scamScore = 0; // Verifica palavras bloqueadas com sistema de pontuação for (const word of this.blockedWords) { const regex = new RegExp(`\\b${word}\\b`, 'i'); if (regex.test(normalizedText)) { if (this.explicitTerms.includes(word)) explicitScore += 2; else if (this.offensiveTerms.includes(word)) offensiveScore += 2; else if (this.scamTerms.includes(word)) scamScore += 2; else spamScore += 1; } } // Verifica padrões de spam if (/(.)\\1{4,}/.test(normalizedText)) spamScore += 2; // Caracteres repetidos if (text.length > 500) spamScore += 1; // Mensagens muito longas if ((text.match(/[A-Z]/g) || []).length > text.length * 0.7) spamScore += 2; // Muitas maiúsculas // Verifica URLs suspeitas const urlRegex = /https?:\/\/[^\s]+/g; const urls = text.match(urlRegex) || []; for (const url of urls) { if (this.isSuspiciousUrl(url)) { scamScore += 3; } } // Verifica emojis impróprios if (this.hasInappropriateEmojis(text)) { explicitScore += 2; } // Determina o tipo de conteúdo baseado nos scores let contentType = null; let isSpam = false; if (explicitScore >= 2) { contentType = 'explicit'; isSpam = true; } else if (scamScore >= 3) { contentType = 'scam'; isSpam = true; } else if (offensiveScore >= 2) { contentType = 'offensive'; isSpam = true; } else if (spamScore >= 3) { contentType = 'spam'; isSpam = true; } return { isSpam, contentType, scores: { explicit: explicitScore, scam: scamScore, offensive: offensiveScore, spam: spamScore } }; } // Método para verificar emojis impróprios hasInappropriateEmojis(text) { const inappropriateEmojis = ['🔞', '🍆', '🍑', '🥒', '🥵', '💦', '👅', '👙', '👄', '💋']; return inappropriateEmojis.some(emoji => text.includes(emoji)); } // Método para verificar conteúdo explícito isExplicitContent(text) { const explicitTerms = ['porn', 'sex', 'nude', 'nudes', 'onlyfans', 'leaks', 'hentai']; return explicitTerms.some(term => text.toLowerCase().includes(term)); } // Mostra o diálogo de aviso async showWarningDialog(file, contentType = 'spam') { return new Promise((resolve) => { const dialog = document.createElement('div'); dialog.className = 'content-moderation-warning'; let title, message, icon; switch(contentType) { case 'explicit': title = '🚫 Conteúdo Explícito Detectado'; message = 'Este conteúdo pode conter material explícito ou inadequado.'; icon = ` `; break; case 'spam': title = '🚫 Possível Spam/Golpe Detectado'; message = 'Este conteúdo pode ser spam ou tentativa de golpe.'; icon = ` `; break; case 'offensive': title = '🚫 Conteúdo Ofensivo Detectado'; message = 'Este conteúdo contém linguagem ofensiva.'; icon = ` `; break; } dialog.innerHTML = `
${icon}
${title}
${message}
${file.type.startsWith('image/') ? `Preview` : file.type.startsWith('video/') ? `` : `
${file.name}
` }

⚠️ Este conteúdo foi identificado como potencialmente perigoso.

`; document.body.appendChild(dialog); const showButton = dialog.querySelector('.warning-button.show'); const closeButton = dialog.querySelector('.warning-button.close'); const blockButton = dialog.querySelector('.warning-button.block'); const preview = dialog.querySelector('.warning-preview'); showButton.onclick = () => { preview.classList.remove('blurred'); showButton.style.display = 'none'; }; closeButton.onclick = () => { document.body.removeChild(dialog); resolve(false); }; blockButton.onclick = () => { localStorage.setItem('blockDangerousContent', 'true'); document.body.removeChild(dialog); resolve(false); }; // Fecha o diálogo com ESC document.addEventListener('keydown', function escHandler(e) { if (e.key === 'Escape') { document.body.removeChild(dialog); document.removeEventListener('keydown', escHandler); resolve(false); } }); }); } // Aplica blur e overlay em conteúdo sensível applyBlurAndOverlay(element, contentType) { element.classList.add('blurred-content'); const overlay = document.createElement('div'); overlay.className = 'warning-overlay'; let icon, text; switch(contentType) { case 'explicit': icon = ` `; text = 'Conteúdo Explícito Detectado'; break; case 'spam': icon = ` `; text = 'Possível Spam/Golpe Detectado'; break; case 'offensive': icon = ` `; text = 'Conteúdo Ofensivo Detectado'; break; } overlay.innerHTML = `
${icon}
${text}
`; element.appendChild(overlay); } // Processa notificações push processPushNotification(notification) { try { const text = notification.body || ''; const title = notification.title || ''; // Verifica título e corpo da notificação const titleCheck = this.isSpam(title); const bodyCheck = this.isSpam(text); // Se qualquer parte da notificação for imprópria if (titleCheck.isSpam || bodyCheck.isSpam) { const contentType = titleCheck.contentType || bodyCheck.contentType; const scores = { title: titleCheck.scores, body: bodyCheck.scores }; // Cria uma notificação segura const safeNotification = { title: this.getSafeNotificationTitle(contentType), body: this.getSafeNotificationBody(contentType), icon: this.getWarningIcon(contentType), tag: `blocked-${Date.now()}`, data: { originalType: contentType, scores: scores, timestamp: Date.now() }, requireInteraction: true, silent: false, vibrate: [200, 100, 200], actions: [ { action: 'view', title: 'Ver Detalhes' }, { action: 'close', title: 'Fechar' } ] }; // Registra a notificação bloqueada para análise this.logBlockedNotification({ originalTitle: title, originalBody: text, contentType: contentType, scores: scores, timestamp: Date.now() }); return safeNotification; } // Se a notificação for segura, adiciona metadados return { ...notification, data: { ...notification.data, safeContent: true, timestamp: Date.now() } }; } catch (error) { console.error('Erro ao processar notificação:', error); return null; } } // Obtém título seguro para notificação getSafeNotificationTitle(contentType) { switch(contentType) { case 'explicit': return '🚫 Conteúdo Explícito Bloqueado'; case 'spam': return '🚫 Spam Detectado'; case 'offensive': return '🚫 Conteúdo Ofensivo Bloqueado'; case 'scam': return '🚫 Possível Golpe Detectado'; default: return '🚫 Conteúdo Bloqueado'; } } // Obtém corpo seguro para notificação getSafeNotificationBody(contentType) { switch(contentType) { case 'explicit': return 'Uma notificação com conteúdo explícito foi bloqueada para sua segurança.'; case 'spam': return 'Uma notificação de spam foi bloqueada.'; case 'offensive': return 'Uma notificação com conteúdo ofensivo foi bloqueada.'; case 'scam': return 'Uma notificação suspeita foi bloqueada para sua segurança.'; default: return 'Uma notificação imprópria foi bloqueada.'; } } // Obtém ícone de aviso apropriado getWarningIcon(contentType) { switch(contentType) { case 'explicit': return '/images/warning-explicit.png'; case 'spam': return '/images/warning-spam.png'; case 'offensive': return '/images/warning-offensive.png'; case 'scam': return '/images/warning-scam.png'; default: return '/images/warning-default.png'; } } // Registra notificação bloqueada para análise logBlockedNotification(data) { try { const blockedNotifications = JSON.parse(localStorage.getItem('blockedNotifications') || '[]'); blockedNotifications.push(data); // Mantém apenas as últimas 100 notificações if (blockedNotifications.length > 100) { blockedNotifications.shift(); } localStorage.setItem('blockedNotifications', JSON.stringify(blockedNotifications)); } catch (error) { console.error('Erro ao registrar notificação bloqueada:', error); } } // Processa um arquivo antes de enviar async processFile(file) { try { // Verifica o arquivo no servidor proxy const formData = new FormData(); formData.append('file', file); const response = await fetch('http://localhost:3001/check-content', { method: 'POST', body: formData }); const result = await response.json(); if (result.blocked) { const shouldView = await this.showWarningDialog(file, 'explicit'); if (!shouldView) { throw new Error(`Arquivo bloqueado: ${result.reason}`); } } return file; } catch (error) { console.error('Erro ao processar arquivo:', error); throw error; } } async checkUrl(url) { try { const response = await fetch('http://localhost:3001/check-url', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ url }) }); const result = await response.json(); return result.blocked; } catch (error) { console.error('Erro ao verificar URL:', error); return false; } } } // Adiciona estilos para o diálogo de aviso const style = document.createElement('style'); style.textContent = ` .content-moderation-warning { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0, 0, 0, 0.95); display: flex; justify-content: center; align-items: center; z-index: 9999; backdrop-filter: blur(10px); animation: fadeIn 0.3s ease; } @keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } } .warning-content { background: #1a1a1a; padding: 30px; border-radius: 16px; max-width: 500px; text-align: center; color: white; box-shadow: 0 0 30px rgba(0, 0, 0, 0.5); display: flex; flex-direction: column; align-items: center; gap: 20px; animation: slideUp 0.3s ease; border: 1px solid rgba(255, 255, 255, 0.1); position: relative; } @keyframes slideUp { from { transform: translateY(20px); opacity: 0; } to { transform: translateY(0); opacity: 1; } } .warning-icon { width: 80px; height: 80px; display: flex; justify-content: center; align-items: center; margin-bottom: 10px; animation: pulse 2s infinite; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); z-index: 2; } @keyframes pulse { 0% { transform: translate(-50%, -50%) scale(1); } 50% { transform: translate(-50%, -50%) scale(1.05); } 100% { transform: translate(-50%, -50%) scale(1); } } .warning-icon svg { filter: drop-shadow(0 0 15px rgba(255, 255, 255, 0.3)); width: 64px; height: 64px; } .warning-text { text-align: center; margin-bottom: 20px; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); z-index: 2; width: 100%; padding: 0 20px; } .warning-text p:first-child { font-size: 28px; font-weight: bold; margin-bottom: 10px; background: linear-gradient(45deg, #ff4444, #ff0000); -webkit-background-clip: text; -webkit-text-fill-color: transparent; text-shadow: 0 2px 4px rgba(0, 0, 0, 0.3); } .warning-text p:last-child { font-size: 18px; color: #e3e3e3; line-height: 1.5; } .media-preview { position: relative; width: 100%; max-height: 300px; border-radius: 12px; overflow: hidden; margin-top: 10px; box-shadow: 0 0 20px rgba(0, 0, 0, 0.3); } .media-preview img, .media-preview video { width: 100%; height: 100%; object-fit: cover; transition: filter 0.3s ease; } .blurred img, .blurred video { filter: blur(25px) brightness(0.7); } .warning-buttons { display: flex; justify-content: center; gap: 20px; margin-top: 20px; width: 100%; position: absolute; bottom: 30px; left: 0; z-index: 2; } .warning-buttons button { padding: 14px 28px; border: none; border-radius: 8px; cursor: pointer; font-size: 16px; font-weight: bold; transition: all 0.3s ease; flex: 1; max-width: 200px; text-transform: uppercase; letter-spacing: 1px; } .btn-cancel { background: linear-gradient(45deg, #ff4444, #ff0000); color: white; box-shadow: 0 4px 15px rgba(255, 0, 0, 0.3); } .btn-cancel:hover { background: linear-gradient(45deg, #ff0000, #cc0000); transform: translateY(-2px); box-shadow: 0 6px 20px rgba(255, 0, 0, 0.4); } .btn-view { background: linear-gradient(45deg, #4CAF50, #45a049); color: white; box-shadow: 0 4px 15px rgba(76, 175, 80, 0.3); } .btn-view:hover { background: linear-gradient(45deg, #45a049, #388e3c); transform: translateY(-2px); box-shadow: 0 6px 20px rgba(76, 175, 80, 0.4); } /* Estilo para notificações de golpe */ .scam-warning { background: linear-gradient(45deg, #ff6b6b, #ff0000); color: white; padding: 15px; border-radius: 8px; margin: 10px 0; font-weight: bold; text-align: center; animation: shake 0.5s ease; } @keyframes shake { 0%, 100% { transform: translateX(0); } 25% { transform: translateX(-5px); } 75% { transform: translateX(5px); } } `; document.head.appendChild(style); export default ContentModeration;