document.addEventListener('DOMContentLoaded', () => { const fileUpload = document.getElementById('file-upload'); const chatForm = document.getElementById('chat-form'); const chatInput = document.getElementById('chat-input'); const messagesList = document.getElementById('messages-list'); const typingIndicator = document.getElementById('typing-indicator'); // Emoji list for reactions const EMOJIS = ['👍', '❤️', '😂', '😮', '😢', '🔥', '✅', '🚀']; // Scroll to bottom messagesList.scrollTop = messagesList.scrollHeight; const currentChannel = new URLSearchParams(window.location.search).get('channel_id') || 1; let typingTimeout; // Notification Permission if ("Notification" in window && Notification.permission === "default") { Notification.requestPermission(); } // WebSocket for real-time let ws; let voiceHandler; function connectWS() { try { ws = new WebSocket('ws://' + window.location.hostname + ':8080'); if (typeof VoiceChannel !== 'undefined') { voiceHandler = new VoiceChannel(ws); } ws.onmessage = (e) => { const msg = JSON.parse(e.data); // Voice signaling if (msg.type && msg.type.startsWith('voice_')) { if (voiceHandler) voiceHandler.handleSignaling(msg); return; } if (msg.type === 'message') { const data = JSON.parse(msg.data); if (data.channel_id == currentChannel) { appendMessage(data); messagesList.scrollTop = messagesList.scrollHeight; // Desktop Notifications for mentions if (data.content.includes(`@${window.currentUsername}`) && data.user_id != window.currentUserId) { if (Notification.permission === "granted" && !window.isDndMode) { new Notification(`Mention in #${window.currentChannelName}`, { body: `${data.username}: ${data.content}`, icon: data.avatar_url || '' }); } } } } else if (msg.type === 'typing') { if (msg.channel_id == currentChannel && msg.user_id != window.currentUserId) { showTyping(msg.username); } } else if (msg.type === 'reaction') { updateReactionUI(msg.message_id, msg.reactions); } else if (msg.type === 'message_edit') { const el = document.querySelector(`.message-item[data-id="${msg.message_id}"] .message-text`); if (el) el.innerHTML = msg.content.replace(/\n/g, '
'); } else if (msg.type === 'message_delete') { document.querySelector(`.message-item[data-id="${msg.message_id}"]`)?.remove(); } else if (msg.type === 'presence') { updatePresenceUI(msg.user_id, msg.status); } }; ws.onopen = () => { ws.send(JSON.stringify({ type: 'presence', user_id: window.currentUserId, status: 'online' })); }; ws.onclose = () => setTimeout(connectWS, 3000); } catch (e) { console.warn('WebSocket connection failed.'); } } connectWS(); function showTyping(username) { typingIndicator.textContent = `${username} is typing...`; clearTimeout(typingTimeout); typingTimeout = setTimeout(() => { typingIndicator.textContent = ''; }, 3000); } chatInput.addEventListener('input', () => { if (ws && ws.readyState === WebSocket.OPEN) { ws.send(JSON.stringify({ type: 'typing', channel_id: currentChannel, user_id: window.currentUserId, username: window.currentUsername })); } }); chatForm.addEventListener('submit', (e) => { e.preventDefault(); const content = chatInput.value.trim(); const file = fileUpload.files[0]; if (!content && !file) return; chatInput.value = ''; const formData = new FormData(); formData.append('content', content); formData.append('channel_id', currentChannel); const progressContainer = document.getElementById('upload-progress-container'); const progressBar = document.getElementById('upload-progress-bar'); const progressPercent = document.getElementById('upload-percentage'); const progressFilename = document.getElementById('upload-filename'); if (file) { formData.append('file', file); fileUpload.value = ''; // Clear file input // Show progress bar progressContainer.style.display = 'block'; progressFilename.textContent = `Uploading: ${file.name}`; progressBar.style.width = '0%'; progressPercent.textContent = '0%'; } const xhr = new XMLHttpRequest(); xhr.open('POST', 'api_v1_messages.php', true); xhr.upload.onprogress = (ev) => { if (ev.lengthComputable && file) { const percent = Math.round((ev.loaded / ev.total) * 100); progressBar.style.width = percent + '%'; progressPercent.textContent = percent + '%'; } }; xhr.onload = () => { if (xhr.status === 200) { const result = JSON.parse(xhr.responseText); if (result.success) { if (ws && ws.readyState === WebSocket.OPEN) { ws.send(JSON.stringify({ type: 'message', data: JSON.stringify({ ...result.message, channel_id: currentChannel }) })); } else { appendMessage(result.message); messagesList.scrollTop = messagesList.scrollHeight; } } else { alert(result.error || 'Failed to send message'); } } progressContainer.style.display = 'none'; }; xhr.onerror = () => { console.error('XHR Error'); progressContainer.style.display = 'none'; alert('An error occurred during the upload.'); }; xhr.send(formData); }); // Handle Reaction Clicks document.addEventListener('click', (e) => { const badge = e.target.closest('.reaction-badge'); if (badge) { const msgId = badge.parentElement.dataset.messageId; const emoji = badge.dataset.emoji; toggleReaction(msgId, emoji); return; } const addBtn = e.target.closest('.add-reaction-btn'); if (addBtn) { const msgId = addBtn.parentElement.dataset.messageId; showEmojiPicker(addBtn, msgId); return; } // Close picker if click outside if (!e.target.closest('.emoji-picker')) { const picker = document.querySelector('.emoji-picker'); if (picker) picker.remove(); } }); async function toggleReaction(messageId, emoji) { try { const resp = await fetch('api_v1_reactions.php', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ message_id: messageId, emoji: emoji }) }); const result = await resp.json(); if (result.success) { if (ws && ws.readyState === WebSocket.OPEN) { ws.send(JSON.stringify({ type: 'reaction', message_id: messageId, reactions: result.reactions })); } updateReactionUI(messageId, result.reactions); } } catch (e) { console.error(e); } } function showEmojiPicker(anchor, messageId) { document.querySelector('.emoji-picker')?.remove(); const picker = document.createElement('div'); picker.className = 'emoji-picker'; EMOJIS.forEach(emoji => { const span = document.createElement('span'); span.textContent = emoji; span.onclick = () => { toggleReaction(messageId, emoji); picker.remove(); }; picker.appendChild(span); }); document.body.appendChild(picker); const rect = anchor.getBoundingClientRect(); picker.style.top = `${rect.top - picker.offsetHeight - 5}px`; picker.style.left = `${rect.left}px`; } function updateReactionUI(messageId, reactions) { const container = document.querySelector(`.message-reactions[data-message-id="${messageId}"]`); if (!container) return; const addBtn = container.querySelector('.add-reaction-btn'); container.innerHTML = ''; reactions.forEach(r => { const badge = document.createElement('span'); const userList = r.users.split(','); const active = userList.includes(String(window.currentUserId)); badge.className = `reaction-badge ${active ? 'active' : ''}`; badge.dataset.emoji = r.emoji; badge.innerHTML = `${r.emoji} ${r.count}`; container.appendChild(badge); }); container.appendChild(addBtn); } function updatePresenceUI(userId, status) { const memberItem = document.querySelector(`.start-dm-btn[data-user-id="${userId}"] .message-avatar`); if (memberItem) { let indicator = memberItem.querySelector('.presence-indicator'); if (!indicator) { indicator = document.createElement('div'); indicator.className = 'presence-indicator'; memberItem.appendChild(indicator); } indicator.style.position = 'absolute'; indicator.style.bottom = '0'; indicator.style.right = '0'; indicator.style.width = '10px'; indicator.style.height = '10px'; indicator.style.borderRadius = '50%'; indicator.style.border = '2px solid var(--bg-members)'; indicator.style.backgroundColor = status === 'online' ? '#23a559' : '#80848e'; } } // Voice if (voiceHandler) { document.querySelectorAll('.voice-item').forEach(item => { item.addEventListener('click', () => { const cid = item.dataset.channelId; if (voiceHandler.currentChannelId == cid) { voiceHandler.leave(); item.classList.remove('active'); } else { voiceHandler.join(cid); document.querySelectorAll('.voice-item').forEach(i => i.classList.remove('active')); item.classList.add('active'); } }); }); } // Message Actions (Edit/Delete) document.addEventListener('click', async (e) => { const editBtn = e.target.closest('.action-btn.edit'); if (editBtn) { const msgId = editBtn.dataset.id; const msgItem = editBtn.closest('.message-item'); const textEl = msgItem.querySelector('.message-text'); const originalContent = textEl.innerText; const input = document.createElement('input'); input.type = 'text'; input.className = 'form-control bg-dark text-white'; input.value = originalContent; textEl.innerHTML = ''; textEl.appendChild(input); input.focus(); input.onkeydown = async (ev) => { if (ev.key === 'Enter') { const newContent = input.value.trim(); if (newContent && newContent !== originalContent) { const resp = await fetch('api_v1_messages.php', { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ id: msgId, content: newContent }) }); if ((await resp.json()).success) { textEl.innerHTML = newContent.replace(/\n/g, '
'); ws?.send(JSON.stringify({ type: 'message_edit', message_id: msgId, content: newContent })); } } else { textEl.innerHTML = originalContent.replace(/\n/g, '
'); } } else if (ev.key === 'Escape') { textEl.innerHTML = originalContent.replace(/\n/g, '
'); } }; return; } const deleteBtn = e.target.closest('.action-btn.delete'); if (deleteBtn) { if (!confirm('Delete this message?')) return; const msgId = deleteBtn.dataset.id; const resp = await fetch('api_v1_messages.php', { method: 'DELETE', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ id: msgId }) }); if ((await resp.json()).success) { deleteBtn.closest('.message-item').remove(); ws?.send(JSON.stringify({ type: 'message_delete', message_id: msgId })); } return; } const pinBtn = e.target.closest('.action-btn.pin'); if (pinBtn) { const msgId = pinBtn.dataset.id; const isPinned = pinBtn.dataset.pinned == '1'; const action = isPinned ? 'unpin' : 'pin'; const resp = await fetch('api_v1_messages.php', { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ id: msgId, action: action }) }); const result = await resp.json(); if (result.success) { location.reload(); // Simplest way to reflect changes across UI } return; } const pinnedMessagesBtn = document.getElementById('pinned-messages-btn'); if (e.target.closest('#pinned-messages-btn')) { const container = document.getElementById('pinned-messages-container'); container.innerHTML = '
Loading pinned messages...
'; const modal = new bootstrap.Modal(document.getElementById('pinnedMessagesModal')); modal.show(); const resp = await fetch(`api_v1_messages.php?channel_id=${currentChannel}&pinned=1`); const data = await resp.json(); if (data.success && data.messages.length > 0) { container.innerHTML = ''; data.messages.forEach(msg => { const div = document.createElement('div'); div.className = 'message-item p-2 border-bottom border-secondary'; div.style.backgroundColor = 'transparent'; div.innerHTML = `
${escapeHTML(msg.username)} ${msg.time}
${escapeHTML(msg.content).replace(/\n/g, '
')}
`; container.appendChild(div); }); } else { container.innerHTML = '
No pinned messages in this channel.
'; } return; } // Start DM const dmBtn = e.target.closest('.start-dm-btn'); if (dmBtn) { const userId = dmBtn.dataset.userId; const formData = new FormData(); formData.append('user_id', userId); const resp = await fetch('api_v1_dms.php', { method: 'POST', body: formData }); const result = await resp.json(); if (result.success) { window.location.href = `?server_id=dms&channel_id=${result.channel_id}`; } } }); // Global Search const searchInput = document.getElementById('global-search'); const searchType = document.getElementById('search-type'); const searchResults = document.getElementById('search-results'); searchInput?.addEventListener('input', async () => { const q = searchInput.value.trim(); const type = searchType.value; if (q.length < 2) { searchResults.style.display = 'none'; return; } const resp = await fetch(`api_v1_search.php?q=${encodeURIComponent(q)}&type=${type}&channel_id=${currentChannel}`); const data = await resp.json(); if (data.success && data.results.length > 0) { searchResults.innerHTML = ''; data.results.forEach(res => { const item = document.createElement('div'); item.className = 'search-result-item d-flex align-items-center gap-2'; if (type === 'users') { item.innerHTML = `
${res.username}
Click to start conversation
`; item.onclick = () => { const formData = new FormData(); formData.append('user_id', res.id); fetch('api_v1_dms.php', { method: 'POST', body: formData }) .then(r => r.json()) .then(resDM => { if (resDM.success) window.location.href = `?server_id=dms&channel_id=${resDM.channel_id}`; }); }; } else { item.innerHTML = `
${res.username}
${res.content}
`; } searchResults.appendChild(item); }); searchResults.style.display = 'block'; } else { searchResults.innerHTML = '
No results found
'; searchResults.style.display = 'block'; } }); // Channel Permissions Management const channelPermissionsTabBtn = document.getElementById('channel-permissions-tab-btn'); const channelPermissionsList = document.getElementById('channel-permissions-list'); const addPermRoleList = document.getElementById('add-permission-role-list'); channelPermissionsTabBtn?.addEventListener('click', async () => { const channelId = document.getElementById('edit-channel-id').value; loadChannelPermissions(channelId); loadRolesForPermissions(channelId); }); async function loadChannelPermissions(channelId) { channelPermissionsList.innerHTML = '
Loading permissions...
'; const resp = await fetch(`api_v1_channel_permissions.php?channel_id=${channelId}`); const data = await resp.json(); if (data.success) { renderChannelPermissions(channelId, data.permissions); } } async function loadRolesForPermissions(channelId) { addPermRoleList.innerHTML = ''; const resp = await fetch(`api_v1_roles.php?server_id=${activeServerId}`); const data = await resp.json(); if (data.success) { data.roles.forEach(role => { const li = document.createElement('li'); li.innerHTML = `
${role.name}
`; li.onclick = async (e) => { e.preventDefault(); await fetch('api_v1_channel_permissions.php', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ channel_id: channelId, role_id: role.id, allow: 0, deny: 0 }) }); loadChannelPermissions(channelId); }; addPermRoleList.appendChild(li); }); } } function renderChannelPermissions(channelId, permissions) { channelPermissionsList.innerHTML = ''; if (permissions.length === 0) { channelPermissionsList.innerHTML = '
No role overrides.
'; return; } permissions.forEach(p => { const item = document.createElement('div'); item.className = 'list-group-item bg-transparent text-white border-secondary p-2'; item.innerHTML = `
${p.role_name}
`; channelPermissionsList.appendChild(item); }); } channelPermissionsList?.addEventListener('click', async (e) => { const channelId = document.getElementById('edit-channel-id').value; if (e.target.classList.contains('remove-perm-btn')) { const roleId = e.target.dataset.roleId; await fetch('api_v1_channel_permissions.php', { method: 'DELETE', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ channel_id: channelId, role_id: roleId }) }); loadChannelPermissions(channelId); } }); channelPermissionsList?.addEventListener('change', async (e) => { if (e.target.classList.contains('perm-select')) { const channelId = document.getElementById('edit-channel-id').value; const roleId = e.target.dataset.roleId; const val = e.target.value; let allow = 0, deny = 0; if (val === 'allow') allow = 1; if (val === 'deny') deny = 1; await fetch('api_v1_channel_permissions.php', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ channel_id: channelId, role_id: roleId, allow, deny }) }); } }); document.addEventListener('click', (e) => { if (!e.target.closest('.search-container')) { searchResults.style.display = 'none'; } }); // Roles Management const channelSettingsBtns = document.querySelectorAll('.channel-settings-btn'); channelSettingsBtns.forEach(btn => { btn.addEventListener('click', () => { const modal = document.getElementById('editChannelModal'); modal.querySelector('#edit-channel-id').value = btn.dataset.id; modal.querySelector('#edit-channel-name').value = btn.dataset.name; modal.querySelector('#edit-channel-files').checked = btn.dataset.files == '1'; modal.querySelector('#edit-channel-limit').value = btn.dataset.limit || ''; modal.querySelector('#edit-channel-theme').value = btn.dataset.theme || '#5865f2'; modal.querySelector('#delete-channel-id').value = btn.dataset.id; }); }); // Clear Channel History const clearHistoryBtn = document.getElementById('clear-channel-history-btn'); clearHistoryBtn?.addEventListener('click', async () => { const channelId = document.getElementById('edit-channel-id').value; if (!confirm('Voulez-vous vraiment vider tout l\'historique de ce salon ? Cette action est irréversible.')) return; try { const formData = new FormData(); formData.append('channel_id', channelId); const resp = await fetch('api_v1_clear_channel.php', { method: 'POST', body: formData }); const result = await resp.json(); if (result.success) { location.reload(); } else { alert(result.error || 'Erreur lors du nettoyage de l\'historique'); } } catch (e) { console.error(e); } }); // Roles Management const rolesTabBtn = document.getElementById('roles-tab-btn'); const rolesList = document.getElementById('roles-list'); const addRoleBtn = document.getElementById('add-role-btn'); const activeServerId = new URLSearchParams(window.location.search).get('server_id') || 1; rolesTabBtn?.addEventListener('click', loadRoles); async function loadRoles() { rolesList.innerHTML = '
Loading roles...
'; try { const resp = await fetch(`api_v1_roles.php?server_id=${activeServerId}`); const data = await resp.json(); if (data.success) { renderRoles(data.roles); } } catch (e) { console.error(e); } } function renderRoles(roles) { rolesList.innerHTML = ''; roles.forEach(role => { const item = document.createElement('div'); item.className = 'list-group-item bg-transparent text-white border-secondary d-flex justify-content-between align-items-center p-2'; item.innerHTML = `
${role.name}
`; rolesList.appendChild(item); }); } addRoleBtn?.addEventListener('click', async () => { const name = prompt('Role name:'); if (!name) return; const color = prompt('Role color (hex):', '#99aab5'); try { const resp = await fetch('api_v1_roles.php', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ action: 'create', server_id: activeServerId, name, color }) }); if ((await resp.json()).success) loadRoles(); } catch (e) { console.error(e); } }); rolesList?.addEventListener('click', async (e) => { if (e.target.classList.contains('delete-role-btn')) { if (!confirm('Delete this role?')) return; const roleId = e.target.dataset.id; const resp = await fetch('api_v1_roles.php', { method: 'DELETE', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ id: roleId }) }); if ((await resp.json()).success) loadRoles(); } if (e.target.classList.contains('edit-role-btn')) { const roleId = e.target.dataset.id; const name = prompt('New name:'); const color = prompt('New color (hex):'); if (!name || !color) return; const resp = await fetch('api_v1_roles.php', { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ id: roleId, name, color, permissions: 0 }) }); if ((await resp.json()).success) loadRoles(); } }); // Webhooks Management const webhooksTabBtn = document.getElementById('webhooks-tab-btn'); const webhooksList = document.getElementById('webhooks-list'); const addWebhookBtn = document.getElementById('add-webhook-btn'); webhooksTabBtn?.addEventListener('click', loadWebhooks); async function loadWebhooks() { webhooksList.innerHTML = '
Loading webhooks...
'; try { const resp = await fetch(`api_v1_webhook.php?server_id=${activeServerId}`); const data = await resp.json(); if (data.success) { renderWebhooks(data.webhooks); } } catch (e) { console.error(e); } } function renderWebhooks(webhooks) { webhooksList.innerHTML = ''; if (webhooks.length === 0) { webhooksList.innerHTML = '
No webhooks found.
'; return; } webhooks.forEach(wh => { const item = document.createElement('div'); item.className = 'list-group-item bg-transparent text-white border-secondary p-2 mb-2'; const url = `${window.location.origin}/api_v1_webhook.php?token=${wh.token}`; item.innerHTML = `
${wh.name}
Channel: #${wh.channel_name}
`; webhooksList.appendChild(item); }); } addWebhookBtn?.addEventListener('click', async () => { const name = prompt('Webhook name:', 'Bot Name'); if (!name) return; // Fetch channels for this server to let user pick one const respChannels = await fetch(`api_v1_channels.php?server_id=${activeServerId}`); const dataChannels = await respChannels.json(); if (!dataChannels.length) return alert('Create a channel first.'); const channelId = prompt('Enter Channel ID:\n' + dataChannels.map(c => `${c.id}: #${c.name}`).join('\n')); if (!channelId) return; try { const resp = await fetch('api_v1_webhook.php', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ channel_id: channelId, name: name }) }); if ((await resp.json()).success) loadWebhooks(); } catch (e) { console.error(e); } }); webhooksList?.addEventListener('click', async (e) => { if (e.target.classList.contains('delete-webhook-btn')) { if (!confirm('Delete this webhook?')) return; const whId = e.target.dataset.id; const resp = await fetch('api_v1_webhook.php', { method: 'DELETE', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ id: whId }) }); if ((await resp.json()).success) loadWebhooks(); } }); // Stats Management const statsTabBtn = document.getElementById('stats-tab-btn'); statsTabBtn?.addEventListener('click', loadStats); async function loadStats() { try { const resp = await fetch(`api_v1_stats.php?server_id=${activeServerId}`); const data = await resp.json(); if (data.success) { document.getElementById('stat-members').textContent = data.stats.total_members; document.getElementById('stat-messages').textContent = data.stats.total_messages; const topUsersList = document.getElementById('top-users-list'); topUsersList.innerHTML = ''; data.stats.top_users.forEach(user => { const item = document.createElement('div'); item.className = 'd-flex justify-content-between align-items-center mb-1 p-2 bg-dark rounded'; item.innerHTML = `${user.username}${user.message_count} msgs`; topUsersList.appendChild(item); }); const activity = document.getElementById('activity-chart-placeholder'); activity.innerHTML = ''; data.stats.history.forEach(day => { const bar = document.createElement('div'); bar.className = 'd-flex align-items-center mb-1'; const percent = Math.min(100, (day.count / 100) * 100); // Normalize to 100 for visual bar.innerHTML = `
${day.date}
${day.count}
`; activity.appendChild(bar); }); if (data.stats.history.length === 0) { activity.innerHTML = '
No activity in the last 7 days.
'; } } } catch (e) { console.error(e); } } // Server Settings const searchServerIconBtn = document.getElementById('search-server-icon-btn'); const serverIconResults = document.getElementById('server-icon-search-results'); const serverIconPreview = document.getElementById('server-icon-preview'); const serverIconUrlInput = document.getElementById('server-icon-url'); searchServerIconBtn?.addEventListener('click', async () => { const query = prompt('Search for a server icon:', 'abstract'); if (!query) return; serverIconResults.innerHTML = '
Searching...
'; try { const resp = await fetch(`api/pexels.php?action=search&query=${encodeURIComponent(query)}`); const data = await resp.json(); serverIconResults.innerHTML = ''; data.forEach(photo => { const img = document.createElement('img'); img.src = photo.url; img.className = 'avatar-pick'; img.style.width = '50px'; img.style.height = '50px'; img.onclick = () => { serverIconUrlInput.value = photo.url; serverIconPreview.style.backgroundImage = `url('${photo.url}')`; serverIconResults.innerHTML = ''; }; serverIconResults.appendChild(img); }); } catch (e) { serverIconResults.innerHTML = '
Error fetching icons
'; } }); // User Settings - Avatar Search const avatarSearchBtn = document.getElementById('search-avatar-btn'); const avatarSearchQuery = document.getElementById('avatar-search-query'); const avatarResults = document.getElementById('avatar-results'); const avatarPreview = document.getElementById('settings-avatar-preview'); const avatarUrlInput = document.getElementById('settings-avatar-url'); avatarSearchBtn?.addEventListener('click', async () => { const q = avatarSearchQuery.value.trim(); if (!q) return; avatarResults.innerHTML = '
Searching...
'; try { const resp = await fetch(`api/pexels.php?action=search&query=${encodeURIComponent(q)}`); const data = await resp.json(); avatarResults.innerHTML = ''; data.forEach(photo => { const img = document.createElement('img'); img.src = photo.url; img.className = 'avatar-pick'; img.style.width = '60px'; img.style.height = '60px'; img.style.cursor = 'pointer'; img.onclick = () => { avatarUrlInput.value = photo.url; avatarPreview.style.backgroundImage = `url('${photo.url}')`; }; avatarResults.appendChild(img); }); } catch (e) { console.error(e); } }); // User Settings - Save const saveSettingsBtn = document.getElementById('save-settings-btn'); saveSettingsBtn?.addEventListener('click', async () => { const form = document.getElementById('user-settings-form'); const formData = new FormData(form); const dndMode = document.getElementById('dnd-switch').checked ? '1' : '0'; formData.append('dnd_mode', dndMode); const theme = form.querySelector('input[name="theme"]:checked').value; document.body.setAttribute('data-theme', theme); const resp = await fetch('api_v1_user.php', { method: 'POST', body: formData }); const result = await resp.json(); if (result.success) { location.reload(); } else { alert(result.error || 'Failed to save settings'); } }); }); function escapeHTML(str) { const div = document.createElement('div'); div.textContent = str; return div.innerHTML; } function appendMessage(msg) { const messagesList = document.getElementById('messages-list'); const div = document.createElement('div'); div.className = 'message-item'; div.dataset.id = msg.id; const avatarStyle = msg.avatar_url ? `background-image: url('${msg.avatar_url}');` : ''; let attachmentHtml = ''; if (msg.attachment_url) { const ext = msg.attachment_url.split('.').pop().toLowerCase(); if (['jpg', 'jpeg', 'png', 'gif', 'webp'].includes(ext)) { attachmentHtml = `
Attachment
`; } else { attachmentHtml = `
${msg.attachment_url.split('/').pop()}
`; } } let embedHtml = ''; if (msg.metadata) { const meta = typeof msg.metadata === 'string' ? JSON.parse(msg.metadata) : msg.metadata; embedHtml = `
${meta.site_name ? `
${escapeHTML(meta.site_name)}
` : ''} ${meta.title ? `${escapeHTML(meta.title)}` : ''} ${meta.description ? `
${escapeHTML(meta.description)}
` : ''} ${meta.image ? `
` : ''}
`; } const isMe = msg.user_id == window.currentUserId || msg.username == window.currentUsername; // Check if user is server owner (could be passed in window object) const isOwner = window.isServerOwner || false; const pinHtml = ` `; const actionsHtml = (isMe || isOwner) ? `
${pinHtml} ${isMe ? ` ` : ''}
` : ''; const pinnedBadge = msg.is_pinned ? ` Pinned ` : ''; const mentionRegex = new RegExp(`@${window.currentUsername}\\b`, 'g'); if (msg.content.match(mentionRegex)) { div.classList.add('mentioned'); } if (msg.is_pinned) div.classList.add('pinned'); div.innerHTML = `
${escapeHTML(msg.username)} ${msg.time} ${pinnedBadge} ${actionsHtml}
${escapeHTML(msg.content).replace(/\n/g, '
').replace(mentionRegex, `@${window.currentUsername}`)} ${attachmentHtml} ${embedHtml}
+
`; messagesList.appendChild(div); }