PairDrop/public/scripts/content-moderation.js

1016 lines
43 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 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.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()))) {
return this._handleExplicitContent(file, 'Nome do arquivo bloqueado', 'explicit');
}
// Processa cada frame do conteúdo
const isExplicit = await this.processMediaFrames(file);
if (isExplicit) {
return this._handleExplicitContent(file, 'Conteúdo explícito detectado', 'explicit');
}
return { isNSFW: false };
} catch (error) {
console.error('Erro na verificação NSFW:', error);
return { isNSFW: false };
}
}
async processMediaFrames(file) {
return new Promise(async (resolve) => {
const mediaElement = document.createElement(file.type.startsWith('image/') ? 'img' : 'video');
mediaElement.src = URL.createObjectURL(file);
mediaElement.onload = async () => {
const canvas = document.createElement('canvas');
canvas.width = mediaElement.naturalWidth || mediaElement.videoWidth;
canvas.height = mediaElement.naturalHeight || mediaElement.videoHeight;
const ctx = canvas.getContext('2d');
ctx.drawImage(mediaElement, 0, 0);
// Verifica cada frame com os modelos NSFW
const result = await this.nsfwModels.default.classify(canvas);
resolve(result.some(p => p.className === 'Porn' && p.probability > 0.85));
};
if (file.type.startsWith('video/')) {
mediaElement.addEventListener('seeked', async () => {
const canvas = document.createElement('canvas');
canvas.width = mediaElement.videoWidth;
canvas.height = mediaElement.videoHeight;
const ctx = canvas.getContext('2d');
ctx.drawImage(mediaElement, 0, 0);
const result = await this.nsfwModels.default.classify(canvas);
if (result.some(p => p.className === 'Porn' && p.probability > 0.85)) {
resolve(true);
}
});
// Verifica frames a cada 1 segundo
mediaElement.currentTime = 0;
const checkFrames = setInterval(() => {
if (mediaElement.currentTime >= mediaElement.duration) {
clearInterval(checkFrames);
resolve(false);
}
mediaElement.currentTime += 1;
}, 1000);
}
});
}
_handleExplicitContent(file, reason, contentType) {
const blurredMedia = this.createBlurredPreview(file);
return {
isNSFW: true,
reason,
contentType,
blurredMedia
};
}
createBlurredPreview(file) {
const media = document.createElement(file.type.startsWith('image/') ? 'img' : 'video');
media.src = URL.createObjectURL(file);
media.style.filter = 'blur(20px)';
media.className = 'blurred-preview';
return media;
}
async showFrameWarningDialog(file, blurredPreview, contentType = 'explicit') {
return new Promise((resolve) => {
const dialog = document.createElement('div');
dialog.className = 'frame-warning-dialog';
const warningIcons = {
explicit: `<svg xmlns="http://www.w3.org/2000/svg" height="64" viewBox="0 -960 960 960" width="64" fill="#e3e3e3">
<path d="M764-84 624-222q-35 11-71 16.5t-73 5.5q-134 0-245-72T61-462q-5-9-7.5-18.5T51-500q0-10 2.5-19.5T61-538q22-39 47-76t58-66l-83-84q-11-11-11-27.5T84-820q11-11 28-11t28 11l680 680q11 11 11.5 27.5T820-84q-11 11-28 11t-28-11ZM480-320q11 0 21-1t20-4L305-541q-3 10-4 20t-1 21q0 75 52.5 127.5T480-320Zm0-480q134 0 245.5 72.5T900-537q5 8 7.5 17.5T910-500q0 10-2 19.5t-7 17.5q-19 37-42.5 70T806-331q-14 14-33 13t-33-15l-80-80q-7-7-9-16.5t1-19.5q4-13 6-25t2-26q0-75-52.5-127.5T480-680q-14 0-26 2t-25 6q-10 3-20 1t-17-9l-33-33q-19-19-12.5-44t31.5-32q25-5 50.5-8t51.5-3Zm79 226q11 13 18.5 28.5T587-513q1 8-6 11t-13-3l-82-82q-6-6-2.5-13t11.5-7q19 2 35 10.5t29 22.5Z"/></svg>`,
offensive: `<svg xmlns="http://www.w3.org/2000/svg" height="64" viewBox="0 -960 960 960" width="64" fill="#e3e3e3">
<path d="M480-280q17 0 28.5-11.5T520-320q0-17-11.5-28.5T480-360q-17 0-28.5 11.5T440-320q0 17 11.5 28.5T480-280Zm0-160q17 0 28.5-11.5T520-480v-160q0-17-11.5-28.5T480-680q-17 0-28.5 11.5T440-640v160q0 17 11.5 28.5T480-440ZM363-120q-16 0-30.5-6T307-143L143-307q-11-11-17-25.5t-6-30.5v-234q0-16 6-30.5t17-25.5l164-164q11-11 25.5-17t30.5-6h234q16 0 30.5 6t25.5 17l164 164q11 11 17 25.5t6 30.5v234q0 16-6 30.5T817-307L653-143q-11 11-25.5 17t-30.5 6H363Z"/></svg>`,
spam: `<svg xmlns="http://www.w3.org/2000/svg" height="64" viewBox="0 -960 960 960" width="64" fill="#e3e3e3">
<path d="M109-120q-11 0-20-5.5T75-140q-5-9-5.5-19.5T75-180l370-640q6-10 15.5-15t19.5-5q10 0 19.5 5t15.5 15l370 640q6 10 5.5 20.5T885-140q-5 9-14 14.5t-20 5.5H109Zm371-120q17 0 28.5-11.5T520-280q0-17-11.5-28.5T480-320q-17 0-28.5 11.5T440-280q0 17 11.5 28.5T480-240Zm0-120q17 0 28.5-11.5T520-400v-120q0-17-11.5-28.5T480-560q-17 0-28.5 11.5T440-520v120q0 17 11.5 28.5T480-360Z"/></svg>`
};
const warningTitles = {
explicit: '🚫 Conteúdo Explícito Detectado',
offensive: '🚫 Conteúdo Ofensivo Detectado',
spam: '🚫 Possível Spam/Golpe Detectado'
};
dialog.innerHTML = `
<div class="warning-content">
<div class="warning-icon">
${warningIcons[contentType]}
</div>
<h2>${warningTitles[contentType]}</h2>
<p>Este conteúdo pode conter material impróprio.</p>
<div class="media-container">
${blurredPreview.outerHTML}
<div class="warning-overlay">
<button class="unblur-btn">👁️ Mostrar Conteúdo</button>
</div>
</div>
<div class="button-group">
<button class="accept">Aceitar e Visualizar</button>
<button class="block">Bloquear Permanentemente</button>
<button class="cancel">Cancelar Envio</button>
</div>
</div>
`;
const unblurBtn = dialog.querySelector('.unblur-btn');
const media = dialog.querySelector('.blurred-preview');
unblurBtn.addEventListener('click', () => {
media.style.filter = 'none';
unblurBtn.style.display = 'none';
});
dialog.querySelector('.accept').addEventListener('click', () => {
document.body.removeChild(dialog);
resolve(true);
});
dialog.querySelector('.block').addEventListener('click', () => {
localStorage.setItem('blockExplicitContent', 'true');
document.body.removeChild(dialog);
resolve(false);
});
dialog.querySelector('.cancel').addEventListener('click', () => {
document.body.removeChild(dialog);
resolve(false);
});
document.body.appendChild(dialog);
});
}
// 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;
// Log inicial
console.log('Verificando mensagem:', text);
// 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;
console.log('Palavra explícita detectada:', word);
}
else if (this.offensiveTerms.includes(word)) {
offensiveScore += 2;
console.log('Palavra ofensiva detectada:', word);
}
else if (this.scamTerms.includes(word)) {
scamScore += 2;
console.log('Termo de golpe detectado:', word);
}
else {
spamScore += 1;
console.log('Palavra bloqueada detectada:', word);
}
}
}
// Verifica padrões de spam
if (/(.)\1{4,}/.test(normalizedText)) {
spamScore += 2;
console.log('Caracteres repetidos detectados');
}
if (text.length > 500) {
spamScore += 1;
console.log('Mensagem muito longa detectada');
}
if ((text.match(/[A-Z]/g) || []).length > text.length * 0.7) {
spamScore += 2;
console.log('Muitas maiúsculas detectadas');
}
// Verifica URLs suspeitas
const urlRegex = /https?:\/\/[^\s]+/g;
const urls = text.match(urlRegex) || [];
for (const url of urls) {
if (this.isSuspiciousUrl(url)) {
scamScore += 3;
console.log('URL suspeita detectada:', url);
}
}
// Verifica emojis impróprios
if (this.hasInappropriateEmojis(text)) {
explicitScore += 2;
console.log('Emojis impróprios detectados');
}
// Log dos scores
console.log('Scores finais:', {
spamScore,
offensiveScore,
explicitScore,
scamScore
});
// 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 = `<svg xmlns="http://www.w3.org/2000/svg" height="64" viewBox="0 -960 960 960" width="64" fill="#ff4444">
<path d="M764-84 624-222q-35 11-71 16.5t-73 5.5q-134 0-245-72T61-462q-5-9-7.5-18.5T51-500q0-10 2.5-19.5T61-538q22-39 47-76t58-66l-83-84q-11-11-11-27.5T84-820q11-11 28-11t28 11l680 680q11 11 11.5 27.5T820-84q-11 11-28 11t-28-11ZM480-320q11 0 21-1t20-4L305-541q-3 10-4 20t-1 21q0 75 52.5 127.5T480-320Zm0-480q134 0 245.5 72.5T900-537q5 8 7.5 17.5T910-500q0 10-2 19.5t-7 17.5q-19 37-42.5 70T806-331q-14 14-33 13t-33-15l-80-80q-7-7-9-16.5t1-19.5q4-13 6-25t2-26q0-75-52.5-127.5T480-680q-14 0-26 2t-25 6q-10 3-20 1t-17-9l-33-33q-19-19-12.5-44t31.5-32q25-5 50.5-8t51.5-3Zm79 226q11 13 18.5 28.5T587-513q1 8-6 11t-13-3l-82-82q-6-6-2.5-13t11.5-7q19 2 35 10.5t29 22.5Z"/>
</svg>`;
break;
case 'spam':
title = '🚫 Possível Spam/Golpe Detectado';
message = 'Este conteúdo pode ser spam ou tentativa de golpe.';
icon = `<svg xmlns="http://www.w3.org/2000/svg" height="64" viewBox="0 -960 960 960" width="64" fill="#ff4444">
<path d="M109-120q-11 0-20-5.5T75-140q-5-9-5.5-19.5T75-180l370-640q6-10 15.5-15t19.5-5q10 0 19.5 5t15.5 15l370 640q6 10 5.5 20.5T885-140q-5 9-14 14.5t-20 5.5H109Zm371-120q17 0 28.5-11.5T520-280q0-17-11.5-28.5T480-320q-17 0-28.5 11.5T440-280q0 17 11.5 28.5T480-240Zm0-120q17 0 28.5-11.5T520-400v-120q0-17-11.5-28.5T480-560q-17 0-28.5 11.5T440-520v120q0 17 11.5 28.5T480-360Z"/>
</svg>`;
break;
case 'offensive':
title = '🚫 Conteúdo Ofensivo Detectado';
message = 'Este conteúdo contém linguagem ofensiva.';
icon = `<svg xmlns="http://www.w3.org/2000/svg" height="64" viewBox="0 -960 960 960" width="64" fill="#ff4444">
<path d="M480-280q17 0 28.5-11.5T520-320q0-17-11.5-28.5T480-360q-17 0-28.5 11.5T440-320q0 17 11.5 28.5T480-280Zm0-160q17 0 28.5-11.5T520-480v-160q0-17-11.5-28.5T480-680q-17 0-28.5 11.5T440-640v160q0 17 11.5 28.5T480-440ZM363-120q-16 0-30.5-6T307-143L143-307q-11-11-17-25.5t-6-30.5v-234q0-16 6-30.5t17-25.5l164-164q11-11 25.5-17t30.5-6h234q16 0 30.5 6t25.5 17l164 164q11 11 17 25.5t6 30.5v234q0 16-6 30.5T817-307L653-143q-11 11-25.5 17t-30.5 6H363Z"/>
</svg>`;
break;
}
dialog.innerHTML = `
<div class="warning-content">
<div class="warning-icon" data-type="${contentType}">${icon}</div>
<div class="warning-title" data-type="${contentType}">${title}</div>
<div class="warning-message">${message}</div>
<div class="warning-preview blurred">
${file.type.startsWith('image/') ?
`<img src="${URL.createObjectURL(file)}" alt="Preview">` :
file.type.startsWith('video/') ?
`<video src="${URL.createObjectURL(file)}" controls></video>` :
`<div class="file-info">${file.name}</div>`
}
</div>
<div class="warning-notice" data-type="${contentType}">
<p>⚠️ Este conteúdo foi identificado como potencialmente perigoso.</p>
</div>
<div class="warning-buttons">
<button class="warning-button show">Mostrar Conteúdo</button>
<button class="warning-button close">Fechar</button>
<button class="warning-button block">Sempre Recusar Conteúdos Perigosos</button>
</div>
</div>
`;
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 = `<svg xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 -960 960 960" width="48" fill="#ffdd00">
<path d="M764-84 624-222q-35 11-71 16.5t-73 5.5q-134 0-245-72T61-462q-5-9-7.5-18.5T51-500q0-10 2.5-19.5T61-538q22-39 47-76t58-66l-83-84q-11-11-11-27.5T84-820q11-11 28-11t28 11l680 680q11 11 11.5 27.5T820-84q-11 11-28 11t-28-11ZM480-320q11 0 21-1t20-4L305-541q-3 10-4 20t-1 21q0 75 52.5 127.5T480-320Zm0-480q134 0 245.5 72.5T900-537q5 8 7.5 17.5T910-500q0 10-2 19.5t-7 17.5q-19 37-42.5 70T806-331q-14 14-33 13t-33-15l-80-80q-7-7-9-16.5t1-19.5q4-13 6-25t2-26q0-75-52.5-127.5T480-680q-14 0-26 2t-25 6q-10 3-20 1t-17-9l-33-33q-19-19-12.5-44t31.5-32q25-5 50.5-8t51.5-3Zm79 226q11 13 18.5 28.5T587-513q1 8-6 11t-13-3l-82-82q-6-6-2.5-13t11.5-7q19 2 35 10.5t29 22.5Z"/>
</svg>`;
text = 'Conteúdo Explícito Detectado';
break;
case 'spam':
icon = `<svg xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 -960 960 960" width="48" fill="#ff0000">
<path d="M109-120q-11 0-20-5.5T75-140q-5-9-5.5-19.5T75-180l370-640q6-10 15.5-15t19.5-5q10 0 19.5 5t15.5 15l370 640q6 10 5.5 20.5T885-140q-5 9-14 14.5t-20 5.5H109Zm371-120q17 0 28.5-11.5T520-280q0-17-11.5-28.5T480-320q-17 0-28.5 11.5T440-280q0 17 11.5 28.5T480-240Zm0-120q17 0 28.5-11.5T520-400v-120q0-17-11.5-28.5T480-560q-17 0-28.5 11.5T440-520v120q0 17 11.5 28.5T480-360Z"/>
</svg>`;
text = 'Possível Spam/Golpe Detectado';
break;
case 'offensive':
icon = `<svg xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 -960 960 960" width="48" fill="#ffdd00">
<path d="M480-280q17 0 28.5-11.5T520-320q0-17-11.5-28.5T480-360q-17 0-28.5 11.5T440-320q0 17 11.5 28.5T480-280Zm0-160q17 0 28.5-11.5T520-480v-160q0-17-11.5-28.5T480-680q-17 0-28.5 11.5T440-640v160q0 17 11.5 28.5T480-440ZM363-120q-16 0-30.5-6T307-143L143-307q-11-11-17-25.5t-6-30.5v-234q0-16 6-30.5t17-25.5l164-164q11-11 25.5-17t30.5-6h234q16 0 30.5 6t25.5 17l164 164q11 11 17 25.5t6 30.5v234q0 16-6 30.5T817-307L653-143q-11 11-25.5 17t-30.5 6H363Z"/>
</svg>`;
text = 'Conteúdo Ofensivo Detectado';
break;
}
overlay.innerHTML = `
<div class="warning-icon">${icon}</div>
<div class="warning-text">${text}</div>
`;
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;