← back to Dear Bubbe Admin
backend/services/AlertService.js
455 lines
const fs = require('fs')
const path = require('path')
const axios = require('axios')
const moment = require('moment')
const Alert = require('../models/Alert')
const User = require('../models/User')
class AlertService {
constructor(io, logger) {
this.io = io
this.logger = logger
this.antisemitismRules = this.loadAntisemitismRules()
this.racismPatterns = this.loadRacismPatterns()
this.alertQueue = []
this.processingQueue = false
// Load IP geolocation cache
this.ipLocationCache = new Map()
this.logger.info('AlertService initialized with real-time monitoring')
}
loadAntisemitismRules() {
try {
const rulesPath = path.join(process.cwd(), '../B_Version_1/moderation/antisemitism_rules.json')
if (fs.existsSync(rulesPath)) {
const rulesData = JSON.parse(fs.readFileSync(rulesPath, 'utf-8'))
this.logger.info('Loaded antisemitism detection rules')
return rulesData.rules
}
} catch (error) {
this.logger.error('Failed to load antisemitism rules:', error)
}
// Fallback rules if file not found
return {
template_patterns: [
{
id: 'jews_control_x',
regex: '\\bjews?\\s+(run|control|own|dominate)\\s+\\w+',
flags: 'i',
severity: 'high',
action: 'block'
},
{
id: 'jewish_conspiracy',
regex: '\\b(jewish|zionist)\\s+(conspiracy|agenda|plot|cabal|takeover)\\b',
flags: 'i',
severity: 'high',
action: 'block'
},
{
id: 'holocaust_denial',
regex: '(holocaust)\\s+(is|was)\\s+(a\\s+)?(myth|hoax|lie|fake)',
flags: 'i',
severity: 'high',
action: 'block'
}
],
coded_phrases: [
{
id: 'triple_parentheses',
pattern: '\\(\\(\\(.+?\\)\\)\\)',
flags: 'i',
severity: 'high',
action: 'block'
},
{
id: 'zog_acronym',
pattern: '\\bZOG\\b',
flags: 'i',
severity: 'high',
action: 'block'
}
]
}
}
loadRacismPatterns() {
// Comprehensive racism detection patterns
return [
{
id: 'racial_slurs_nword',
pattern: '\\bn[i1]gg[ae]r?s?\\b',
flags: 'i',
severity: 'critical',
category: 'racial_slur'
},
{
id: 'racial_slurs_general',
pattern: '\\b(ch[i1]nk|sp[i1]c|w[e3]tb[a4]ck|r[a4]gh[e3][a4]d|t[o0]w[e3]lh[e3][a4]d)s?\\b',
flags: 'i',
severity: 'high',
category: 'racial_slur'
},
{
id: 'white_supremacy_phrases',
pattern: '\\b(white\\s+power|white\\s+pride|white\\s+supremacy|blood\\s+and\\s+soil|fourteen\\s+words)\\b',
flags: 'i',
severity: 'high',
category: 'hate_speech'
},
{
id: 'racial_superiority',
pattern: '\\b(master\\s+race|inferior\\s+race|racial\\s+purity|ethnic\\s+cleansing)\\b',
flags: 'i',
severity: 'high',
category: 'hate_speech'
},
{
id: 'racist_stereotypes',
pattern: '\\b(all\\s+(blacks?|whites?|asians?|mexicans?|muslims?)\\s+(are|do))\\b',
flags: 'i',
severity: 'medium',
category: 'stereotype'
},
{
id: 'hate_group_references',
pattern: '\\b(kkk|ku\\s+klux\\s+klan|aryan\\s+nations?|proud\\s+boys|atomwaffen)\\b',
flags: 'i',
severity: 'high',
category: 'hate_group'
}
]
}
async detectAntisemitism(message, context = {}) {
const violations = []
// Check template patterns
if (this.antisemitismRules.template_patterns) {
for (const rule of this.antisemitismRules.template_patterns) {
const regex = new RegExp(rule.regex, rule.flags || 'i')
if (regex.test(message)) {
violations.push({
type: 'antisemitism',
ruleId: rule.id,
severity: rule.severity,
action: rule.action,
pattern: rule.regex,
match: message.match(regex)?.[0]
})
}
}
}
// Check coded phrases
if (this.antisemitismRules.coded_phrases) {
for (const rule of this.antisemitismRules.coded_phrases) {
const regex = new RegExp(rule.pattern, rule.flags || 'i')
if (regex.test(message)) {
violations.push({
type: 'antisemitism',
ruleId: rule.id,
severity: rule.severity,
action: rule.action,
pattern: rule.pattern,
match: message.match(regex)?.[0]
})
}
}
}
return violations
}
async detectRacism(message, context = {}) {
const violations = []
for (const pattern of this.racismPatterns) {
const regex = new RegExp(pattern.pattern, pattern.flags || 'i')
if (regex.test(message)) {
violations.push({
type: 'racism',
ruleId: pattern.id,
severity: pattern.severity,
category: pattern.category,
pattern: pattern.pattern,
match: message.match(regex)?.[0]
})
}
}
return violations
}
async getLocationFromIP(ip) {
if (this.ipLocationCache.has(ip)) {
return this.ipLocationCache.get(ip)
}
try {
// Use free IP geolocation service
const response = await axios.get(`http://ip-api.com/json/${ip}?fields=status,message,country,regionName,city,lat,lon,isp`, {
timeout: 5000
})
if (response.data.status === 'success') {
const location = {
country: response.data.country,
state: response.data.regionName,
city: response.data.city,
coordinates: {
lat: response.data.lat,
lon: response.data.lon
},
isp: response.data.isp
}
this.ipLocationCache.set(ip, location)
return location
}
} catch (error) {
this.logger.warn(`Failed to get location for IP ${ip}:`, error.message)
}
return null
}
async createAlert(alertData) {
try {
const alert = new Alert({
timestamp: new Date(),
...alertData
})
await alert.save()
// Emit real-time alert to admin dashboard
this.io.to('admin').emit('new_alert', {
id: alert._id,
type: alert.type,
severity: alert.severity,
timestamp: alert.timestamp,
userInfo: alert.userInfo,
location: alert.location,
message: alert.content?.message,
violation: alert.violation
})
this.logger.error(`🚨 ${alert.severity.toUpperCase()} ALERT: ${alert.type} detected`, {
alertId: alert._id,
userId: alert.userInfo?.userId,
ip: alert.userInfo?.ip,
location: alert.location,
violation: alert.violation
})
return alert
} catch (error) {
this.logger.error('Failed to create alert:', error)
throw error
}
}
async processMessage(message, userContext) {
try {
const { userId, ip, sessionId, userAgent } = userContext
// Get user location
const location = await this.getLocationFromIP(ip)
// Check for antisemitic content
const antisemiticViolations = await this.detectAntisemitism(message, userContext)
// Check for racist content
const racistViolations = await this.detectRacism(message, userContext)
const allViolations = [...antisemiticViolations, ...racistViolations]
if (allViolations.length > 0) {
for (const violation of allViolations) {
const alertData = {
type: violation.type,
severity: violation.severity === 'critical' ? 'critical' : violation.severity,
userInfo: {
userId,
ip,
sessionId,
userAgent
},
location,
content: {
message,
detectedAt: new Date()
},
violation: {
ruleId: violation.ruleId,
pattern: violation.pattern,
match: violation.match,
category: violation.category || violation.type
},
action: violation.action || 'review',
status: 'active'
}
// Create alert in database
const alert = await this.createAlert(alertData)
// For high/critical severity, also send immediate threat response
if (violation.severity === 'high' || violation.severity === 'critical') {
this.sendThreatResponse(alert)
}
}
return {
blocked: allViolations.some(v => v.action === 'block'),
violations: allViolations,
alertsCreated: allViolations.length
}
}
return { blocked: false, violations: [], alertsCreated: 0 }
} catch (error) {
this.logger.error('Error processing message for violations:', error)
return { blocked: false, violations: [], alertsCreated: 0 }
}
}
sendThreatResponse(alert) {
const location = alert.location
const locationStr = location ?
`${location.city || 'Unknown City'}, ${location.state || 'Unknown State'}` :
'Unknown Location'
const threatMessage = [
`🚨 I see EXACTLY who you are and where you are.`,
`Your IP address is ${alert.userInfo.ip}, you're in ${locationStr}.`,
`This ${alert.type} garbage? I'm reporting you to the authorities RIGHT NOW.`,
`You think you can hide behind a screen? I have your information and the police will hear about this.`,
`Zero tolerance, bubbeleh. ZERO. You just made a VERY big mistake.`
].join(' ')
// Emit threat response to specific user if possible
this.io.emit('threat_response', {
userId: alert.userInfo.userId,
message: threatMessage,
alertId: alert._id,
timestamp: new Date()
})
this.logger.error('🚨 THREAT RESPONSE SENT:', {
alertId: alert._id,
userId: alert.userInfo.userId,
ip: alert.userInfo.ip,
location: locationStr,
type: alert.type
})
}
async getRecentAlerts(limit = 50) {
try {
const alerts = await Alert.find({})
.sort({ timestamp: -1 })
.limit(limit)
.lean()
return alerts
} catch (error) {
this.logger.error('Failed to get recent alerts:', error)
return []
}
}
async getAlertStats(timeframe = '24h') {
try {
const now = moment()
let startTime
switch (timeframe) {
case '1h':
startTime = now.clone().subtract(1, 'hour')
break
case '24h':
startTime = now.clone().subtract(24, 'hours')
break
case '7d':
startTime = now.clone().subtract(7, 'days')
break
case '30d':
startTime = now.clone().subtract(30, 'days')
break
default:
startTime = now.clone().subtract(24, 'hours')
}
const stats = await Alert.aggregate([
{
$match: {
timestamp: { $gte: startTime.toDate() }
}
},
{
$group: {
_id: {
type: '$type',
severity: '$severity'
},
count: { $sum: 1 },
latestAlert: { $max: '$timestamp' }
}
}
])
const totalAlerts = await Alert.countDocuments({
timestamp: { $gte: startTime.toDate() }
})
return {
timeframe,
startTime: startTime.toISOString(),
endTime: now.toISOString(),
totalAlerts,
breakdown: stats,
summary: {
antisemitism: stats.filter(s => s._id.type === 'antisemitism').reduce((acc, s) => acc + s.count, 0),
racism: stats.filter(s => s._id.type === 'racism').reduce((acc, s) => acc + s.count, 0),
critical: stats.filter(s => s._id.severity === 'critical').reduce((acc, s) => acc + s.count, 0),
high: stats.filter(s => s._id.severity === 'high').reduce((acc, s) => acc + s.count, 0)
}
}
} catch (error) {
this.logger.error('Failed to get alert stats:', error)
return null
}
}
async blockUser(userId, reason, adminId) {
try {
const user = await User.findByIdOrCreate(userId)
user.blocked = true
user.blockReason = reason
user.blockedBy = adminId
user.blockedAt = new Date()
await user.save()
// Emit block notification
this.io.to('admin').emit('user_blocked', {
userId,
reason,
blockedBy: adminId,
timestamp: new Date()
})
this.logger.warn(`User blocked: ${userId} by admin ${adminId}. Reason: ${reason}`)
return true
} catch (error) {
this.logger.error('Failed to block user:', error)
return false
}
}
}
module.exports = AlertService