prepare("DELETE FROM voice_sessions WHERE last_seen < ?")->execute([$stale_time]); $stmt = db()->prepare(" SELECT vs.peer_id as id, vs.user_id, vs.name, vs.last_seen, vs.is_muted, vs.is_deafened, u.avatar_url FROM voice_sessions vs LEFT JOIN users u ON vs.user_id = u.id WHERE vs.channel_id = ? AND vs.last_seen > ? "); $stmt->execute([$room, $stale_time]); $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); foreach ($rows as $r) { $r['user_id'] = (int)$r['user_id']; $r['last_seen'] = (int)$r['last_seen']; $r['is_muted'] = (int)$r['is_muted']; $r['is_deafened'] = (int)$r['is_deafened']; $ps[$r['id']] = $r; } } catch (Exception $e) { error_log("get_room_participants error: " . $e->getMessage()); } return $ps; } // Logic for signaling $action = $_REQUEST["action"] ?? ""; $room = room_id($_REQUEST["room"] ?? "secours"); $my_id = $_REQUEST["peer_id"] ?? ""; if ($action === "join") { $name = $_REQUEST["name"] ?? "User"; $new_id = substr($_REQUEST["peer_id"] ?: peer_id(), 0, 16); // DB Integration for sidebar and participation if ($current_user_id > 0) { try { $stmt = db()->prepare("INSERT INTO voice_sessions (user_id, channel_id, last_seen, peer_id, name) VALUES (?, ?, ?, ?, ?) ON DUPLICATE KEY UPDATE channel_id = ?, last_seen = ?, peer_id = ?, name = ?"); $stmt->execute([$current_user_id, $room, now_ms(), $new_id, $name, $room, now_ms(), $new_id, $name]); } catch (Exception $e) { error_log("Voice session DB error: " . $e->getMessage()); } } $ps = get_room_participants($room); json_out(["success" => true, "peer_id" => $new_id, "participants" => $ps, "can_speak" => Permissions::canDoInChannel($current_user_id, (int)$room, Permissions::SPEAK)]); } if ($action === "poll") { if (!$my_id) json_out(["error" => "Missing peer_id"], 400); $is_muted = isset($_REQUEST["is_muted"]) ? (int)$_REQUEST["is_muted"] : 0; $is_deafened = isset($_REQUEST["is_deafened"]) ? (int)$_REQUEST["is_deafened"] : 0; $can_speak = Permissions::canDoInChannel($current_user_id, (int)$room, Permissions::SPEAK); if (!$can_speak) $is_muted = 1; // Update DB last_seen if ($current_user_id > 0) { try { $stmt = db()->prepare("UPDATE voice_sessions SET last_seen = ?, is_muted = ?, is_deafened = ?, channel_id = ?, peer_id = ?, name = ? WHERE user_id = ?"); $name = $_REQUEST["name"] ?? ($user["display_name"] ?: $user["username"] ?: "User"); $stmt->execute([now_ms(), $is_muted, $is_deafened, $room, $my_id, $name, $current_user_id]); // If update failed (no rows affected because session was deleted or user_id mismatch), re-insert if ($stmt->rowCount() === 0) { $stmt = db()->prepare("INSERT INTO voice_sessions (user_id, channel_id, last_seen, peer_id, name, is_muted, is_deafened) VALUES (?, ?, ?, ?, ?, ?, ?) ON DUPLICATE KEY UPDATE channel_id = ?, last_seen = ?, peer_id = ?, name = ?, is_muted = ?, is_deafened = ?"); $stmt->execute([$current_user_id, $room, now_ms(), $my_id, $name, $is_muted, $is_deafened, $room, now_ms(), $my_id, $name, $is_muted, $is_deafened]); } } catch (Exception $e) {} } $ps = get_room_participants($room); // Read signals from DB $signals = []; try { $stmt = db()->prepare("SELECT id, from_peer_id as `from`, data FROM voice_signals WHERE to_peer_id = ? ORDER BY id ASC"); $stmt->execute([$my_id]); $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); if (!empty($rows)) { $ids = []; foreach ($rows as $r) { $signals[] = [ "from" => $r["from"], "data" => json_decode($r["data"], true) ]; $ids[] = $r["id"]; } // Delete signals we just read $placeholders = implode(',', array_fill(0, count($ids), '?')); $stmt_del = db()->prepare("DELETE FROM voice_signals WHERE id IN ($placeholders)"); $stmt_del->execute($ids); } // Periodic cleanup of old signals (> 1 minute) if (rand(1, 20) === 1) { $old_time = now_ms() - 60000; $stmt_clean = db()->prepare("DELETE FROM voice_signals WHERE created_at_ms < ?"); $stmt_clean->execute([$old_time]); } } catch (Exception $e) { error_log("Signal poll error: " . $e->getMessage()); } json_out([ "success" => true, "participants" => $ps, "signals" => $signals, "can_speak" => $can_speak ]); } if ($action === "signal") { if (!$my_id) json_out(["error" => "Missing peer_id"], 400); $to = $_REQUEST["to"] ?? ""; $data = $_REQUEST["data"] ?? ""; if (!$to || !$data) json_out(["error" => "Missing to/data"], 400); try { $stmt = db()->prepare("INSERT INTO voice_signals (room_id, from_peer_id, to_peer_id, data, created_at_ms) VALUES (?, ?, ?, ?, ?)"); $stmt->execute([$room, $my_id, $to, $data, now_ms()]); json_out(["success" => true]); } catch (Exception $e) { json_out(["error" => "Signal send failed: " . $e->getMessage()], 500); } } if ($action === "list_all") { // Periodic cleanup of the DB table (stale sessions > 15s) if (rand(1, 10) === 1) { try { $stale_db_time = now_ms() - 30000; $stmt = db()->prepare("DELETE FROM voice_sessions WHERE last_seen < ?"); $stmt->execute([$stale_db_time]); } catch (Exception $e) {} } try { $stmt = db()->prepare(" SELECT vs.channel_id, vs.user_id, vs.is_muted, vs.is_deafened, u.username, u.display_name, u.avatar_url FROM voice_sessions vs JOIN users u ON vs.user_id = u.id WHERE vs.last_seen > ? "); $stale_db_time = now_ms() - 30000; $stmt->execute([$stale_db_time]); $sessions = $stmt->fetchAll(PDO::FETCH_ASSOC); $by_channel = []; foreach ($sessions as $s) { $by_channel[$s['channel_id']][] = $s; } json_out(["success" => true, "channels" => $by_channel]); } catch (Exception $e) { json_out(["error" => $e->getMessage()], 500); } } if ($action === "leave") { if ($current_user_id > 0) { try { $stmt = db()->prepare("DELETE FROM voice_sessions WHERE user_id = ?"); $stmt->execute([$current_user_id]); } catch (Exception $e) {} } json_out(["success" => true]); } if ($action === "get_whispers") { if ($current_user_id <= 0) json_out(["error" => "Unauthorized"], 401); try { $stmt = db()->prepare("SELECT * FROM voice_whispers WHERE user_id = ?"); $stmt->execute([$current_user_id]); json_out(["success" => true, "whispers" => $stmt->fetchAll(PDO::FETCH_ASSOC)]); } catch (Exception $e) { json_out(["error" => $e->getMessage()], 500); } } if ($action === "save_whisper") { if ($current_user_id <= 0) json_out(["error" => "Unauthorized"], 401); $target_type = $_REQUEST["target_type"] ?? ""; $target_id = (int)($_REQUEST["target_id"] ?? 0); $key = $_REQUEST["key"] ?? ""; if (!$target_type || !$target_id || !$key) json_out(["error" => "Missing parameters"], 400); try { $stmt = db()->prepare("INSERT INTO voice_whispers (user_id, target_type, target_id, whisper_key) VALUES (?, ?, ?, ?) ON DUPLICATE KEY UPDATE target_type = ?, target_id = ?, whisper_key = ?"); $stmt->execute([$current_user_id, $target_type, $target_id, $key, $target_type, $target_id, $key]); json_out(["success" => true]); } catch (Exception $e) { json_out(["error" => $e->getMessage()], 500); } } if ($action === "delete_whisper") { if ($current_user_id <= 0) json_out(["error" => "Unauthorized"], 401); $id = (int)($_REQUEST["id"] ?? 0); try { $stmt = db()->prepare("DELETE FROM voice_whispers WHERE id = ? AND user_id = ?"); $stmt->execute([$id, $current_user_id]); json_out(["success" => true]); } catch (Exception $e) { json_out(["error" => $e->getMessage()], 500); } } if ($action === "find_whisper_targets") { if ($current_user_id <= 0) json_out(["error" => "Unauthorized"], 401); $target_type = $_REQUEST["target_type"] ?? ""; $target_id = $_REQUEST["target_id"] ?? ""; // Can be channel name or user_id if (!$target_type || !$target_id) json_out(["error" => "Missing parameters"], 400); try { $stale_time = now_ms() - 30000; if ($target_type === 'user') { $stmt = db()->prepare("SELECT peer_id, name FROM voice_sessions WHERE user_id = ? AND last_seen > ?"); $stmt->execute([(int)$target_id, $stale_time]); } else { // target_id is channel_id (room) $stmt = db()->prepare("SELECT peer_id, name FROM voice_sessions WHERE channel_id = ? AND last_seen > ?"); $stmt->execute([(string)$target_id, $stale_time]); } json_out(["success" => true, "targets" => $stmt->fetchAll(PDO::FETCH_ASSOC)]); } catch (Exception $e) { json_out(["error" => $e->getMessage()], 500); } } json_out(["error" => "Unknown action"], 404);