// 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 = `
⚠️ Este conteúdo foi identificado como potencialmente perigoso.