diff --git a/app/public/guild.css b/app/public/guild.css index 1f9661c..054e543 100644 --- a/app/public/guild.css +++ b/app/public/guild.css @@ -799,3 +799,168 @@ body { background: #d9a02a; } +/* ===== Send Message Preview ===== */ +.message-preview { + background: var(--bg-dark); + border-radius: var(--radius-md); + padding: var(--spacing-md); + min-height: 100px; +} + +.preview-content { + color: var(--text-primary); + margin-bottom: var(--spacing-md); + white-space: pre-wrap; + word-break: break-word; +} + +.preview-embed { + background: var(--bg-card); + border-left: 4px solid var(--primary-color); + border-radius: var(--radius-sm); + padding: var(--spacing-md); + position: relative; + max-width: 520px; +} + +.preview-embed-author { + display: flex; + align-items: center; + gap: var(--spacing-xs); + font-size: 0.85rem; + color: var(--text-secondary); + margin-bottom: var(--spacing-xs); +} + +.preview-author-icon { + width: 24px; + height: 24px; + border-radius: 50%; +} + +.preview-embed-author a { + color: var(--text-secondary); + text-decoration: none; +} + +.preview-embed-author a:hover { + text-decoration: underline; +} + +.preview-embed-title { + font-weight: 600; + color: var(--text-primary); + font-size: 1rem; + margin-bottom: var(--spacing-xs); +} + +.preview-embed-description { + color: var(--text-secondary); + font-size: 0.9rem; + white-space: pre-wrap; + word-break: break-word; +} + +.preview-embed-thumbnail { + position: absolute; + top: var(--spacing-md); + right: var(--spacing-md); + max-width: 80px; + max-height: 80px; + border-radius: var(--radius-sm); +} + +.preview-embed-fields { + display: flex; + flex-wrap: wrap; + gap: var(--spacing-sm); + margin-top: var(--spacing-md); +} + +.preview-embed-field { + flex: 1 1 100%; +} + +.preview-embed-field.inline { + flex: 1 1 calc(33% - var(--spacing-sm)); + min-width: 150px; +} + +.preview-field-name { + font-weight: 600; + font-size: 0.85rem; + color: var(--text-primary); + margin-bottom: 2px; +} + +.preview-field-value { + font-size: 0.85rem; + color: var(--text-secondary); +} + +.preview-embed-image { + max-width: 100%; + max-height: 300px; + border-radius: var(--radius-sm); + margin-top: var(--spacing-md); +} + +.preview-embed-footer { + display: flex; + align-items: center; + gap: var(--spacing-xs); + margin-top: var(--spacing-md); + font-size: 0.75rem; + color: var(--text-muted); +} + +.preview-footer-icon { + width: 20px; + height: 20px; + border-radius: 50%; +} + +/* ===== Field Items ===== */ +.field-item { + background: var(--bg-card); + padding: var(--spacing-sm); + border-radius: var(--radius-sm); + margin-bottom: var(--spacing-sm); +} + +.field-item .form-row { + align-items: center; +} + +/* ===== Send Message History ===== */ +.history-item { + background: var(--bg-card); + border-radius: var(--radius-sm); + padding: var(--spacing-md); + margin-bottom: var(--spacing-sm); +} + +.history-item-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: var(--spacing-xs); +} + +.history-channel { + color: var(--primary-color); + font-weight: 500; +} + +.history-time { + color: var(--text-muted); + font-size: 0.8rem; +} + +.history-content { + color: var(--text-secondary); + font-size: 0.9rem; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} diff --git a/app/public/guild.html b/app/public/guild.html index 4e4c04c..58b7fe0 100644 --- a/app/public/guild.html +++ b/app/public/guild.html @@ -66,6 +66,10 @@ Messages programmés + + ✉️ + Envoyer un message + @@ -932,6 +936,132 @@ + +
+
+
+
+

✉️ Envoyer un message

+

Envoyez un message dans un salon de votre serveur

+
+
+
+ +
+ + +
+ +
+ + +
+ + +
+ +
+ + + + +
+ +
+

L'aperçu apparaîtra ici...

+
+
+ +
+ +
+ + +
+

📋 Derniers messages envoyés

+
+

Aucun message envoyé récemment.

+
+
+ +
+
+
+ @@ -950,5 +1080,6 @@ + diff --git a/app/public/guild/guildBase.js b/app/public/guild/guildBase.js index 88e8a6b..eb085e1 100644 --- a/app/public/guild/guildBase.js +++ b/app/public/guild/guildBase.js @@ -60,6 +60,7 @@ fetch(`/api/bot/get-text-channels/${guildId}`) const goodbye = document.getElementById("goodbye-channel"); const levelAnnouncements = document.getElementById("level-announcements-channel"); const levelChannelRestrict = document.getElementById("level-channel-with-or-without-xp"); + const sendmsgChannel = document.getElementById("sendmsg-channel-select"); channels.forEach(c => { const opt = new Option(`#${c.name}`, c.id); @@ -67,6 +68,7 @@ fetch(`/api/bot/get-text-channels/${guildId}`) goodbye?.appendChild(opt.cloneNode(true)); levelAnnouncements?.appendChild(opt.cloneNode(true)); levelChannelRestrict?.appendChild(opt.cloneNode(true)); + sendmsgChannel?.appendChild(opt.cloneNode(true)); }); }); diff --git a/app/public/guild/sendMessageForm.js b/app/public/guild/sendMessageForm.js new file mode 100644 index 0000000..6e4b146 --- /dev/null +++ b/app/public/guild/sendMessageForm.js @@ -0,0 +1,316 @@ +// ===== SEND MESSAGE FORM ===== +(async function () { + const channelSelect = document.getElementById("sendmsg-channel-select"); + const messageContent = document.getElementById("sendmsg-content"); + const embedEnabled = document.getElementById("sendmsg-embed-enabled"); + const embedOptions = document.getElementById("sendmsg-embed-options"); + const embedTitle = document.getElementById("sendmsg-embed-title"); + const embedDescription = document.getElementById("sendmsg-embed-description"); + const embedColor = document.getElementById("sendmsg-embed-color"); + const embedAuthorName = document.getElementById("sendmsg-embed-author-name"); + const embedAuthorUrl = document.getElementById("sendmsg-embed-author-url"); + const embedAuthorIcon = document.getElementById("sendmsg-embed-author-icon"); + const embedImage = document.getElementById("sendmsg-embed-image"); + const embedThumbnail = document.getElementById("sendmsg-embed-thumbnail"); + const embedFooterText = document.getElementById("sendmsg-embed-footer-text"); + const embedFooterIcon = document.getElementById("sendmsg-embed-footer-icon"); + const embedTimestamp = document.getElementById("sendmsg-embed-timestamp"); + const fieldsContainer = document.getElementById("sendmsg-fields-container"); + const addFieldBtn = document.getElementById("sendmsg-add-field"); + const previewContainer = document.getElementById("sendmsg-preview"); + const sendBtn = document.getElementById("sendmsg-send-btn"); + const historyContainer = document.getElementById("sendmsg-history"); + + let fields = []; + let sentMessages = []; + + // Toggle embed options + embedEnabled.addEventListener("change", () => { + embedOptions.style.display = embedEnabled.checked ? "block" : "none"; + updatePreview(); + }); + + // Add field + addFieldBtn.addEventListener("click", () => { + const fieldIndex = fields.length; + fields.push({ name: "", value: "", inline: false }); + renderFields(); + }); + + function renderFields() { + fieldsContainer.innerHTML = ""; + fields.forEach((field, index) => { + const fieldDiv = document.createElement("div"); + fieldDiv.className = "field-item"; + fieldDiv.innerHTML = ` +
+
+ +
+
+ +
+
+ + +
+
+ `; + fieldsContainer.appendChild(fieldDiv); + }); + + // Event listeners for fields + document.querySelectorAll(".field-name").forEach(input => { + input.addEventListener("input", (e) => { + fields[e.target.dataset.index].name = e.target.value; + updatePreview(); + }); + }); + document.querySelectorAll(".field-value").forEach(input => { + input.addEventListener("input", (e) => { + fields[e.target.dataset.index].value = e.target.value; + updatePreview(); + }); + }); + document.querySelectorAll(".field-inline").forEach(input => { + input.addEventListener("change", (e) => { + fields[e.target.dataset.index].inline = e.target.checked; + updatePreview(); + }); + }); + document.querySelectorAll(".field-remove").forEach(btn => { + btn.addEventListener("click", (e) => { + fields.splice(e.target.dataset.index, 1); + renderFields(); + updatePreview(); + }); + }); + } + + // Update preview + function updatePreview() { + let html = ""; + + const content = messageContent.value.trim(); + if (content) { + html += `
${escapeHtml(content)}
`; + } + + if (embedEnabled.checked) { + const color = embedColor.value || "#5865F2"; + html += `
`; + + // Author + if (embedAuthorName.value.trim()) { + html += `
`; + if (embedAuthorIcon.value.trim()) { + html += ``; + } + if (embedAuthorUrl.value.trim()) { + html += `${escapeHtml(embedAuthorName.value)}`; + } else { + html += `${escapeHtml(embedAuthorName.value)}`; + } + html += `
`; + } + + // Title + if (embedTitle.value.trim()) { + html += `
${escapeHtml(embedTitle.value)}
`; + } + + // Description + if (embedDescription.value.trim()) { + html += `
${escapeHtml(embedDescription.value)}
`; + } + + // Thumbnail + if (embedThumbnail.value.trim()) { + html += ``; + } + + // Fields + const validFields = fields.filter(f => f.name.trim() && f.value.trim()); + if (validFields.length > 0) { + html += `
`; + validFields.forEach(f => { + html += `
+
${escapeHtml(f.name)}
+
${escapeHtml(f.value)}
+
`; + }); + html += `
`; + } + + // Image + if (embedImage.value.trim()) { + html += ``; + } + + // Footer + if (embedFooterText.value.trim() || embedTimestamp.checked) { + html += ``; + } + + html += `
`; + } + + if (!html) { + html = `

L'aperçu apparaîtra ici...

`; + } + + previewContainer.innerHTML = html; + } + + function escapeHtml(text) { + const div = document.createElement("div"); + div.textContent = text; + return div.innerHTML; + } + + // Add event listeners for preview updates + [messageContent, embedTitle, embedDescription, embedAuthorName, embedAuthorUrl, + embedAuthorIcon, embedImage, embedThumbnail, embedFooterText, embedFooterIcon].forEach(el => { + el.addEventListener("input", updatePreview); + }); + embedColor.addEventListener("change", updatePreview); + embedTimestamp.addEventListener("change", updatePreview); + + // Send message + sendBtn.addEventListener("click", async () => { + const channelId = channelSelect.value; + if (!channelId) { + alert("Veuillez sélectionner un salon."); + return; + } + + const content = messageContent.value.trim(); + const hasEmbed = embedEnabled.checked; + + if (!content && !hasEmbed) { + alert("Veuillez entrer un message ou activer l'embed."); + return; + } + + // Build embed data + let embed = null; + if (hasEmbed) { + embed = { + title: embedTitle.value.trim() || null, + description: embedDescription.value.trim() || null, + color: embedColor.value, + author: null, + thumbnail: embedThumbnail.value.trim() ? { url: embedThumbnail.value.trim() } : null, + image: embedImage.value.trim() ? { url: embedImage.value.trim() } : null, + footer: null, + timestamp: embedTimestamp.checked, + fields: fields.filter(f => f.name.trim() && f.value.trim()) + }; + + if (embedAuthorName.value.trim()) { + embed.author = { + name: embedAuthorName.value.trim(), + url: embedAuthorUrl.value.trim() || null, + icon_url: embedAuthorIcon.value.trim() || null + }; + } + + if (embedFooterText.value.trim()) { + embed.footer = { + text: embedFooterText.value.trim(), + icon_url: embedFooterIcon.value.trim() || null + }; + } + } + + sendBtn.disabled = true; + sendBtn.textContent = "⏳ Envoi en cours..."; + + try { + const res = await fetch("/api/bot/send-message", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + guildId: window.guildId, + channelId, + content: content || null, + embed + }) + }); + + const data = await res.json(); + + if (data.success) { + // Add to history + sentMessages.unshift({ + channelId, + channelName: channelSelect.options[channelSelect.selectedIndex].text, + content: content || "(Embed uniquement)", + timestamp: new Date().toLocaleString("fr-FR") + }); + if (sentMessages.length > 10) sentMessages.pop(); + renderHistory(); + + // Clear form + messageContent.value = ""; + embedTitle.value = ""; + embedDescription.value = ""; + embedAuthorName.value = ""; + embedAuthorUrl.value = ""; + embedAuthorIcon.value = ""; + embedImage.value = ""; + embedThumbnail.value = ""; + embedFooterText.value = ""; + embedFooterIcon.value = ""; + embedTimestamp.checked = false; + fields = []; + renderFields(); + updatePreview(); + + alert("✅ Message envoyé avec succès !"); + } else { + alert("❌ Erreur lors de l'envoi: " + (data.error || "Erreur inconnue")); + } + } catch (err) { + console.error(err); + alert("❌ Erreur lors de l'envoi du message."); + } + + sendBtn.disabled = false; + sendBtn.textContent = "📤 Envoyer le message"; + }); + + function renderHistory() { + if (sentMessages.length === 0) { + historyContainer.innerHTML = `

Aucun message envoyé récemment.

`; + return; + } + + historyContainer.innerHTML = sentMessages.map(msg => ` +
+
+ #${escapeHtml(msg.channelName)} + ${msg.timestamp} +
+
${escapeHtml(msg.content.substring(0, 100))}${msg.content.length > 100 ? '...' : ''}
+
+ `).join(""); + } + + // Initialize + updatePreview(); +})(); diff --git a/app/routes/api.js b/app/routes/api.js index 0ac6399..1f581d5 100644 --- a/app/routes/api.js +++ b/app/routes/api.js @@ -1122,5 +1122,98 @@ module.exports = (app, db, client) => { ); }); + // Envoyer un message instantané + router.post("/bot/send-message", express.json(), async (req, res) => { + const { guildId, channelId, content, embed } = req.body; + + if (!req.session.guilds) { + return res.status(401).json({ success: false, error: "Non connecté" }); + } + + const isAdmin = req.session.guilds.find( + g => g.id === guildId && (BigInt(g.permissions) & 0x8n) === 0x8n + ); + + if (!isAdmin) { + return res.status(403).json({ success: false, error: "Permission refusée" }); + } + + try { + const guild = client.guilds.cache.get(guildId); + if (!guild) { + return res.status(404).json({ success: false, error: "Serveur non trouvé" }); + } + + const channel = guild.channels.cache.get(channelId); + if (!channel) { + return res.status(404).json({ success: false, error: "Salon non trouvé" }); + } + + // Build message options + const messageOptions = {}; + + if (content) { + messageOptions.content = content; + } + + if (embed) { + const { EmbedBuilder } = require("discord.js"); + const embedBuilder = new EmbedBuilder(); + + if (embed.title) embedBuilder.setTitle(embed.title); + if (embed.description) embedBuilder.setDescription(embed.description); + if (embed.color) embedBuilder.setColor(embed.color); + + if (embed.author && embed.author.name) { + embedBuilder.setAuthor({ + name: embed.author.name, + url: embed.author.url || undefined, + iconURL: embed.author.icon_url || undefined + }); + } + + if (embed.thumbnail && embed.thumbnail.url) { + embedBuilder.setThumbnail(embed.thumbnail.url); + } + + if (embed.image && embed.image.url) { + embedBuilder.setImage(embed.image.url); + } + + if (embed.footer && embed.footer.text) { + embedBuilder.setFooter({ + text: embed.footer.text, + iconURL: embed.footer.icon_url || undefined + }); + } + + if (embed.timestamp) { + embedBuilder.setTimestamp(); + } + + if (embed.fields && embed.fields.length > 0) { + embed.fields.forEach(field => { + if (field.name && field.value) { + embedBuilder.addFields({ + name: field.name, + value: field.value, + inline: field.inline || false + }); + } + }); + } + + messageOptions.embeds = [embedBuilder]; + } + + await channel.send(messageOptions); + res.json({ success: true }); + + } catch (err) { + console.error("Erreur envoi message:", err); + res.status(500).json({ success: false, error: err.message }); + } + }); + app.use("/api", router); };