diff --git a/api_v1_messages.php b/api_v1_messages.php index 40d3455..8901b8c 100644 --- a/api_v1_messages.php +++ b/api_v1_messages.php @@ -135,31 +135,70 @@ if ($_SERVER['REQUEST_METHOD'] === 'PUT') { $action = $data['action'] ?? 'edit'; try { + $stmt = db()->prepare("SELECT user_id, channel_id FROM messages WHERE id = ?"); + $stmt->execute([$message_id]); + $msg_data = $stmt->fetch(); + + if (!$msg_data) { + echo json_encode(['success' => false, 'error' => 'Message not found']); + exit; + } + if ($action === 'pin') { + if (!Permissions::canDoInChannel($user_id, $msg_data['channel_id'], Permissions::MANAGE_MESSAGES)) { + echo json_encode(['success' => false, 'error' => 'Unauthorized']); + exit; + } $stmt = db()->prepare("UPDATE messages SET is_pinned = 1 WHERE id = ?"); $stmt->execute([$message_id]); echo json_encode(['success' => true]); exit; } if ($action === 'unpin') { + if (!Permissions::canDoInChannel($user_id, $msg_data['channel_id'], Permissions::MANAGE_MESSAGES)) { + echo json_encode(['success' => false, 'error' => 'Unauthorized']); + exit; + } $stmt = db()->prepare("UPDATE messages SET is_pinned = 0 WHERE id = ?"); $stmt->execute([$message_id]); echo json_encode(['success' => true]); exit; } + if ($msg_data['user_id'] != $user_id && !Permissions::canDoInChannel($user_id, $msg_data['channel_id'], Permissions::MANAGE_MESSAGES)) { + echo json_encode(['success' => false, 'error' => 'Unauthorized']); + exit; + } + if (empty($content)) { echo json_encode(['success' => false, 'error' => 'Content cannot be empty']); exit; } - $stmt = db()->prepare("UPDATE messages SET content = ? WHERE id = ? AND user_id = ?"); - $stmt->execute([$content, $message_id, $user_id]); - - if ($stmt->rowCount() > 0) { - echo json_encode(['success' => true]); - } else { - echo json_encode(['success' => false, 'error' => 'Message not found or unauthorized']); + + $metadata = null; + if (isset($data['is_announcement']) && $data['is_announcement'] == '1') { + if (!Permissions::canDoInChannel($user_id, $msg_data['channel_id'], Permissions::CREATE_ANNOUNCEMENT)) { + echo json_encode(['success' => false, 'error' => 'You do not have permission to manage announcements in this channel.']); + exit; + } + $metadata = json_encode([ + 'is_manual_announcement' => true, + 'title' => $data['ann_title'] ?? '', + 'color' => $data['ann_color'] ?? '#5865f2', + 'description' => $content, + 'url' => $data['ann_link'] ?? '' + ]); } + + if ($metadata) { + $stmt = db()->prepare("UPDATE messages SET content = ?, metadata = ? WHERE id = ?"); + $stmt->execute([$content, $metadata, $message_id]); + } else { + $stmt = db()->prepare("UPDATE messages SET content = ? WHERE id = ?"); + $stmt->execute([$content, $message_id]); + } + + echo json_encode(['success' => true]); } catch (Exception $e) { echo json_encode(['success' => false, 'error' => $e->getMessage()]); } @@ -171,14 +210,24 @@ if ($_SERVER['REQUEST_METHOD'] === 'DELETE') { $message_id = $data['id'] ?? 0; try { - $stmt = db()->prepare("DELETE FROM messages WHERE id = ? AND user_id = ?"); - $stmt->execute([$message_id, $user_id]); + $stmt = db()->prepare("SELECT user_id, channel_id FROM messages WHERE id = ?"); + $stmt->execute([$message_id]); + $msg_data = $stmt->fetch(); - if ($stmt->rowCount() > 0) { - echo json_encode(['success' => true]); - } else { - echo json_encode(['success' => false, 'error' => 'Message not found or unauthorized']); + if (!$msg_data) { + echo json_encode(['success' => false, 'error' => 'Message not found']); + exit; } + + if ($msg_data['user_id'] != $user_id && !Permissions::canDoInChannel($user_id, $msg_data['channel_id'], Permissions::MANAGE_MESSAGES)) { + echo json_encode(['success' => false, 'error' => 'Unauthorized']); + exit; + } + + $stmt = db()->prepare("DELETE FROM messages WHERE id = ?"); + $stmt->execute([$message_id]); + + echo json_encode(['success' => true]); } catch (Exception $e) { echo json_encode(['success' => false, 'error' => $e->getMessage()]); } @@ -261,7 +310,21 @@ if (!empty($content)) { } $metadata = null; -if (!empty($content)) { +if (isset($_POST['is_announcement']) && $_POST['is_announcement'] == '1') { + if (!Permissions::canDoInChannel($user_id, $channel_id, Permissions::CREATE_ANNOUNCEMENT)) { + echo json_encode(['success' => false, 'error' => 'You do not have permission to create announcements in this channel.']); + exit; + } + $metadata = json_encode([ + 'is_manual_announcement' => true, + 'title' => $_POST['ann_title'] ?? '', + 'color' => $_POST['ann_color'] ?? '#5865f2', + 'description' => $content, + 'url' => $_POST['ann_link'] ?? '' + ]); + // Clear content for the message text itself if we want it only in the embed + // But keeping it in content might be good for search/fallback +} elseif (!empty($content)) { $urls = extractUrls($content); if (!empty($urls)) { // Fetch OG data for the first URL diff --git a/assets/js/main.js b/assets/js/main.js index 1ffc3d3..9ba6eaf 100644 --- a/assets/js/main.js +++ b/assets/js/main.js @@ -693,6 +693,98 @@ document.addEventListener('DOMContentLoaded', () => { xhr.send(formData); }); + // Announcement News Button + document.addEventListener('click', (e) => { + const annBtn = e.target.closest('#announcement-news-btn'); + if (annBtn) { + const idField = document.getElementById('announcement-id'); + if (idField) idField.value = ''; + const form = document.getElementById('announcement-form'); + if (form) form.reset(); + const submitBtn = document.getElementById('announcement-submit-btn'); + if (submitBtn) submitBtn.innerText = "Publier l'annonce"; + + const modal = new bootstrap.Modal(document.getElementById('createAnnouncementModal')); + modal.show(); + return; + } + }); + + // Announcement Form Submission + const annForm = document.getElementById('announcement-form'); + annForm?.addEventListener('submit', async (e) => { + e.preventDefault(); + + const annId = document.getElementById('announcement-id').value; + const title = document.getElementById('announcement-title').value.trim(); + const color = document.getElementById('announcement-color').value; + const content = document.getElementById('announcement-content').value.trim(); + const link = document.getElementById('announcement-link').value.trim(); + + if (!title || !content) return; + + const modalEl = document.getElementById('createAnnouncementModal'); + const modal = bootstrap.Modal.getInstance(modalEl); + + let resp; + if (annId) { + resp = await fetch('api_v1_messages.php', { + method: 'PUT', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + id: annId, + content: content, + is_announcement: '1', + ann_title: title, + ann_color: color, + ann_link: link + }) + }); + } else { + const formData = new FormData(); + formData.append('channel_id', currentChannel); + formData.append('content', content); + formData.append('is_announcement', '1'); + formData.append('ann_title', title); + formData.append('ann_color', color); + formData.append('ann_link', link); + + resp = await fetch('api_v1_messages.php', { + method: 'POST', + body: formData + }); + } + + try { + const result = await resp.json(); + if (result.success) { + if (annId) { + // Update via WS or reload + ws?.send(JSON.stringify({ type: 'message_edit', message_id: annId, content: content })); + location.reload(); + } else { + appendMessage(result.message); + if (ws && ws.readyState === WebSocket.OPEN) { + ws.send(JSON.stringify({ + type: 'message', + data: JSON.stringify({ + ...result.message, + channel_id: currentChannel + }) + })); + } + } + modal.hide(); + annForm.reset(); + } else { + alert(result.error || 'Erreur lors de la publication'); + } + } catch (err) { + console.error(err); + alert('Une erreur est survenue'); + } + }); + // Handle Click Events document.addEventListener('click', (e) => { console.log('Global click at:', e.target); @@ -805,6 +897,40 @@ document.addEventListener('DOMContentLoaded', () => { if (editBtn) { const msgId = editBtn.dataset.id; const msgItem = editBtn.closest('.message-item'); + + if (editBtn.classList.contains('edit-announcement')) { + const modalEl = document.getElementById('createAnnouncementModal'); + const modal = new bootstrap.Modal(modalEl); + + const titleEl = msgItem.querySelector('.embed-title'); + const title = titleEl ? titleEl.innerText : ''; + const description = msgItem.dataset.rawContent; + const embedEl = msgItem.querySelector('.rich-embed'); + const color = embedEl ? embedEl.style.borderLeftColor : '#5865f2'; + + const rgbToHex = (rgb) => { + if (!rgb || !rgb.startsWith('rgb')) return '#5865f2'; + const parts = rgb.match(/\d+/g); + return "#" + parts.map(x => { + const hex = parseInt(x).toString(16); + return hex.length === 1 ? "0" + hex : hex; + }).join(""); + }; + + const hexColor = rgbToHex(color); + const url = titleEl ? (titleEl.getAttribute('href') || '') : ''; + + document.getElementById('announcement-id').value = msgId; + document.getElementById('announcement-title').value = title; + document.getElementById('announcement-color').value = hexColor; + document.getElementById('announcement-content').value = description; + document.getElementById('announcement-link').value = url; + document.getElementById('announcement-submit-btn').innerText = "Modifier l'annonce"; + + modal.show(); + return; + } + const textEl = msgItem.querySelector('.message-text'); const originalContent = msgItem.dataset.rawContent || textEl.innerText; @@ -1462,6 +1588,12 @@ document.addEventListener('DOMContentLoaded', () => { eventPerms.forEach(p => { p.style.setProperty('display', channelType === 'event' ? 'block' : 'none', channelType === 'event' ? '' : 'important'); }); + + // Show/Hide announcement permissions + const annPerms = document.querySelectorAll('.announcement-permission-only'); + annPerms.forEach(p => { + p.style.setProperty('display', channelType === 'announcement' ? 'block' : 'none', channelType === 'announcement' ? '' : 'important'); + }); }); }); @@ -1487,6 +1619,12 @@ document.addEventListener('DOMContentLoaded', () => { p.style.setProperty('display', type === 'event' ? 'block' : 'none', type === 'event' ? '' : 'important'); }); + // Show/Hide announcement permissions + const annPerms = document.querySelectorAll('.announcement-permission-only'); + annPerms.forEach(p => { + p.style.setProperty('display', type === 'announcement' ? 'block' : 'none', type === 'announcement' ? '' : 'important'); + }); + // Rules specific visibility const rulesRoleContainer = document.getElementById('edit-channel-rules-role-container'); if (rulesRoleContainer) { @@ -3092,12 +3230,41 @@ document.addEventListener('DOMContentLoaded', () => { let embedHtml = ''; if (msg.metadata) { const meta = typeof msg.metadata === 'string' ? JSON.parse(msg.metadata) : msg.metadata; + const borderColor = meta.color || 'var(--blurple)'; + + let metaHtml = ''; + if (meta.is_rss) { + const parts = []; + if (meta.category) parts.push(escapeHTML(meta.category)); + if (meta.date) parts.push(escapeHTML(meta.date)); + if (meta.author) parts.push(escapeHTML(meta.author)); + metaHtml = `
${parts.join(' · ')}
`; + } else if (meta.is_manual_announcement) { + metaHtml = `
${msg.timestamp || 'Just now'}
`; + } + + let titleHtml = ''; + if (meta.title) { + if (meta.url) { + titleHtml = `${escapeHTML(meta.title)}`; + } else { + titleHtml = `
${escapeHTML(meta.title)}
`; + } + } + + let footerHtml = ''; + if (meta.is_manual_announcement && meta.url) { + footerHtml = ``; + } + embedHtml = ` -
- ${meta.site_name ? `
${escapeHTML(meta.site_name)}
` : ''} - ${meta.title ? `${escapeHTML(meta.title)}` : ''} - ${meta.description ? `
${escapeHTML(meta.description)}
` : ''} +
+ ${(meta.site_name && !meta.is_rss && !meta.is_manual_announcement) ? `
${escapeHTML(meta.site_name)}
` : ''} + ${titleHtml} + ${metaHtml} + ${meta.description ? `
${parseMarkdown(meta.description)}
` : ''} ${meta.image ? `
` : ''} + ${footerHtml}
`; } @@ -3114,8 +3281,8 @@ document.addEventListener('DOMContentLoaded', () => { const actionsHtml = (isMe || hasManageRights) ? `
${pinHtml} - ${isMe ? ` - + ${(isMe || (isManualAnn && hasManageRights)) ? ` + @@ -3132,21 +3299,12 @@ document.addEventListener('DOMContentLoaded', () => { ` : ''; - let userBadgesHtml = ''; - const bData = msg.badge_data || ''; - if (bData) { - bData.split(':::').forEach(d => { - const parts = d.split('|'); - const name = parts[0]; - const url = parts[1]; - userBadgesHtml += ``; - }); - } - const mentionRegex = new RegExp(`@${window.currentUsername}\\b`, 'g'); const mentionHtml = `@${window.currentUsername}`; const contentWithMentions = parseCustomEmotes(msg.content).replace(mentionRegex, mentionHtml); + const isManualAnn = msg.metadata && (typeof msg.metadata === 'string' ? JSON.parse(msg.metadata) : msg.metadata).is_manual_announcement; + div.innerHTML = `
@@ -3154,12 +3312,11 @@ document.addEventListener('DOMContentLoaded', () => { ${escapeHTML(msg.username)} ${renderRoleIconJS(msg.role_icon, '14px')} - ${userBadgesHtml} ${msg.timestamp || 'Just now'} ${pinnedBadge}
-
${contentWithMentions}
+ ${!isManualAnn ? `
${contentWithMentions}
` : ''} ${attachmentHtml} ${embedHtml}
diff --git a/assets/pasted-20260220-202504-6801c34a.png b/assets/pasted-20260220-202504-6801c34a.png new file mode 100644 index 0000000..32b6fa5 Binary files /dev/null and b/assets/pasted-20260220-202504-6801c34a.png differ diff --git a/includes/permissions.php b/includes/permissions.php index 596ff22..a5da920 100644 --- a/includes/permissions.php +++ b/includes/permissions.php @@ -16,6 +16,7 @@ class Permissions { const CREATE_EVENT = 4096; const EDIT_EVENT = 8192; const DELETE_EVENT = 16384; + const CREATE_ANNOUNCEMENT = 32768; public static function hasPermission($user_id, $server_id, $permission) { $stmt = db()->prepare("SELECT is_admin FROM users WHERE id = ?"); diff --git a/index.php b/index.php index f9b8336..8014842 100644 --- a/index.php +++ b/index.php @@ -239,6 +239,7 @@ if ($is_dm_view) { $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); break; } @@ -1259,7 +1260,18 @@ $projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
- +
-
- +
+
- + + + +
+
@@ -1297,8 +1313,18 @@ $projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? ''; ?>
+ +
+ +
+ -
+
+ + +
@@ -1330,8 +1356,8 @@ $projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? ''; - - + + @@ -1393,6 +1419,12 @@ $projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
+ + + + @@ -1432,6 +1464,52 @@ $projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
+ + + +
+
+
+
Gérer les messages
+
Permet de supprimer ou d'épingler les messages des autres membres.
+
+
+ + + + + + + + +
+
+
+ + +