sanitize($data['user_name']), 'text' => sanitize($data['text']), 'timestamp' => $data['timestamp'] ?? date('H:i:s'), 'created_at' => time() ]; // Add to room addTranscription($room, $transcription); // Cleanup old sessions cleanupOldSessions(); // Success response sendJson(['status' => 'ok', 'message' => 'Transcription added']); } /** * Handle streaming transcriptions via Server-Sent Events * Note: Passphrase is optional for streaming (read-only access) */ function handleStream() { // Get parameters $room = sanitize($_GET['room'] ?? ''); if (empty($room)) { sendError('Missing room name', 400); } // Passphrase is optional for streaming (read-only) // If room doesn't exist yet, return empty stream if (!roomExists($room)) { // Return empty stream - room doesn't exist yet header('Content-Type: text/event-stream'); header('Cache-Control: no-cache'); header('X-Accel-Buffering: no'); echo ": waiting for room to be created\n\n"; flush(); exit(); } // Set SSE headers header('Content-Type: text/event-stream'); header('Cache-Control: no-cache'); header('X-Accel-Buffering: no'); // Disable nginx buffering // Track last known count $lastCount = 0; // Stream loop while (true) { $transcriptions = getTranscriptions($room); $currentCount = count($transcriptions); // If new transcriptions, send them if ($currentCount > $lastCount) { $newTranscriptions = array_slice($transcriptions, $lastCount); foreach ($newTranscriptions as $trans) { echo "data: " . json_encode($trans) . "\n\n"; flush(); } $lastCount = $currentCount; } // Send keepalive comment every 1 second (keeps SSE connection alive) echo ": keepalive\n\n"; flush(); // Check if client disconnected if (connection_aborted()) { break; } // Wait before next check sleep(1); } } /** * Handle listing recent transcriptions * Note: Passphrase is optional for listing (read-only access) */ function handleList() { $room = sanitize($_GET['room'] ?? ''); if (empty($room)) { sendError('Missing room name', 400); } // Passphrase is optional for read-only access // If room doesn't exist, return empty array $transcriptions = getTranscriptions($room); sendJson(['transcriptions' => $transcriptions]); } /** * Handle info request */ function handleInfo() { sendJson([ 'service' => 'Local Transcription Multi-User Server', 'version' => '1.0.0', 'endpoints' => [ 'POST ?action=send' => 'Send a transcription', 'GET ?action=stream' => 'Stream transcriptions (SSE)', 'GET ?action=list' => 'List recent transcriptions' ] ]); } /** * Verify passphrase for a room */ function verifyPassphrase($room, $passphrase) { $file = getRoomFile($room); // If room doesn't exist, create it with this passphrase if (!file_exists($file)) { $roomData = [ 'passphrase_hash' => password_hash($passphrase, PASSWORD_DEFAULT), 'created_at' => time(), 'transcriptions' => [] ]; file_put_contents($file, json_encode($roomData)); return true; } // Verify passphrase $roomData = json_decode(file_get_contents($file), true); return password_verify($passphrase, $roomData['passphrase_hash']); } /** * Add transcription to room */ function addTranscription($room, $transcription) { $file = getRoomFile($room); $roomData = json_decode(file_get_contents($file), true); // Add transcription $roomData['transcriptions'][] = $transcription; // Limit to max transcriptions if (count($roomData['transcriptions']) > MAX_TRANSCRIPTIONS_PER_ROOM) { $roomData['transcriptions'] = array_slice( $roomData['transcriptions'], -MAX_TRANSCRIPTIONS_PER_ROOM ); } // Update last activity $roomData['last_activity'] = time(); // Save file_put_contents($file, json_encode($roomData)); } /** * Get transcriptions for a room */ function getTranscriptions($room) { $file = getRoomFile($room); if (!file_exists($file)) { return []; } $roomData = json_decode(file_get_contents($file), true); return $roomData['transcriptions'] ?? []; } /** * Get room data file path */ function getRoomFile($room) { return STORAGE_DIR . '/room_' . md5($room) . '.json'; } /** * Check if room exists */ function roomExists($room) { return file_exists(getRoomFile($room)); } /** * Cleanup old sessions */ function cleanupOldSessions() { $files = glob(STORAGE_DIR . '/room_*.json'); $now = time(); foreach ($files as $file) { $data = json_decode(file_get_contents($file), true); $lastActivity = $data['last_activity'] ?? $data['created_at']; if ($now - $lastActivity > CLEANUP_THRESHOLD) { unlink($file); } } } /** * Sanitize input */ function sanitize($input) { return htmlspecialchars(strip_tags(trim($input)), ENT_QUOTES, 'UTF-8'); } /** * Send JSON response */ function sendJson($data, $code = 200) { http_response_code($code); header('Content-Type: application/json'); echo json_encode($data); exit(); } /** * Send error response */ function sendError($message, $code = 400) { sendJson(['error' => $message], $code); } ?>