// 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' ]; 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' }; // Inicializa o modelo NSFW this.nsfwModel = null; this.nsfwModelLoading = false; this.loadNSFWModel(); } async loadNSFWModel() { if (this.nsfwModel || this.nsfwModelLoading) return; this.nsfwModelLoading = true; try { this.nsfwModel = await nsfwjs.load(); console.log('Modelo NSFW carregado!'); } catch (error) { console.error('Erro ao carregar modelo NSFW:', error); } this.nsfwModelLoading = 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 { const fileName = file.name.toLowerCase(); if (this.blockedWords.some(word => fileName.includes(word.toLowerCase()))) { return true; } if (file.type.startsWith('image/')) { return await this.checkImageNSFW(file); } else if (file.type.startsWith('video/')) { return await this.checkVideoNSFW(file); } return false; } catch (error) { console.error('Erro ao verificar NSFW:', error); return false; } } async checkImageNSFW(file) { if (!this.nsfwModel) await this.loadNSFWModel(); return new Promise((resolve) => { const img = new window.Image(); img.onload = async () => { const predictions = await this.nsfwModel.classify(img); const isNSFW = predictions.some(p => (p.className === 'Porn' || p.className === 'Hentai' || p.className === 'Sexy') && p.probability > 0.7 ); resolve(isNSFW); }; img.onerror = () => resolve(false); img.src = URL.createObjectURL(file); }); } async checkVideoNSFW(file) { if (!this.nsfwModel) await this.loadNSFWModel(); // Extrai frames do vídeo e analisa cada um return new Promise((resolve) => { const video = document.createElement('video'); video.preload = 'auto'; video.src = URL.createObjectURL(file); video.muted = true; video.currentTime = 0; video.onloadeddata = async () => { const canvas = document.createElement('canvas'); canvas.width = video.videoWidth; canvas.height = video.videoHeight; const ctx = canvas.getContext('2d'); let nsfwDetected = false; let framesChecked = 0; const totalFrames = 5; const duration = video.duration; for (let i = 1; i <= totalFrames; i++) { video.currentTime = (duration * i) / (totalFrames + 1); await new Promise(r => video.onseeked = r); ctx.drawImage(video, 0, 0, canvas.width, canvas.height); const img = new window.Image(); img.src = canvas.toDataURL(); await new Promise(r => img.onload = r); const predictions = await this.nsfwModel.classify(img); if (predictions.some(p => (p.className === 'Porn' || p.className === 'Hentai' || p.className === 'Sexy') && p.probability > 0.7 )) { nsfwDetected = true; break; } framesChecked++; } resolve(nsfwDetected); }; video.onerror = () => resolve(false); }); } // 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) { // Verifica se o texto contém palavras bloqueadas const hasBlockedWords = this.blockedWords.some(word => { const regex = new RegExp(`\\b${word}\\b`, 'i'); return regex.test(text); }); // Verifica se o texto contém palavras bloqueadas com substituições const hasBlockedWordsWithSubs = this.hasBlockedWordsWithSubstitutions(text); // Verifica se o texto contém URLs suspeitas const hasSuspiciousUrls = this.isSuspiciousUrl(text); // Verifica se o texto contém caracteres cirílicos const hasCyrillicChars = this.hasCyrillicChars(text); // Verifica se o texto contém emojis impróprios const hasInappropriateEmojis = this.hasInappropriateEmojis(text); // Verifica se o texto contém padrões de spam const hasSpamPatterns = this.spamPatterns.some(pattern => { if (pattern.type === 'repeated') { return pattern.regex.test(text); } else if (pattern.type === 'length') { return text.length > pattern.threshold; } else if (pattern.type === 'repetitive') { return pattern.regex.test(text); } return false; }); // Determina o tipo de conteúdo impróprio let contentType = 'spam'; if (hasBlockedWords || hasBlockedWordsWithSubs) { contentType = 'profanity'; } if (hasInappropriateEmojis || this.isExplicitContent(text)) { contentType = 'explicit'; } if (hasSuspiciousUrls || hasCyrillicChars) { contentType = 'scam'; } // Retorna o resultado com o tipo de conteúdo return { isSpam: hasBlockedWords || hasBlockedWordsWithSubs || hasSuspiciousUrls || hasCyrillicChars || hasInappropriateEmojis || hasSpamPatterns, contentType: contentType }; } // 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) { const text = notification.body || ''; // Verifica se o texto contém conteúdo ofensivo const spamCheck = this.isSpam(text); if (spamCheck.isSpam) { try { notification.title = 'Aviso de Moderação'; notification.icon = '/images/warning-icon.png'; // Mensagens específicas para cada tipo de conteúdo switch(spamCheck.contentType) { case 'explicit': notification.body = '🚫 Conteúdo Explícito Bloqueado'; break; case 'spam': notification.body = '🚫 Possível Spam/Golpe Detectado'; break; case 'offensive': notification.body = '🚫 Conteúdo Ofensivo Detectado'; break; case 'scam': notification.body = '🚫 Possível Golpe Detectado'; break; default: notification.body = '🚫 Conteúdo Bloqueado'; } // Adiciona um timestamp para evitar duplicatas notification.tag = `blocked-${Date.now()}`; notification.options = { ...notification.options, requireInteraction: true, vibrate: [200, 100, 200], actions: [ { action: 'close', title: 'Fechar' } ] }; // Se por algum motivo não for possível definir o body ou o ícone, retorna null para ocultar a notificação if (!notification.body || !notification.icon) { return null; } } catch (e) { // Em caso de erro, oculta a notificação return null; } } return notification; } // 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;