Reubencf's picture
Revert Messages to use /tmp - no write permissions on HF Spaces
9953d0a
import { NextResponse } from 'next/server'
import fs from 'fs'
import path from 'path'
// Use /tmp for temporary message storage
// Messages will reset on rebuilds, which is fine for a global chat
const dataFilePath = path.join('/tmp', 'messages.json')
// Simple bad word list (expand as needed)
const BAD_WORDS = [
// English
'badword', 'spam', 'toxic', 'hate', 'violence', 'kill', 'stupid', 'idiot',
'fuck', 'fuxk', 'fck', 'shit', 'sh!t', 'bitch', 'asshole', 'damn', 'hell',
'crap', 'piss', 'dick', 'cock', 'pussy', 'bastard', 'slut', 'whore',
// Spanish
'puto', 'mierda', 'coño', 'cabron', 'pendejo', 'joder',
// French
'merde', 'putain', 'connard', 'salope', 'encule',
// German
'scheisse', 'arschloch', 'schlampe', 'fotze',
// Italian
'cazzo', 'merda', 'vaffanculo', 'stronzo',
// Portuguese
'porra', 'caralho', 'merda', 'puta',
// Russian (transliterated)
'cyka', 'blyat', 'nahui', 'pizda',
// Hindi (transliterated)
'madarchod', 'bhenchod', 'chutiya', 'kutta', 'kamina'
]
interface Message {
id: string
text: string
sender: string
userId: string
timestamp: number
}
const getMessages = (): Message[] => {
try {
if (!fs.existsSync(dataFilePath)) {
fs.writeFileSync(dataFilePath, '[]', 'utf8')
return []
}
const fileData = fs.readFileSync(dataFilePath, 'utf8')
return JSON.parse(fileData)
} catch (error) {
console.error('Error reading messages:', error)
return []
}
}
const saveMessages = (messages: Message[]) => {
try {
fs.writeFileSync(dataFilePath, JSON.stringify(messages, null, 2), 'utf8')
} catch (error) {
console.error('Error saving messages:', error)
}
}
export async function GET() {
const messages = getMessages()
return NextResponse.json(messages, {
headers: { 'Content-Type': 'application/json; charset=utf-8' }
})
}
export async function POST(request: Request) {
try {
const body = await request.json()
const { text, sender, userId } = body
// 1. Validation: Length
if (!text || text.length > 200) {
return NextResponse.json({ error: 'Message too long (max 200 chars)' }, { status: 400 })
}
if (!text.trim()) {
return NextResponse.json({ error: 'Message cannot be empty' }, { status: 400 })
}
// 2. Validation: Toxicity (whole word matching only)
const lowerText = text.toLowerCase()
const containsBadWord = BAD_WORDS.some(word => {
// Use word boundaries to match whole words only
const regex = new RegExp(`\\b${word.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\b`, 'i')
return regex.test(text)
})
if (containsBadWord) {
return NextResponse.json({ error: 'Message contains inappropriate content' }, { status: 400 })
}
const messages = getMessages()
const now = Date.now()
// 3. Validation: Spam (Rate Limiting)
// Check if this user sent a message in the last 2 seconds
const lastMessageFromUser = messages
.filter(m => m.userId === userId)
.sort((a, b) => b.timestamp - a.timestamp)[0]
if (lastMessageFromUser && (now - lastMessageFromUser.timestamp) < 2000) {
return NextResponse.json({ error: 'You are sending messages too fast. Please wait.' }, { status: 429 })
}
// Check for duplicate message from same user
if (lastMessageFromUser && lastMessageFromUser.text === text) {
return NextResponse.json({ error: 'Do not send duplicate messages.' }, { status: 400 })
}
const newMessage: Message = {
id: Math.random().toString(36).substring(2, 15),
text: text.trim(),
sender: sender || 'Anonymous',
userId: userId,
timestamp: now
}
// Keep only last 100 messages to prevent file from growing too large
const updatedMessages = [...messages, newMessage].slice(-100)
saveMessages(updatedMessages)
return NextResponse.json(updatedMessages, {
headers: { 'Content-Type': 'application/json; charset=utf-8' }
})
} catch (error) {
console.error('Error processing message:', error)
return NextResponse.json({ error: 'Internal Server Error' }, { status: 500 })
}
}