diff --git a/api_v1_voice.php b/api_v1_voice.php index dd125d4..2a576d8 100644 --- a/api_v1_voice.php +++ b/api_v1_voice.php @@ -138,8 +138,13 @@ if ($action === "poll") { $p_file = room_participants_file($room); $ps = read_json_file($p_file); + $is_muted = isset($_REQUEST["is_muted"]) ? (int)$_REQUEST["is_muted"] : 0; + $is_deafened = isset($_REQUEST["is_deafened"]) ? (int)$_REQUEST["is_deafened"] : 0; + if (isset($ps[$my_id])) { $ps[$my_id]["last_seen"] = now_ms(); + $ps[$my_id]["is_muted"] = $is_muted; + $ps[$my_id]["is_deafened"] = $is_deafened; } $stale_time = now_ms() - 10000; @@ -151,8 +156,8 @@ if ($action === "poll") { // Update DB last_seen if ($current_user_id > 0) { try { - $stmt = db()->prepare("UPDATE voice_sessions SET last_seen = ? WHERE user_id = ?"); - $stmt->execute([now_ms(), $current_user_id]); + $stmt = db()->prepare("UPDATE voice_sessions SET last_seen = ?, is_muted = ?, is_deafened = ? WHERE user_id = ?"); + $stmt->execute([now_ms(), $is_muted, $is_deafened, $current_user_id]); } catch (Exception $e) {} } @@ -218,7 +223,7 @@ if ($action === "list_all") { try { $stmt = db()->prepare(" - SELECT vs.channel_id, vs.user_id, u.username, u.display_name, u.avatar_url + 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 > ? diff --git a/assets/css/discord.css b/assets/css/discord.css index ee53328..686bb12 100644 --- a/assets/css/discord.css +++ b/assets/css/discord.css @@ -926,8 +926,19 @@ body { /* Voice active state */ .voice-item.active, .voice-item.connected { - background-color: rgba(35, 165, 89, 0.1); - color: #23a559 !important; + background-color: var(--active); + color: var(--text-primary); +} +.user-actions .btn:hover { + background-color: var(--hover); + color: var(--text-primary) !important; +} +.user-actions .btn.active { + color: #f23f43 !important; +} +.user-actions a:hover { + background-color: var(--hover); + border-radius: 4px; } .voice-user { diff --git a/assets/js/voice.js b/assets/js/voice.js index fa78177..088aa43 100644 --- a/assets/js/voice.js +++ b/assets/js/voice.js @@ -13,6 +13,8 @@ class VoiceChannel { this.myPeerId = null; this.pollInterval = null; this.remoteAudios = {}; // userId -> Audio element + this.isMuted = false; + this.isDeafened = false; this.audioContext = null; this.analyser = null; @@ -140,7 +142,7 @@ class VoiceChannel { if (!this.myPeerId || !this.currentChannelId) return; try { - const resp = await fetch(`api_v1_voice.php?action=poll&room=${this.currentChannelId}&peer_id=${this.myPeerId}`); + const resp = await fetch(`api_v1_voice.php?action=poll&room=${this.currentChannelId}&peer_id=${this.myPeerId}&is_muted=${this.isMuted ? 1 : 0}&is_deafened=${this.isDeafened ? 1 : 0}`); const data = await resp.json(); if (data.success) { @@ -230,6 +232,7 @@ class VoiceChannel { remoteAudio.autoplay = true; remoteAudio.style.display = 'none'; remoteAudio.srcObject = stream; + remoteAudio.muted = this.isDeafened; document.body.appendChild(remoteAudio); this.remoteAudios[userId] = remoteAudio; @@ -404,10 +407,53 @@ class VoiceChannel { } setMute(mute) { + this.isMuted = mute; if (this.localStream) { console.log('Setting mute to:', mute); this.localStream.getAudioTracks().forEach(track => { track.enabled = !mute; }); } + this.updateUserPanelButtons(); + } + + toggleMute() { + this.setMute(!this.isMuted); + } + + toggleDeafen() { + this.isDeafened = !this.isDeafened; + console.log('Setting deafen to:', this.isDeafened); + Object.values(this.remoteAudios).forEach(audio => { + audio.muted = this.isDeafened; + }); + // If we deafen, we usually also mute in Discord + if (this.isDeafened && !this.isMuted) { + this.setMute(true); + } else if (!this.isDeafened && this.isMuted) { + // Not necessarily unmute when undeafen, but often expected + // Let's just update UI + } + this.updateUserPanelButtons(); + } + + updateUserPanelButtons() { + const btnMute = document.getElementById('btn-panel-mute'); + const btnDeafen = document.getElementById('btn-panel-deafen'); + + if (btnMute) { + btnMute.classList.toggle('active', this.isMuted); + btnMute.style.color = this.isMuted ? '#f23f43' : 'var(--text-muted)'; + btnMute.innerHTML = this.isMuted ? + '' : + ''; + } + + if (btnDeafen) { + btnDeafen.classList.toggle('active', this.isDeafened); + btnDeafen.style.color = this.isDeafened ? '#f23f43' : 'var(--text-muted)'; + btnDeafen.innerHTML = this.isDeafened ? + '' : + ''; + } } leave() { @@ -506,6 +552,7 @@ class VoiceChannel { } updateSpeakingUI(userId, isSpeaking) { + userId = String(userId); if (isSpeaking) { this.speakingUsers.add(userId); } else { @@ -548,8 +595,9 @@ class VoiceChannel { const listEl = container.querySelector('.voice-users-list'); if (listEl) { data.channels[channelId].forEach(p => { - const isSpeaking = window.voiceHandler && window.voiceHandler.speakingUsers.has(p.user_id); - VoiceChannel.renderUserToUI(listEl, p.user_id, p.display_name || p.username, p.avatar_url, isSpeaking); + const pid = String(p.user_id); + const isSpeaking = window.voiceHandler && window.voiceHandler.speakingUsers.has(pid); + VoiceChannel.renderUserToUI(listEl, p.user_id, p.display_name || p.username, p.avatar_url, isSpeaking, p.is_muted, p.is_deafened); }); } } @@ -561,16 +609,25 @@ class VoiceChannel { } } - static renderUserToUI(container, userId, username, avatarUrl, isSpeaking = false) { + static renderUserToUI(container, userId, username, avatarUrl, isSpeaking = false, isMuted = false, isDeafened = false) { const userEl = document.createElement('div'); userEl.className = 'voice-user small text-muted d-flex align-items-center mb-1'; userEl.dataset.userId = userId; userEl.style.paddingLeft = '8px'; const avatarStyle = avatarUrl ? `background-image: url('${avatarUrl}'); background-size: cover;` : "background-color: #555;"; const boxShadow = isSpeaking ? 'box-shadow: 0 0 0 2px #23a559;' : ''; + + let icons = ''; + if (isDeafened) { + icons += ''; + } else if (isMuted) { + icons += ''; + } + userEl.innerHTML = `
- ${username} + ${username} + ${icons} `; container.appendChild(userEl); } diff --git a/contact.php b/contact.php new file mode 100644 index 0000000..4ffba5b --- /dev/null +++ b/contact.php @@ -0,0 +1,115 @@ + + + + + + + Contact Us - Flatlogic Discord + + + + + + +
+

Contact Us

+ + +
+
+ Back to App +
+ + +
+ + +
+
+ + +
+
+ + +
+
+ + +
+ +
+ Cancel +
+
+ + +
+

This is for testing purposes only — Flatlogic does not guarantee usage of the mail server. Please set up your own SMTP in .env with our AI Agent.

+
+
+ + diff --git a/data/22.participants.json b/data/22.participants.json index 0637a08..62d840a 100644 --- a/data/22.participants.json +++ b/data/22.participants.json @@ -1 +1 @@ -[] \ No newline at end of file +{"cd751c28f7e35458":{"id":"cd751c28f7e35458","user_id":3,"name":"swefheim","avatar_url":"","last_seen":1771446668805,"is_muted":1,"is_deafened":0}} \ No newline at end of file diff --git a/data/3.participants.json b/data/3.participants.json index 7ff70f7..563e840 100644 --- a/data/3.participants.json +++ b/data/3.participants.json @@ -1 +1 @@ -{"ec29a24a820e7529":{"id":"ec29a24a820e7529","user_id":3,"name":"swefheim","avatar_url":"","last_seen":1771443332052},"9312694ff19bebf2":{"id":"9312694ff19bebf2","user_id":2,"name":"swefpifh ᵇʰᶠʳ","avatar_url":"","last_seen":1771443331634}} \ No newline at end of file +{"6c0fa2db85f281cf":{"id":"6c0fa2db85f281cf","user_id":2,"name":"swefpifh ᵇʰᶠʳ","avatar_url":"","last_seen":1771446668059,"is_muted":1,"is_deafened":0}} \ No newline at end of file diff --git a/db/migrations/20260218_voice_status.sql b/db/migrations/20260218_voice_status.sql new file mode 100644 index 0000000..471a65f --- /dev/null +++ b/db/migrations/20260218_voice_status.sql @@ -0,0 +1,3 @@ +-- Add mute and deafen status to voice sessions +ALTER TABLE voice_sessions ADD COLUMN is_muted BOOLEAN DEFAULT FALSE; +ALTER TABLE voice_sessions ADD COLUMN is_deafened BOOLEAN DEFAULT FALSE; diff --git a/healthz.php b/healthz.php new file mode 100644 index 0000000..f219d80 --- /dev/null +++ b/healthz.php @@ -0,0 +1,22 @@ + 'ok', + 'timestamp' => time(), + 'php_version' => PHP_VERSION, + 'database' => 'unknown' +]; + +try { + $db = db(); + $db->query('SELECT 1'); + $response['database'] = 'connected'; +} catch (Exception $e) { + $response['status'] = 'error'; + $response['database'] = 'error: ' . $e->getMessage(); +} + +echo json_encode($response); diff --git a/index.php b/index.php index eff5509..2b2a366 100644 --- a/index.php +++ b/index.php @@ -632,16 +632,23 @@ $projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
@ #
-
- - +
+ + + + + + + - -
- PHP | + PHP | | Health | Contact
diff --git a/requests.log b/requests.log index 5f10dd3..8744106 100644 --- a/requests.log +++ b/requests.log @@ -688,3 +688,15 @@ 2026-02-18 19:21:05 - GET /?fl_project=38443 - POST: [] 2026-02-18 19:34:14 - GET /index.php - POST: [] 2026-02-18 19:34:43 - GET /index.php - POST: [] +2026-02-18 19:35:59 - GET /index.php?server_id=3 - POST: [] +2026-02-18 19:36:04 - GET /index.php?server_id=1 - POST: [] +2026-02-18 19:36:06 - GET /index.php?server_id=1&channel_id=6 - POST: [] +2026-02-18 19:36:50 - GET /index.php?server_id=1&channel_id=15 - POST: [] +2026-02-18 19:37:29 - GET /index.php?server_id=1&channel_id=6 - POST: [] +2026-02-18 19:37:38 - GET /index.php?server_id=1&channel_id=15 - POST: [] +2026-02-18 19:40:34 - GET /?fl_project=38443 - POST: [] +2026-02-18 19:52:03 - GET / - POST: [] +2026-02-18 19:52:24 - GET /?fl_project=38443 - POST: [] +2026-02-18 20:29:14 - GET /index.php?server_id=1&channel_id=15 - POST: [] +2026-02-18 20:30:52 - GET /index.php - POST: [] +2026-02-18 20:30:56 - GET /index.php - POST: []