Autosave: 20260218-203108

This commit is contained in:
Flatlogic Bot 2026-02-18 20:31:09 +00:00
parent e923af9f34
commit f1694cdee6
10 changed files with 250 additions and 18 deletions

View File

@ -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 > ?

View File

@ -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 {

View File

@ -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 ?
'<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="1" y1="1" x2="23" y2="23"></line><path d="M9 9v3a3 3 0 0 0 5.12 2.12M15 9.34V4a3 3 0 0 0-5.94-.6"></path><path d="M17 16.95A7 7 0 0 1 5 12v-2m14 0v2a7 7 0 0 1-.11 1.23"></path><line x1="12" y1="19" x2="12" y2="23"></line><line x1="8" y1="23" x2="16" y2="23"></line></svg>' :
'<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 1a3 3 0 0 0-3 3v8a3 3 0 0 0 6 0V4a3 3 0 0 0-3-3z"></path><path d="M19 10v2a7 7 0 0 1-14 0v-2"></path><line x1="12" y1="19" x2="12" y2="23"></line><line x1="8" y1="23" x2="16" y2="23"></line></svg>';
}
if (btnDeafen) {
btnDeafen.classList.toggle('active', this.isDeafened);
btnDeafen.style.color = this.isDeafened ? '#f23f43' : 'var(--text-muted)';
btnDeafen.innerHTML = this.isDeafened ?
'<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="1" y1="1" x2="23" y2="23"></line><path d="M8.85 4.11A9 9 0 1 1 20 12"></path><path d="M11.64 6.64A5 5 0 1 1 15 10"></path></svg>' :
'<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 18v-6a9 9 0 0 1 18 0v6"></path><path d="M21 19a2 2 0 0 1-2 2h-1a2 2 0 0 1-2-2v-3a2 2 0 0 1 2-2h3zM3 19a2 2 0 0 0 2 2h1a2 2 0 0 0 2-2v-3a2 2 0 0 0-2-2H3z"></path></svg>';
}
}
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 += '<i class="fa-solid fa-volume-xmark ms-auto text-danger" style="font-size: 10px;"></i>';
} else if (isMuted) {
icons += '<i class="fa-solid fa-microphone-slash ms-auto text-danger" style="font-size: 10px;"></i>';
}
userEl.innerHTML = `
<div class="message-avatar me-2" style="width: 16px; height: 16px; border-radius: 50%; transition: box-shadow 0.2s; ${avatarStyle} ${boxShadow}"></div>
<span style="font-size: 13px;">${username}</span>
<span class="text-truncate" style="font-size: 13px; max-width: 100px;">${username}</span>
${icons}
`;
container.appendChild(userEl);
}

115
contact.php Normal file
View File

@ -0,0 +1,115 @@
<?php
require_once 'auth/session.php';
require_once 'mail/MailService.php';
$success = '';
$error = '';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$name = trim($_POST['name'] ?? '');
$email = trim($_POST['email'] ?? '');
$message = trim($_POST['message'] ?? '');
if (empty($name) || empty($email) || empty($message)) {
$error = 'All fields are required.';
} elseif (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
$error = 'Invalid email address.';
} else {
$res = MailService::sendContactMessage($name, $email, $message);
if (!empty($res['success'])) {
$success = 'Your message has been sent successfully!';
} else {
$error = 'Failed to send message: ' . ($res['error'] ?? 'Unknown error');
}
}
}
?>
<!DOCTYPE html>
<html lang="en" data-theme="dark">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Contact Us - Flatlogic Discord</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<link rel="stylesheet" href="assets/css/discord.css?v=<?php echo time(); ?>">
<style>
body {
background-color: var(--bg-chat);
color: var(--text-primary);
display: flex;
align-items: center;
justify-content: center;
min-height: 100vh;
}
.contact-card {
background-color: var(--bg-channels);
border-radius: 8px;
padding: 32px;
width: 100%;
max-width: 480px;
box-shadow: 0 2px 10px rgba(0,0,0,0.2);
}
.form-control {
background-color: #1e1f22;
border: none;
color: white;
padding: 10px;
}
.form-control:focus {
background-color: #1e1f22;
color: white;
box-shadow: none;
border: 1px solid var(--blurple);
}
.btn-primary {
background-color: var(--blurple);
border: none;
padding: 10px;
font-weight: bold;
}
.btn-primary:hover {
background-color: #4752c4;
}
</style>
</head>
<body>
<div class="contact-card">
<h3 class="text-center mb-4">Contact Us</h3>
<?php if ($success): ?>
<div class="alert alert-success"><?php echo htmlspecialchars($success); ?></div>
<div class="text-center mt-3">
<a href="index.php" class="btn btn-link text-white">Back to App</a>
</div>
<?php else: ?>
<?php if ($error): ?>
<div class="alert alert-danger"><?php echo htmlspecialchars($error); ?></div>
<?php endif; ?>
<form method="POST">
<div class="mb-3">
<label class="form-label text-uppercase small fw-bold">Name</label>
<input type="text" name="name" class="form-control" required value="<?php echo htmlspecialchars($_POST['name'] ?? ''); ?>">
</div>
<div class="mb-3">
<label class="form-label text-uppercase small fw-bold">Email</label>
<input type="email" name="email" class="form-control" required value="<?php echo htmlspecialchars($_POST['email'] ?? ''); ?>">
</div>
<div class="mb-3">
<label class="form-label text-uppercase small fw-bold">Message</label>
<textarea name="message" class="form-control" rows="5" required><?php echo htmlspecialchars($_POST['message'] ?? ''); ?></textarea>
</div>
<button type="submit" class="btn btn-primary w-100 mb-3">Send Message</button>
<div class="text-center">
<a href="index.php" class="btn btn-link text-muted small text-decoration-none">Cancel</a>
</div>
</form>
<?php endif; ?>
<div class="mt-4 p-3 rounded" style="background-color: rgba(255,255,255,0.05); font-size: 0.8em;">
<p class="mb-0 text-muted">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.</p>
</div>
</div>
</body>
</html>

View File

@ -1 +1 @@
[]
{"cd751c28f7e35458":{"id":"cd751c28f7e35458","user_id":3,"name":"swefheim","avatar_url":"","last_seen":1771446668805,"is_muted":1,"is_deafened":0}}

View File

@ -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}}
{"6c0fa2db85f281cf":{"id":"6c0fa2db85f281cf","user_id":2,"name":"swefpifh ᵇʰᶠʳ","avatar_url":"","last_seen":1771446668059,"is_muted":1,"is_deafened":0}}

View File

@ -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;

22
healthz.php Normal file
View File

@ -0,0 +1,22 @@
<?php
require_once 'db/config.php';
header('Content-Type: application/json');
$response = [
'status' => '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);

View File

@ -632,16 +632,23 @@ $projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
<div style="color: var(--text-muted); font-size: 0.75em;">@<?php echo htmlspecialchars($user['username']); ?> #<?php echo str_pad($user['id'], 4, '0', STR_PAD_LEFT); ?></div>
</div>
</div>
<div class="user-actions">
<a href="#" title="Settings" style="color: var(--text-muted); margin-right: 8px;" data-bs-toggle="modal" data-bs-target="#userSettingsModal">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"></circle><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33 1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33 1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"></path></svg>
<div class="user-actions d-flex align-items-center">
<button class="btn btn-link p-1 text-muted border-0" id="btn-panel-mute" title="Mute/Unmute" onclick="if(window.voiceHandler) window.voiceHandler.toggleMute()">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 1a3 3 0 0 0-3 3v8a3 3 0 0 0 6 0V4a3 3 0 0 0-3-3z"></path><path d="M19 10v2a7 7 0 0 1-14 0v-2"></path><line x1="12" y1="19" x2="12" y2="23"></line><line x1="8" y1="23" x2="16" y2="23"></line></svg>
</button>
<button class="btn btn-link p-1 text-muted border-0" id="btn-panel-deafen" title="Deafen/Undeafen" onclick="if(window.voiceHandler) window.voiceHandler.toggleDeafen()">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 18v-6a9 9 0 0 1 18 0v6"></path><path d="M21 19a2 2 0 0 1-2 2h-1a2 2 0 0 1-2-2v-3a2 2 0 0 1 2-2h3zM3 19a2 2 0 0 0 2 2h1a2 2 0 0 0 2-2v-3a2 2 0 0 0-2-2H3z"></path></svg>
</button>
<a href="#" title="Settings" class="p-1 text-muted d-inline-flex" data-bs-toggle="modal" data-bs-target="#userSettingsModal">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"></circle><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33 1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"></path></svg>
</a>
<a href="auth/logout.php" class="p-1 text-muted d-inline-flex" onclick="if(window.voiceHandler) window.voiceHandler.leave(); sessionStorage.clear();" title="Logout">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"></path><polyline points="16 17 21 12 16 7"></polyline><line x1="21" y1="12" x2="9" y2="12"></line></svg>
</a>
<a href="auth/logout.php" onclick="if(window.voiceHandler) window.voiceHandler.leave(); sessionStorage.clear();" title="Logout" style="color: var(--text-muted);">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"></path><polyline points="16 17 21 12 16 7"></polyline><line x1="21" y1="12" x2="9" y2="12"></line></svg></a>
</div>
</div>
<div style="padding: 10px; font-size: 10px; color: #4e5058; border-top: 1px solid #1e1f22;">
PHP <?php echo PHP_VERSION; ?> | <?php echo date('H:i'); ?>
PHP <?php echo PHP_VERSION; ?> | <?php echo date('H:i'); ?> | <a href="healthz.php" style="color: inherit; text-decoration: none;">Health</a> | <a href="contact.php" style="color: inherit; text-decoration: none;">Contact</a>
</div>
</div>

View File

@ -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: []