// 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.initNSFWModel(); } async initNSFWModel() { try { // Carrega o modelo NSFW do TensorFlow.js this.nsfwModel = await tf.loadGraphModel('https://d1zv2aa70wpiur.cloudfront.net/tfjs_models/tfjs_nsfw_mobilenet/model.json'); } catch (error) { console.error('Erro ao carregar modelo NSFW:', error); } } // 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 { // Verifica primeiro pelo nome do arquivo const fileName = file.name.toLowerCase(); if (this.blockedWords.some(word => fileName.includes(word.toLowerCase()))) { return true; } // Se for uma imagem ou vídeo, verifica o conteúdo 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) return false; try { const img = await createImageBitmap(file); const tensor = tf.browser.fromPixels(img) .resizeBilinear([224, 224]) .expandDims() .toFloat() .div(255.0); const predictions = await this.nsfwModel.predict(tensor).data(); tensor.dispose(); // Verifica se o conteúdo é NSFW const nsfwScore = predictions[1]; // Índice 1 é para conteúdo NSFW return nsfwScore > 0.5; // Threshold de 50% } catch (error) { console.error('Erro ao verificar imagem NSFW:', error); return false; } } async checkVideoNSFW(file) { if (!this.nsfwModel) return false; try { const video = document.createElement('video'); video.src = URL.createObjectURL(file); return new Promise((resolve) => { video.onloadeddata = async () => { // Captura frames do vídeo para análise const canvas = document.createElement('canvas'); canvas.width = video.videoWidth; canvas.height = video.videoHeight; const ctx = canvas.getContext('2d'); // Analisa alguns frames do vídeo const frameCount = 5; const interval = video.duration / frameCount; let nsfwFrames = 0; for (let i = 0; i < frameCount; i++) { video.currentTime = i * interval; await new Promise(r => video.onseeked = r); ctx.drawImage(video, 0, 0); const tensor = tf.browser.fromPixels(canvas) .resizeBilinear([224, 224]) .expandDims() .toFloat() .div(255.0); const predictions = await this.nsfwModel.predict(tensor).data(); tensor.dispose(); if (predictions[1] > 0.5) { nsfwFrames++; } } URL.revokeObjectURL(video.src); resolve(nsfwFrames > frameCount / 2); // Se mais da metade dos frames for NSFW }; }); } catch (error) { console.error('Erro ao verificar vídeo NSFW:', error); return 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.