diff --git a/Temp/index_new.php b/Temp/index_new.php
new file mode 100644
index 0000000..63d21a5
--- /dev/null
+++ b/Temp/index_new.php
@@ -0,0 +1,3965 @@
+';
+ } elseif ($isFa) {
+ return '';
+ } elseif ($isCustomEmote) {
+ // Fetch emote path
+ static $ce_icons_cache;
+ if ($ce_icons_cache === null) {
+ try { $ce_icons_cache = db()->query("SELECT code, path FROM custom_emotes")->fetchAll(PDO::FETCH_KEY_PAIR); } catch (Exception $e) { $ce_icons_cache = []; }
+ }
+ if (isset($ce_icons_cache[$icon])) {
+ return '';
+ }
+ return '' . htmlspecialchars($icon) . '';
+ } else {
+ return '' . htmlspecialchars($icon) . '';
+ }
+}
+
+// Helper to parse markdown in content
+function parse_markdown($text) {
+ if (empty($text)) return "";
+
+ // First escape HTML
+ $html = htmlspecialchars($text);
+
+ // Code blocks: ```language\ncontent```
+ $code_blocks = [];
+ $html = preg_replace_callback('/```(?:(\w+)\n)?([\s\S]*?)```/', function($matches) use (&$code_blocks) {
+ $lang = $matches[1] ?? 'text';
+ $content = $matches[2];
+ $placeholder = "__CODE_BLOCK_" . count($code_blocks) . "__";
+ $code_blocks[] = '
' . $content . '';
+ return $placeholder;
+ }, $html);
+
+ // Inline code: `content`
+ $inline_codes = [];
+ $html = preg_replace_callback('/`([^`\n]+)`/', function($matches) use (&$inline_codes) {
+ $content = $matches[1];
+ $placeholder = "__INLINE_CODE_" . count($inline_codes) . "__";
+ $inline_codes[] = '' . $content . '';
+ return $placeholder;
+ }, $html);
+
+ // Bold: **text**
+ $html = preg_replace('/\*\*([^*]+)\*\*/', '$1', $html);
+
+ // Italics: *text* or _text_
+ $html = preg_replace('/\*([^*]+)\*/', '$1', $html);
+ $html = preg_replace('/_([^_]+)_/', '$1', $html);
+
+ // Underline: __text__
+ $html = preg_replace('/__([^_]+)__/', '$1', $html);
+
+ // Strikethrough: ~~text~~
+ $html = preg_replace('/~~([^~]+)~~/', '$1', $html); + $html = preg_replace('/^>>> ([\s\S]*$)/', '
$1', $html); + + // Hyperlinks: [text](url) + $html = preg_replace('/\[([^\]]+)\]\(([^)]+)\)/', '$1', $html); + + // Pure links:
)/i', '$2', $html); + $html = preg_replace('/(<\/h[1-3]>|<\/blockquote>)\s*(
\s*)/i', '$1', $html); + + // Re-insert inline code + foreach ($inline_codes as $i => $code) { + $html = str_replace("__INLINE_CODE_$i" . "__", $code, $html); + } + + // Re-insert code blocks + foreach ($code_blocks as $i => $block) { + $html = str_replace("__CODE_BLOCK_$i" . "__", $block, $html); + } + + return $html; +} + +// Helper to parse emotes in content +function parse_emotes($content, $username_to_mention = null) { + static $custom_emotes_cache; + if ($custom_emotes_cache === null) { + try { + $custom_emotes_cache = db()->query("SELECT name, path, code FROM custom_emotes")->fetchAll(); + } catch (Exception $e) { + $custom_emotes_cache = []; + } + } + + $result = parse_markdown($content); + + // Parse mentions if username provided + if ($username_to_mention) { + $mention_pattern = '/@' . preg_quote($username_to_mention, '/') . '\b/'; + $result = preg_replace($mention_pattern, '@' . htmlspecialchars($username_to_mention) . '', $result); + } + + foreach ($custom_emotes_cache as $ce) { + $emote_html = ''; + $result = str_replace($ce['code'], $emote_html, $result); + } + return $result; +} +requireLogin(); + +$user = getCurrentUser(); +if ($user) { + db()->prepare("UPDATE users SET status = 'online' WHERE id = ?")->execute([$user['id']]); + $user['status'] = 'online'; // Update local variable too +} +$current_user_id = $user['id']; +$messages = []; // Initialize messages array +$threads = []; +$rules = []; +$autoroles = []; + +// Fetch servers user is member of +$stmt = db()->prepare(" + SELECT s.* FROM servers s + JOIN server_members sm ON s.id = sm.server_id + WHERE sm.user_id = ? + LIMIT 20 +"); +$stmt->execute([$current_user_id]); +$servers = $stmt->fetchAll(); +$is_dm_view = (isset($_GET['server_id']) && $_GET['server_id'] == 'dms') || !isset($_GET['server_id']) && empty($servers); + +if ($is_dm_view) { + $active_server_id = 'dms'; + // Fetch DM channels + $stmt = db()->prepare(" + SELECT c.id, u.display_name as other_user, u.avatar_url, u.status, u.id as other_user_id + FROM channels c + JOIN channel_members cm1 ON c.id = cm1.channel_id + JOIN channel_members cm2 ON c.id = cm2.channel_id + JOIN users u ON cm2.user_id = u.id + WHERE c.type = 'dm' AND cm1.user_id = ? AND cm2.user_id != ? + "); + $stmt->execute([$current_user_id, $current_user_id]); + $dm_channels = $stmt->fetchAll(); + + $active_channel_id = $_GET['channel_id'] ?? ($dm_channels[0]['id'] ?? 0); + $channel_theme = null; // DMs don't have custom themes for now + + if ($active_channel_id) { + // Fetch DM messages + $stmt = db()->prepare(" + SELECT m.*, u.display_name as username, u.username as login_name, u.avatar_url + FROM messages m + JOIN users u ON m.user_id = u.id + WHERE m.channel_id = ? + ORDER BY m.created_at ASC + LIMIT 50 + "); + $stmt->execute([$active_channel_id]); + $messages = $stmt->fetchAll(); + + $current_channel_name = 'Message privé'; + foreach($dm_channels as $dm) { + if ($dm['id'] == $active_channel_id) { + $current_channel_name = $dm['other_user']; + break; + } + } + } else { + $messages = []; + $current_channel_name = 'Messages privés'; + } + $channels = []; + $members = []; // Members list is different for DMs or hidden +} else { + $active_server_id = $_GET['server_id'] ?? ($servers[0]['id'] ?? 1); + + // Fetch channels + $stmt = db()->prepare("SELECT c.*, (SELECT COUNT(*) FROM channel_events e WHERE e.channel_id = c.id AND (e.is_permanent = 1 OR CONCAT(e.end_date, ' ', e.end_time) >= NOW())) as event_count FROM channels c WHERE c.server_id = ? ORDER BY c.position ASC, c.id ASC"); + $stmt->execute([$active_server_id]); + $all_channels = $stmt->fetchAll(); + + require_once 'includes/permissions.php'; + $channels = []; + foreach($all_channels as $c) { + if (Permissions::canViewChannel($current_user_id, $c['id'])) { + $channels[] = $c; + } + } + + $active_channel_id = $_GET['channel_id'] ?? ($channels[0]['id'] ?? 0); + + // Fetch active channel details for theme + $active_channel = null; + foreach($channels as $c) { + if($c['id'] == $active_channel_id) { + $active_channel = $c; + break; + } + } + + $is_owner = false; + $can_manage_channels = false; + $can_manage_server = false; + $active_server = null; + foreach($servers as $s) { + if($s['id'] == $active_server_id) { + $active_server = $s; + $is_owner = ($s['owner_id'] == $current_user_id); + $can_manage_channels = Permissions::hasPermission($current_user_id, $active_server_id, Permissions::MANAGE_CHANNELS) || $is_owner; + $can_manage_server = Permissions::hasPermission($current_user_id, $active_server_id, Permissions::MANAGE_SERVER) || + Permissions::hasPermission($current_user_id, $active_server_id, Permissions::MANAGE_MESSAGES) || + Permissions::hasPermission($current_user_id, $active_server_id, Permissions::ADMINISTRATOR) || + $is_owner; + + // Event permissions + $can_create_event = Permissions::canDoInChannel($current_user_id, $active_channel_id, Permissions::CREATE_EVENT); + $can_edit_event = Permissions::canDoInChannel($current_user_id, $active_channel_id, Permissions::EDIT_EVENT); + $can_delete_event = Permissions::canDoInChannel($current_user_id, $active_channel_id, Permissions::DELETE_EVENT); + $can_create_announcement = Permissions::canDoInChannel($current_user_id, $active_channel_id, Permissions::CREATE_ANNOUNCEMENT); + $can_create_poll = Permissions::canDoInChannel($current_user_id, $active_channel_id, Permissions::CREATE_POLL); + + break; + } + } + + $channel_theme = $active_server['theme_color'] ?? null; + + $channel_type = $active_channel['type'] ?? 'chat'; + $active_thread_id = $_GET['thread_id'] ?? null; + $active_thread = null; + + if ($active_thread_id) { + $stmt = db()->prepare("SELECT t.*, (SELECT GROUP_CONCAT(CONCAT(ft.name, ':', ft.color) SEPARATOR '|') FROM thread_tags tt JOIN forum_tags ft ON tt.tag_id = ft.id WHERE tt.thread_id = t.id) as tags, u.display_name as username, u.username as login_name FROM forum_threads t JOIN users u ON t.user_id = u.id WHERE t.id = ?"); + $stmt->execute([$active_thread_id]); + $active_thread = $stmt->fetch(); + + if ($active_thread) { + $stmt_t_ids = db()->prepare("SELECT tag_id FROM thread_tags WHERE thread_id = ?"); + $stmt_t_ids->execute([$active_thread_id]); + $active_thread_tag_ids = $stmt_t_ids->fetchAll(PDO::FETCH_COLUMN); + $stmt = db()->prepare(" + SELECT m.*, u.display_name as username, u.username as login_name, u.avatar_url, + (SELECT r.color FROM roles r JOIN user_roles ur ON r.id = ur.role_id WHERE ur.user_id = u.id AND r.server_id = ? ORDER BY r.position DESC LIMIT 1) as role_color, + (SELECT r.icon_url FROM roles r JOIN user_roles ur ON r.id = ur.role_id WHERE ur.user_id = u.id AND r.server_id = ? ORDER BY r.position DESC LIMIT 1) as role_icon + FROM messages m + JOIN users u ON m.user_id = u.id + WHERE m.thread_id = ? + ORDER BY m.created_at ASC + "); + $stmt->execute([$active_server_id, $active_server_id, $active_thread_id]); + $messages = $stmt->fetchAll(); + } + } + + // Always fetch tags if it's a forum channel + if ($channel_type === 'forum') { + $stmt_tags = db()->prepare("SELECT * FROM forum_tags WHERE channel_id = ? ORDER BY name ASC"); + $stmt_tags->execute([$active_channel_id]); + $forum_tags = $stmt_tags->fetchAll(); + + $selected_tag_ids = []; + if (!empty($_GET['tags'])) { + $selected_tag_ids = array_filter(explode(',', $_GET['tags']), 'is_numeric'); + } + } + + if ($active_thread) { + // Thread messages already fetched above + } elseif ($channel_type === 'rules') { + $stmt = db()->prepare("SELECT * FROM channel_rules WHERE channel_id = ? ORDER BY position ASC"); + $stmt->execute([$active_channel_id]); + $rules = $stmt->fetchAll(); + } elseif ($channel_type === 'autorole') { + $stmt = db()->prepare("SELECT ca.*, r.name as role_name FROM channel_autoroles ca JOIN roles r ON ca.role_id = r.id WHERE ca.channel_id = ? ORDER BY ca.id ASC"); + $stmt->execute([$active_channel_id]); + $autoroles = $stmt->fetchAll(); + } elseif ($channel_type === 'forum') { + $tag_where = ""; + $query_params = [$active_server_id, $active_server_id, $active_channel_id]; + + if (!empty($selected_tag_ids)) { + $placeholders = implode(',', array_fill(0, count($selected_tag_ids), '?')); + $tag_where = " AND EXISTS (SELECT 1 FROM thread_tags tt WHERE tt.thread_id = t.id AND tt.tag_id IN ($placeholders))"; + foreach ($selected_tag_ids as $tid) $query_params[] = $tid; + } + + $stmt = db()->prepare(" + SELECT t.*, (SELECT GROUP_CONCAT(CONCAT(ft.name, ':', ft.color) SEPARATOR '|') FROM thread_tags tt JOIN forum_tags ft ON tt.tag_id = ft.id WHERE tt.thread_id = t.id) as tags, u.display_name as username, u.avatar_url, + (SELECT COUNT(*) FROM messages m WHERE m.thread_id = t.id) as message_count, + (SELECT MAX(created_at) FROM messages m WHERE m.thread_id = t.id) as last_message_at, + (SELECT r.color FROM roles r JOIN user_roles ur ON r.id = ur.role_id WHERE ur.user_id = u.id AND r.server_id = ? ORDER BY r.position DESC LIMIT 1) as role_color, + (SELECT r.icon_url FROM roles r JOIN user_roles ur ON r.id = ur.role_id WHERE ur.user_id = u.id AND r.server_id = ? ORDER BY r.position DESC LIMIT 1) as role_icon + FROM forum_threads t + JOIN users u ON t.user_id = u.id + WHERE t.channel_id = ? $tag_where + ORDER BY t.is_pinned DESC, last_message_at DESC + "); + $stmt->execute($query_params); + $threads = $stmt->fetchAll(); + } elseif ($channel_type === 'event') { + // Cleanup expired temporary events + try { + $now = date('Y-m-d H:i:s'); + $stmt_cleanup = db()->prepare("DELETE FROM channel_events WHERE channel_id = ? AND is_permanent = 0 AND CONCAT(end_date, ' ', end_time) < ?"); + $stmt_cleanup->execute([$active_channel_id, $now]); + + $stmt = db()->prepare(" + SELECT e.*, u.display_name as username, u.avatar_url, + (SELECT COUNT(*) FROM event_participations WHERE event_id = e.id) as participation_count, + (SELECT COUNT(*) FROM event_participations WHERE event_id = e.id AND user_id = ?) as is_participating + FROM channel_events e + JOIN users u ON e.user_id = u.id + WHERE e.channel_id = ? + ORDER BY e.is_permanent DESC, e.start_date ASC, e.start_time ASC + "); + $stmt->execute([$current_user_id, $active_channel_id]); + $events = $stmt->fetchAll(); + } catch (Exception $e) { + $events = []; + } + } elseif ($channel_type === 'poll') { + $display_limit = !empty($active_channel['message_limit']) ? (int)$active_channel['message_limit'] : 50; + $stmt = db()->prepare(" + SELECT m.*, u.display_name as username, u.avatar_url, + (SELECT r.color FROM roles r JOIN user_roles ur ON r.id = ur.role_id WHERE ur.user_id = u.id AND r.server_id = ? ORDER BY r.position DESC LIMIT 1) as role_color, + (SELECT r.icon_url FROM roles r JOIN user_roles ur ON r.id = ur.role_id WHERE ur.user_id = u.id AND r.server_id = ? ORDER BY r.position DESC LIMIT 1) as role_icon + FROM messages m + JOIN users u ON m.user_id = u.id + WHERE m.channel_id = ? AND m.thread_id IS NULL + ORDER BY m.created_at DESC + LIMIT " . $display_limit . " + "); + $stmt->execute([$active_server_id, $active_server_id, $active_channel_id]); + $messages = $stmt->fetchAll(); + + // Fetch votes for each poll message + foreach ($messages as &$m) { + $stmt_v = db()->prepare("SELECT pv.option_index, COUNT(*) as vote_count, GROUP_CONCAT(u.display_name SEPARATOR ', ') as user_names, GROUP_CONCAT(pv.user_id) as user_ids FROM poll_votes pv JOIN users u ON pv.user_id = u.id WHERE pv.message_id = ? GROUP BY pv.option_index"); + $stmt_v->execute([$m['id']]); + $m['votes_data'] = $stmt_v->fetchAll(); + } + unset($m); + } else { + // Fetch messages for normal chat channels + $display_limit = !empty($active_channel['message_limit']) ? (int)$active_channel['message_limit'] : 50; + + $stmt = db()->prepare(" + SELECT m.*, u.display_name as username, u.username as login_name, u.avatar_url, + (SELECT r.color FROM roles r JOIN user_roles ur ON r.id = ur.role_id WHERE ur.user_id = u.id AND r.server_id = ? ORDER BY r.position DESC LIMIT 1) as role_color, + (SELECT r.icon_url FROM roles r JOIN user_roles ur ON r.id = ur.role_id WHERE ur.user_id = u.id AND r.server_id = ? ORDER BY r.position DESC LIMIT 1) as role_icon + FROM messages m + JOIN users u ON m.user_id = u.id + WHERE m.channel_id = ? AND m.thread_id IS NULL + ORDER BY m.created_at ASC + LIMIT " . $display_limit . " + "); + $stmt->execute([$active_server_id, $active_server_id, $active_channel_id]); + $messages = $stmt->fetchAll(); + } + + $current_channel_name = 'general'; + foreach($channels as $c) { + if($c['id'] == $active_channel_id) { + $current_channel_name = $c['name']; + if ($c['type'] === 'event' && !empty($c['event_count'])) { + $current_channel_name .= " (" . $c['event_count'] . ")"; + } + } + } + + // Fetch voice sessions for the sidebar + $stmt_vs = 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 = (int) floor(microtime(true) * 1000) - 15000; + $stmt_vs->execute([$stale_db_time]); + $voice_sessions = $stmt_vs->fetchAll(); + $voice_users_by_channel = []; + foreach($voice_sessions as $vs) { + $voice_users_by_channel[$vs['channel_id']][] = $vs; + } + + // Fetch members + $stmt = db()->prepare(" + SELECT u.id, u.display_name as username, u.username as login_name, u.avatar_url, u.status, + (SELECT GROUP_CONCAT(r.id) FROM roles r JOIN user_roles ur ON r.id = ur.role_id WHERE ur.user_id = u.id AND r.server_id = ?) as role_ids, + (SELECT r.color FROM roles r JOIN user_roles ur ON r.id = ur.role_id WHERE ur.user_id = u.id AND r.server_id = ? ORDER BY r.position DESC LIMIT 1) as role_color, + (SELECT r.icon_url FROM roles r JOIN user_roles ur ON r.id = ur.role_id WHERE ur.user_id = u.id AND r.server_id = ? ORDER BY r.position DESC LIMIT 1) as role_icon, + (SELECT GROUP_CONCAT(CONCAT(sb.name, '|', sb.image_url) SEPARATOR ':::') FROM member_badges mb JOIN server_badges sb ON mb.badge_id = sb.id WHERE mb.user_id = u.id AND mb.server_id = ?) as badge_data + FROM users u + JOIN server_members sm ON u.id = sm.user_id + WHERE sm.server_id = ? + "); + $stmt->execute([$active_server_id, $active_server_id, $active_server_id, $active_server_id, $active_server_id]); + $all_server_members = $stmt->fetchAll(); + + $members = []; + foreach($all_server_members as $m) { + if (Permissions::canViewChannel($m['id'], $active_channel_id)) { + $members[] = $m; + } + } + + // Fetch all server roles + $stmt = db()->prepare("SELECT * FROM roles WHERE server_id = ? ORDER BY position DESC"); + $stmt->execute([$active_server_id]); + $server_roles = $stmt->fetchAll(); +} + +// SEO & Env tags +$projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Corvara - Messaging App'; +$projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? ''; +?> + + + + + +
# | + + + + + + + + + + + + + + + + + + + ++ + + + + + + ++ + + + + + +++ ++ + '; + elseif ($active_channel['type'] === 'rules') echo ''; + elseif ($active_channel['type'] === 'autorole') echo ''; + elseif ($active_channel['type'] === 'forum') echo ''; + elseif ($active_channel['type'] === 'voice') echo ''; + elseif ($active_channel['type'] === 'poll') echo ''; + else echo ''; + + if (!empty($active_channel['icon'])) { + echo ' ' . renderRoleIcon($active_channel['icon'], '16px') . ''; + } + } + ?> + + + ++ + + + + ++ + +++++ + ++ ++ + Vous ne disposez pas de la permission d\'envoyer des messages dans ce salon. +'; + } else { + $allow_files = true; + foreach($channels as $c) { + if($c['id'] == $active_channel_id) { + $allow_files = (bool)$c['allow_file_sharing']; + break; + } + } + ?> + + + +++ + ++ ++++ + + + + + + + ++ ++++ + ++ +++++ ++++ + ++ ++++ + ++ ++++ + ++ ++++ + ++ ++++ + ++ ++++ + ++ ++++ + ++ ++++ + ++ ++++ + ++ ++++ + ++ ++++ + ++ ++++ + + + + + ++ ++++ + ++ ++++ + ++ ++++ + + + + diff --git a/Temp/voice_tab.html b/Temp/voice_tab.html new file mode 100644 index 0000000..0057774 --- /dev/null +++ b/Temp/voice_tab.html @@ -0,0 +1,106 @@ + ++ +++diff --git a/api_v1_user.php b/api_v1_user.php index 03732da..7f01427 100644 --- a/api_v1_user.php +++ b/api_v1_user.php @@ -28,10 +28,11 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { $voice_vox_threshold = isset($_POST['voice_vox_threshold']) ? (float)$_POST['voice_vox_threshold'] : ($user['voice_vox_threshold'] ?? 0.1); $voice_echo_cancellation = isset($_POST['voice_echo_cancellation']) ? (int)$_POST['voice_echo_cancellation'] : ($user['voice_echo_cancellation'] ?? 1); $voice_noise_suppression = isset($_POST['voice_noise_suppression']) ? (int)$_POST['voice_noise_suppression'] : ($user['voice_noise_suppression'] ?? 1); + $voice_advanced_filters = isset($_POST['voice_advanced_filters']) ? (int)$_POST['voice_advanced_filters'] : ($user['voice_advanced_filters'] ?? 1); try { - $stmt = db()->prepare("UPDATE users SET display_name = ?, avatar_url = ?, dnd_mode = ?, sound_notifications = ?, theme = ?, voice_mode = ?, voice_ptt_key = ?, voice_vox_threshold = ?, voice_echo_cancellation = ?, voice_noise_suppression = ? WHERE id = ?"); - $stmt->execute([$display_name, $avatar_url, $dnd_mode, $sound_notifications, $theme, $voice_mode, $voice_ptt_key, $voice_vox_threshold, $voice_echo_cancellation, $voice_noise_suppression, $user['id']]); + $stmt = db()->prepare("UPDATE users SET display_name = ?, avatar_url = ?, dnd_mode = ?, sound_notifications = ?, theme = ?, voice_mode = ?, voice_ptt_key = ?, voice_vox_threshold = ?, voice_echo_cancellation = ?, voice_noise_suppression = ?, voice_advanced_filters = ? WHERE id = ?"); + $stmt->execute([$display_name, $avatar_url, $dnd_mode, $sound_notifications, $theme, $voice_mode, $voice_ptt_key, $voice_vox_threshold, $voice_echo_cancellation, $voice_noise_suppression, $voice_advanced_filters, $user['id']]); echo json_encode(['success' => true]); } catch (Exception $e) { @@ -40,4 +41,4 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { exit; } -echo json_encode(['success' => false, 'error' => 'Invalid request']); +echo json_encode(['success' => false, 'error' => 'Invalid request']); \ No newline at end of file diff --git a/assets/js/main.js b/assets/js/main.js index a8c7104..ddb7f8f 100644 --- a/assets/js/main.js +++ b/assets/js/main.js @@ -480,7 +480,7 @@ document.addEventListener('DOMContentLoaded', () => { let voiceHandler; if (typeof VoiceChannel !== 'undefined') { - voiceHandler = new VoiceChannel(null, window.voiceSettings); + voiceHandler = new VoiceChannel(null, window.voiceParamètres); window.voiceHandler = voiceHandler; console.log('VoiceHandler initialized'); diff --git a/assets/js/voice.js b/assets/js/voice.js index 704299b..ad5d644 100644 --- a/assets/js/voice.js +++ b/assets/js/voice.js @@ -12,7 +12,8 @@ class VoiceChannel { inputVolume: 1.0, outputVolume: 1.0, echoCancellation: true, - noiseSuppression: true + noiseSuppression: true, + advancedFilters: true }; console.log('VoiceChannel constructor called with settings:', this.settings); this.localStream = null; @@ -69,6 +70,36 @@ class VoiceChannel { }, 200); } + getAudioConstraints() { + const useAdvanced = this.settings.advancedFilters !== false; + + const constraints = { + echoCancellation: { ideal: this.settings.echoCancellation }, + noiseSuppression: { ideal: this.settings.noiseSuppression }, + autoGainControl: { ideal: useAdvanced }, + // Chromium-specific flags + googEchoCancellation: { ideal: this.settings.echoCancellation }, + googAutoGainControl: { ideal: useAdvanced }, + googNoiseSuppression: { ideal: this.settings.noiseSuppression }, + googHighpassFilter: { ideal: useAdvanced }, + googTypingNoiseDetection: { ideal: true }, + googAudioMirroring: { ideal: false }, + googNoiseReduction: { ideal: this.settings.noiseSuppression }, + googAutoGainControl2: { ideal: useAdvanced }, + googAudioMirroring: { ideal: false }, + // Standard constraints + channelCount: { ideal: 1 }, + sampleRate: { ideal: 48000 }, + sampleSize: { ideal: 16 } + }; + + if (this.settings.inputDevice !== 'default') { + constraints.deviceId = { exact: this.settings.inputDevice }; + } + + return constraints; + } + setupPTTListeners() { window.addEventListener('keydown', (e) => { // Ignore if in input field @@ -197,16 +228,9 @@ class VoiceChannel { try { console.log('Requesting microphone access with device:', this.settings.inputDevice); const constraints = { - audio: { - echoCancellation: this.settings.echoCancellation, - noiseSuppression: this.settings.noiseSuppression, - autoGainControl: true - }, + audio: this.getAudioConstraints(), video: false }; - if (this.settings.inputDevice !== 'default') { - constraints.audio.deviceId = { exact: this.settings.inputDevice }; - } this.localStream = await navigator.mediaDevices.getUserMedia(constraints); console.log('Microphone access granted'); this.setMute(false); // Join unmuted by default (self-mute off) @@ -685,16 +709,9 @@ class VoiceChannel { this.settings.inputDevice = deviceId; if (this.currentChannelId && this.localStream) { const constraints = { - audio: { - echoCancellation: this.settings.echoCancellation, - noiseSuppression: this.settings.noiseSuppression, - autoGainControl: true - }, + audio: this.getAudioConstraints(), video: false }; - if (deviceId !== 'default') { - constraints.audio.deviceId = { exact: deviceId }; - } const newStream = await navigator.mediaDevices.getUserMedia(constraints); const newTrack = newStream.getAudioTracks()[0]; @@ -721,18 +738,11 @@ class VoiceChannel { async updateAudioConstraints() { if (this.currentChannelId && this.localStream) { - console.log('Updating audio constraints:', this.settings.echoCancellation, this.settings.noiseSuppression); + console.log('Updating audio constraints:', this.settings.echoCancellation, this.settings.noiseSuppression, this.settings.advancedFilters); const constraints = { - audio: { - echoCancellation: this.settings.echoCancellation, - noiseSuppression: this.settings.noiseSuppression, - autoGainControl: true - }, + audio: this.getAudioConstraints(), video: false }; - if (this.settings.inputDevice !== 'default') { - constraints.audio.deviceId = { exact: this.settings.inputDevice }; - } try { const newStream = await navigator.mediaDevices.getUserMedia(constraints); const newTrack = newStream.getAudioTracks()[0]; diff --git a/db/migrations/20260325_add_voice_advanced_filters.sql b/db/migrations/20260325_add_voice_advanced_filters.sql new file mode 100644 index 0000000..119c915 --- /dev/null +++ b/db/migrations/20260325_add_voice_advanced_filters.sql @@ -0,0 +1 @@ +ALTER TABLE users ADD COLUMN voice_advanced_filters TINYINT(1) DEFAULT 1; diff --git a/index.php b/index.php index 9899109..63d21a5 100644 --- a/index.php +++ b/index.php @@ -482,6 +482,7 @@ $projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? ''; voxThreshold: , echoCancellation: , noiseSuppression: , + advancedFilters: , inputDevice: localStorage.getItem('voice_input_device') || 'default', outputDevice: localStorage.getItem('voice_output_device') || 'default', inputVolume: parseFloat(localStorage.getItem('voice_input_volume') || 1.0), @@ -1861,6 +1862,7 @@ $projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? ''; +Paramètres de voix
+ +++ ++ + +++ + +++ + + +++ + + ++++ ++++ > + ++Réduit l'écho causé par vos haut-parleurs captés par votre micro.++++ > + ++Filtre les bruits de fond comme les ventilateurs ou les clics de clavier.++++ > + ++Active des filtres supplémentaires (passe-haut, gain auto optimisé) pour une qualité vocale supérieure sur Chrome.++ ++ +++ + + + ++ onchange="togglePTTParamètres('vox')"> + +++ onchange="togglePTTParamètres('ptt')"> + +++++ ++++Microphone Access+Voice channels require microphone permission. If you don't hear anything, check your browser's site settings.+Paramètres de voix
@@ -1904,6 +1906,13 @@ $projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';Filtre les bruits de fond comme les ventilateurs ou les clics de clavier.+++ > + ++Active des filtres supplémentaires (passe-haut, gain auto optimisé) pour une qualité vocale supérieure sur Chrome.+@@ -1959,8 +1968,6 @@ $projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';- -Whisper Configurations
@@ -2182,11 +2189,13 @@ async function handleSaveUserParamètres(btn) { const soundNotifications = document.getElementById('sound-notifications-switch')?.checked ? '1' : '0'; const echoCancellation = document.getElementById('echo-cancellation-switch')?.checked ? '1' : '0'; const noiseSuppression = document.getElementById('noise-suppression-switch')?.checked ? '1' : '0'; + const advancedFilters = document.getElementById('advanced-filters-switch')?.checked ? '1' : '0'; formData.set('dnd_mode', dndMode); formData.set('sound_notifications', soundNotifications); formData.set('voice_echo_cancellation', echoCancellation); formData.set('voice_noise_suppression', noiseSuppression); + formData.set('voice_advanced_filters', advancedFilters); // Explicitly get theme and voice_mode to ensure they are captured const themeInput = form.querySelector('input[name="theme"]:checked'); @@ -2221,6 +2230,7 @@ async function handleSaveUserParamètres(btn) { window.voiceHandler.settings.voxThreshold = voxThreshold; window.voiceHandler.settings.echoCancellation = echoCancellation === '1'; window.voiceHandler.settings.noiseSuppression = noiseSuppression === '1'; + window.voiceHandler.settings.advancedFilters = advancedFilters === '1'; // New settings const inputDevice = document.getElementById('voice_input_device')?.value;