From 0ba91ac116c622a4c8b72f6a47351a03853d8ec5 Mon Sep 17 00:00:00 2001 From: Arthur Puechberty Date: Sun, 18 Jan 2026 17:07:07 +0100 Subject: [PATCH] add role panel --- app/commands/🔧 Administration/rolepanel.js | 382 +++++++++++++++++ app/db.js | 31 ++ app/events/interactionCreate.js | 109 +++++ app/public/guild.css | 242 +++++++++++ app/public/guild.html | 163 +++++++ app/public/guild/rolePanelsForm.js | 448 ++++++++++++++++++++ app/routes/api.js | 408 ++++++++++++++++++ 7 files changed, 1783 insertions(+) create mode 100644 app/commands/🔧 Administration/rolepanel.js create mode 100644 app/events/interactionCreate.js create mode 100644 app/public/guild/rolePanelsForm.js diff --git a/app/commands/🔧 Administration/rolepanel.js b/app/commands/🔧 Administration/rolepanel.js new file mode 100644 index 0000000..e7b2b89 --- /dev/null +++ b/app/commands/🔧 Administration/rolepanel.js @@ -0,0 +1,382 @@ +const { EmbedBuilder, ActionRowBuilder, ButtonBuilder, ButtonStyle, ChannelType } = require('discord.js'); +const addCommand = require('../../fonctions/addCommand'); +const db = require('../../db'); + +const buttonStyles = { + 'primary': ButtonStyle.Primary, + 'secondary': ButtonStyle.Secondary, + 'success': ButtonStyle.Success, + 'danger': ButtonStyle.Danger +}; + +// Fonction pour créer/mettre à jour le message du panel +async function updatePanelMessage(client, panel, buttons) { + try { + const guild = client.guilds.cache.get(panel.guild_id); + if (!guild) return null; + + const channel = guild.channels.cache.get(panel.channel_id); + if (!channel) return null; + + // Créer l'embed + const embed = new EmbedBuilder() + .setColor(panel.color || '#5865F2') + .setTitle(panel.title || '🎭 Choisissez vos rôles') + .setDescription(panel.description || 'Cliquez sur les boutons ci-dessous pour obtenir ou retirer des rôles.'); + + if (panel.image_url) embed.setImage(panel.image_url); + if (panel.thumbnail_url) embed.setThumbnail(panel.thumbnail_url); + + // Infos sur le mode + const modeText = panel.exclusive + ? '⚠️ *Un seul rôle possible à la fois*' + : (panel.mode === 'toggle' ? '💡 *Cliquez à nouveau pour retirer un rôle*' : ''); + + if (modeText) { + embed.setFooter({ text: modeText }); + } + + // Créer les boutons (max 5 par ligne, max 5 lignes) + const rows = []; + const enabledButtons = buttons.filter(b => b.enabled).sort((a, b) => a.position - b.position); + + for (let i = 0; i < enabledButtons.length && rows.length < 5; i += 5) { + const row = new ActionRowBuilder(); + const rowButtons = enabledButtons.slice(i, i + 5); + + for (const btn of rowButtons) { + const button = new ButtonBuilder() + .setCustomId(`role_panel_${btn.id}`) + .setLabel(btn.label) + .setStyle(buttonStyles[btn.style] || ButtonStyle.Primary); + + if (btn.emoji) { + // Vérifier si c'est un emoji custom ou unicode + if (btn.emoji.match(/^\d+$/)) { + button.setEmoji({ id: btn.emoji }); + } else { + button.setEmoji(btn.emoji); + } + } + + row.addComponents(button); + } + + if (row.components.length > 0) { + rows.push(row); + } + } + + // Mettre à jour ou créer le message + if (panel.message_id) { + try { + const message = await channel.messages.fetch(panel.message_id); + await message.edit({ embeds: [embed], components: rows }); + return panel.message_id; + } catch { + // Message introuvable, en créer un nouveau + } + } + + const message = await channel.send({ embeds: [embed], components: rows }); + + // Sauvegarder l'ID du message + await new Promise((resolve, reject) => { + db.run("UPDATE role_panels SET message_id = ? WHERE id = ?", [message.id, panel.id], (err) => { + if (err) reject(err); + else resolve(); + }); + }); + + return message.id; + + } catch (err) { + console.error('Erreur mise à jour panel:', err); + return null; + } +} + +module.exports = addCommand({ + name: 'rolepanel', + description: 'Créer un panneau de rôles interactif', + aliases: ['rp', 'rolebuttons', 'reactionroles'], + permissions: ['ManageRoles', 'ManageMessages'], + botOwnerOnly: false, + dm: false, + scope: 'global', + slashOptions: [ + { type: 'STRING', name: 'action', description: 'Action à effectuer', required: true, choices: [ + { name: 'Créer un panel', value: 'create' }, + { name: 'Ajouter un bouton', value: 'addbutton' }, + { name: 'Supprimer un panel', value: 'delete' }, + { name: 'Liste des panels', value: 'list' }, + { name: 'Actualiser un panel', value: 'refresh' } + ]}, + { type: 'STRING', name: 'nom', description: 'Nom du panel', required: false }, + { type: 'CHANNEL', name: 'salon', description: 'Salon où envoyer le panel', required: false }, + { type: 'ROLE', name: 'role', description: 'Rôle à associer (pour addbutton)', required: false }, + { type: 'STRING', name: 'label', description: 'Texte du bouton', required: false }, + { type: 'STRING', name: 'emoji', description: 'Emoji du bouton', required: false } + ], + + executeSlash: async (client, interaction) => { + const action = interaction.options.getString('action'); + const guildId = interaction.guild.id; + + switch (action) { + case 'create': { + const name = interaction.options.getString('nom'); + const channel = interaction.options.getChannel('salon'); + + if (!name || !channel) { + return interaction.reply({ + content: '❌ Vous devez spécifier un nom et un salon.\nUsage: `/rolepanel create nom:MonPanel salon:#roles`', + ephemeral: true + }); + } + + if (channel.type !== ChannelType.GuildText) { + return interaction.reply({ + content: '❌ Le salon doit être un salon textuel.', + ephemeral: true + }); + } + + // Vérifier si un panel avec ce nom existe déjà + const existing = await db.getAsync( + "SELECT id FROM role_panels WHERE guild_id = ? AND name = ?", + [guildId, name] + ); + + if (existing) { + return interaction.reply({ + content: '❌ Un panel avec ce nom existe déjà.', + ephemeral: true + }); + } + + // Créer le panel + const panelId = await new Promise((resolve, reject) => { + db.run( + "INSERT INTO role_panels (guild_id, channel_id, name, title, description) VALUES (?, ?, ?, ?, ?)", + [guildId, channel.id, name, `🎭 ${name}`, 'Cliquez sur les boutons ci-dessous pour obtenir vos rôles.'], + function(err) { + if (err) reject(err); + else resolve(this.lastID); + } + ); + }); + + const embed = new EmbedBuilder() + .setColor(0x57F287) + .setTitle('✅ Panel créé') + .setDescription(`Le panel **${name}** a été créé.`) + .addFields( + { name: '📁 Salon', value: `${channel}`, inline: true }, + { name: '🆔 ID', value: `${panelId}`, inline: true } + ) + .setFooter({ text: 'Ajoutez des boutons avec /rolepanel addbutton' }); + + return interaction.reply({ embeds: [embed], ephemeral: true }); + } + + case 'addbutton': { + const name = interaction.options.getString('nom'); + const role = interaction.options.getRole('role'); + const label = interaction.options.getString('label'); + const emoji = interaction.options.getString('emoji'); + + if (!name || !role) { + return interaction.reply({ + content: '❌ Vous devez spécifier le nom du panel et un rôle.\nUsage: `/rolepanel addbutton nom:MonPanel role:@MonRole label:Mon Rôle emoji:🎮`', + ephemeral: true + }); + } + + // Trouver le panel + const panel = await db.getAsync( + "SELECT * FROM role_panels WHERE guild_id = ? AND name = ?", + [guildId, name] + ); + + if (!panel) { + return interaction.reply({ + content: `❌ Panel "${name}" non trouvé. Utilisez \`/rolepanel list\` pour voir les panels existants.`, + ephemeral: true + }); + } + + // Vérifier le nombre de boutons (max 25) + const buttonCount = await db.getAsync( + "SELECT COUNT(*) as count FROM role_panel_buttons WHERE panel_id = ?", + [panel.id] + ); + + if (buttonCount.count >= 25) { + return interaction.reply({ + content: '❌ Ce panel a atteint la limite de 25 boutons.', + ephemeral: true + }); + } + + // Ajouter le bouton + await new Promise((resolve, reject) => { + db.run( + "INSERT INTO role_panel_buttons (panel_id, role_id, label, emoji, position) VALUES (?, ?, ?, ?, ?)", + [panel.id, role.id, label || role.name, emoji || null, buttonCount.count], + (err) => { + if (err) reject(err); + else resolve(); + } + ); + }); + + // Mettre à jour le message + const buttons = await db.allAsync( + "SELECT * FROM role_panel_buttons WHERE panel_id = ?", + [panel.id] + ); + + await updatePanelMessage(client, panel, buttons); + + const embed = new EmbedBuilder() + .setColor(0x57F287) + .setTitle('✅ Bouton ajouté') + .addFields( + { name: '📋 Panel', value: name, inline: true }, + { name: '🎭 Rôle', value: `${role}`, inline: true }, + { name: '🏷️ Label', value: label || role.name, inline: true } + ); + + if (emoji) embed.addFields({ name: '😀 Emoji', value: emoji, inline: true }); + + return interaction.reply({ embeds: [embed], ephemeral: true }); + } + + case 'delete': { + const name = interaction.options.getString('nom'); + + if (!name) { + return interaction.reply({ + content: '❌ Vous devez spécifier le nom du panel à supprimer.', + ephemeral: true + }); + } + + const panel = await db.getAsync( + "SELECT * FROM role_panels WHERE guild_id = ? AND name = ?", + [guildId, name] + ); + + if (!panel) { + return interaction.reply({ + content: `❌ Panel "${name}" non trouvé.`, + ephemeral: true + }); + } + + // Supprimer le message + try { + const channel = interaction.guild.channels.cache.get(panel.channel_id); + if (channel && panel.message_id) { + const message = await channel.messages.fetch(panel.message_id).catch(() => null); + if (message) await message.delete(); + } + } catch {} + + // Supprimer de la DB + await new Promise((resolve, reject) => { + db.run("DELETE FROM role_panel_buttons WHERE panel_id = ?", [panel.id], (err) => { + if (err) reject(err); + else resolve(); + }); + }); + + await new Promise((resolve, reject) => { + db.run("DELETE FROM role_panels WHERE id = ?", [panel.id], (err) => { + if (err) reject(err); + else resolve(); + }); + }); + + return interaction.reply({ + content: `✅ Panel **${name}** supprimé avec succès.`, + ephemeral: true + }); + } + + case 'list': { + const panels = await db.allAsync( + "SELECT rp.*, COUNT(rpb.id) as button_count FROM role_panels rp LEFT JOIN role_panel_buttons rpb ON rp.id = rpb.panel_id WHERE rp.guild_id = ? GROUP BY rp.id", + [guildId] + ); + + if (!panels || panels.length === 0) { + return interaction.reply({ + content: 'ℹ️ Aucun panel de rôles configuré. Créez-en un avec `/rolepanel create`.', + ephemeral: true + }); + } + + const embed = new EmbedBuilder() + .setColor(0x5865F2) + .setTitle('📋 Panels de rôles') + .setDescription(panels.map(p => { + const status = p.enabled ? '🟢' : '🔴'; + return `${status} **${p.name}** - ${p.button_count} boutons - <#${p.channel_id}>`; + }).join('\n')); + + return interaction.reply({ embeds: [embed], ephemeral: true }); + } + + case 'refresh': { + const name = interaction.options.getString('nom'); + + if (!name) { + return interaction.reply({ + content: '❌ Vous devez spécifier le nom du panel à actualiser.', + ephemeral: true + }); + } + + const panel = await db.getAsync( + "SELECT * FROM role_panels WHERE guild_id = ? AND name = ?", + [guildId, name] + ); + + if (!panel) { + return interaction.reply({ + content: `❌ Panel "${name}" non trouvé.`, + ephemeral: true + }); + } + + const buttons = await db.allAsync( + "SELECT * FROM role_panel_buttons WHERE panel_id = ?", + [panel.id] + ); + + const messageId = await updatePanelMessage(client, panel, buttons); + + if (messageId) { + return interaction.reply({ + content: `✅ Panel **${name}** actualisé !`, + ephemeral: true + }); + } else { + return interaction.reply({ + content: '❌ Erreur lors de l\'actualisation du panel.', + ephemeral: true + }); + } + } + } + }, + + executePrefix: async (client, message, args) => { + return message.reply('❌ Cette commande est disponible uniquement en slash command. Utilisez `/rolepanel`.'); + } +}); + +// Exporter la fonction de mise à jour pour l'API +module.exports.updatePanelMessage = updatePanelMessage; diff --git a/app/db.js b/app/db.js index 6f0f6d2..c92f0bb 100644 --- a/app/db.js +++ b/app/db.js @@ -410,6 +410,37 @@ db.exec(` last_channel_activity INTEGER, created_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now')) ); + + -- Système de rôles par boutons + CREATE TABLE IF NOT EXISTS role_panels ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + guild_id TEXT NOT NULL, + channel_id TEXT NOT NULL, + message_id TEXT, + name TEXT NOT NULL, + title TEXT, + description TEXT, + color TEXT DEFAULT '#5865F2', + image_url TEXT, + thumbnail_url TEXT, + mode TEXT NOT NULL DEFAULT 'toggle', + exclusive INTEGER NOT NULL DEFAULT 0, + required_role_id TEXT, + enabled INTEGER NOT NULL DEFAULT 1, + created_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now')) + ); + + CREATE TABLE IF NOT EXISTS role_panel_buttons ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + panel_id INTEGER NOT NULL, + role_id TEXT NOT NULL, + label TEXT NOT NULL, + emoji TEXT, + style TEXT NOT NULL DEFAULT 'primary', + position INTEGER NOT NULL DEFAULT 0, + enabled INTEGER NOT NULL DEFAULT 1, + FOREIGN KEY (panel_id) REFERENCES role_panels(id) ON DELETE CASCADE + ); `); module.exports = db; diff --git a/app/events/interactionCreate.js b/app/events/interactionCreate.js new file mode 100644 index 0000000..0bc6201 --- /dev/null +++ b/app/events/interactionCreate.js @@ -0,0 +1,109 @@ +const { Events, EmbedBuilder } = require("discord.js"); +const db = require("../db"); + +module.exports = { + name: Events.InteractionCreate, + async execute(client, interaction) { + // Gérer uniquement les boutons de rôles + if (!interaction.isButton()) return; + if (!interaction.customId.startsWith('role_panel_')) return; + + const buttonId = interaction.customId.replace('role_panel_', ''); + + try { + // Récupérer les infos du bouton + const button = await db.getAsync( + "SELECT rpb.*, rp.mode, rp.exclusive, rp.required_role_id, rp.enabled as panel_enabled FROM role_panel_buttons rpb JOIN role_panels rp ON rpb.panel_id = rp.id WHERE rpb.id = ?", + [buttonId] + ); + + if (!button) { + return interaction.reply({ + content: '❌ Ce bouton n\'est plus valide.', + ephemeral: true + }); + } + + if (!button.panel_enabled || !button.enabled) { + return interaction.reply({ + content: '❌ Ce système de rôles est actuellement désactivé.', + ephemeral: true + }); + } + + // Vérifier le rôle requis + if (button.required_role_id) { + if (!interaction.member.roles.cache.has(button.required_role_id)) { + return interaction.reply({ + content: `❌ Vous devez avoir le rôle <@&${button.required_role_id}> pour utiliser ce menu.`, + ephemeral: true + }); + } + } + + const role = interaction.guild.roles.cache.get(button.role_id); + if (!role) { + return interaction.reply({ + content: '❌ Le rôle associé à ce bouton n\'existe plus.', + ephemeral: true + }); + } + + // Vérifier que le bot peut gérer ce rôle + if (role.position >= interaction.guild.members.me.roles.highest.position) { + return interaction.reply({ + content: '❌ Je ne peux pas gérer ce rôle car il est au-dessus de mon rôle le plus élevé.', + ephemeral: true + }); + } + + const hasRole = interaction.member.roles.cache.has(role.id); + + // Mode exclusif : retirer les autres rôles du même panel + if (button.exclusive && !hasRole) { + const panelButtons = await db.allAsync( + "SELECT role_id FROM role_panel_buttons WHERE panel_id = ? AND id != ?", + [button.panel_id, buttonId] + ); + + for (const btn of panelButtons) { + if (interaction.member.roles.cache.has(btn.role_id)) { + await interaction.member.roles.remove(btn.role_id).catch(() => {}); + } + } + } + + // Gérer le rôle selon le mode + if (hasRole) { + // Mode toggle : retirer le rôle + if (button.mode === 'toggle') { + await interaction.member.roles.remove(role); + return interaction.reply({ + content: `✅ Le rôle ${role} vous a été retiré.`, + ephemeral: true + }); + } else { + // Mode add-only : ne pas retirer + return interaction.reply({ + content: `ℹ️ Vous avez déjà le rôle ${role}.`, + ephemeral: true + }); + } + } else { + // Ajouter le rôle + await interaction.member.roles.add(role); + return interaction.reply({ + content: `✅ Le rôle ${role} vous a été attribué !`, + ephemeral: true + }); + } + + } catch (err) { + console.error('Erreur interaction role panel:', err); + return interaction.reply({ + content: '❌ Une erreur est survenue.', + ephemeral: true + }); + } + } +}; diff --git a/app/public/guild.css b/app/public/guild.css index 45a8499..229b4d8 100644 --- a/app/public/guild.css +++ b/app/public/guild.css @@ -1286,3 +1286,245 @@ body { color: var(--error-color); background: rgba(237, 66, 69, 0.1); } + +/* ===== Modal ===== */ +.modal { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(0, 0, 0, 0.7); + display: flex; + align-items: center; + justify-content: center; + z-index: 1000; + padding: var(--spacing-lg); +} + +.modal-content { + background: var(--bg-card); + border-radius: var(--radius-lg); + width: 100%; + max-width: 500px; + max-height: 90vh; + overflow-y: auto; + box-shadow: 0 10px 40px rgba(0, 0, 0, 0.3); +} + +.modal-content.modal-lg { + max-width: 700px; +} + +.modal-header { + display: flex; + justify-content: space-between; + align-items: center; + padding: var(--spacing-lg); + border-bottom: 1px solid var(--border-color); +} + +.modal-header h3 { + margin: 0; + font-size: 1.2rem; +} + +.modal-close { + background: none; + border: none; + color: var(--text-muted); + font-size: 1.5rem; + cursor: pointer; + padding: 0; + line-height: 1; +} + +.modal-close:hover { + color: var(--text-primary); +} + +.modal-body { + padding: var(--spacing-lg); +} + +.modal-footer { + padding: var(--spacing-lg); + border-top: 1px solid var(--border-color); + display: flex; + justify-content: flex-end; + gap: var(--spacing-sm); + align-items: center; +} + +/* ===== Role Panels ===== */ +.panels-list { + display: flex; + flex-direction: column; + gap: var(--spacing-md); +} + +.panel-item { + background: var(--bg-secondary); + border-radius: var(--radius-md); + overflow: hidden; +} + +.panel-item-header { + display: flex; + justify-content: space-between; + align-items: center; + padding: var(--spacing-md); + border-bottom: 1px solid var(--border-color); +} + +.panel-item-info { + display: flex; + align-items: center; + gap: var(--spacing-sm); +} + +.panel-item-info h4 { + margin: 0; + font-size: 1rem; +} + +.panel-status { + width: 10px; + height: 10px; + border-radius: 50%; +} + +.panel-status.status-active { + background: #57F287; +} + +.panel-status.status-inactive { + background: #ED4245; +} + +.panel-meta { + color: var(--text-muted); + font-size: 0.85rem; +} + +.panel-item-actions { + display: flex; + gap: var(--spacing-xs); +} + +.panel-item-preview { + padding: var(--spacing-md); +} + +.preview-embed { + background: var(--bg-main); + border-left: 4px solid #5865F2; + border-radius: var(--radius-sm); + padding: var(--spacing-md); +} + +.preview-title { + font-weight: 600; + margin-bottom: var(--spacing-xs); +} + +.preview-desc { + color: var(--text-muted); + font-size: 0.9rem; + margin-bottom: var(--spacing-sm); +} + +.preview-buttons { + display: flex; + flex-wrap: wrap; + gap: var(--spacing-xs); +} + +.preview-btn { + display: inline-flex; + align-items: center; + gap: 4px; + padding: 4px 12px; + border-radius: 4px; + font-size: 0.85rem; + font-weight: 500; +} + +.preview-btn-primary { + background: #5865F2; + color: white; +} + +.preview-btn-secondary { + background: #4f545c; + color: white; +} + +.preview-btn-success { + background: #57F287; + color: black; +} + +.preview-btn-danger { + background: #ED4245; + color: white; +} + +.preview-more { + color: var(--text-muted); + font-size: 0.85rem; + padding: 4px 8px; +} + +/* Buttons list in modal */ +.buttons-list { + display: flex; + flex-direction: column; + gap: var(--spacing-sm); +} + +.button-item { + display: flex; + align-items: center; + justify-content: space-between; + gap: var(--spacing-md); + padding: var(--spacing-sm) var(--spacing-md); + background: var(--bg-secondary); + border-radius: var(--radius-sm); +} + +.button-item.button-disabled { + opacity: 0.5; +} + +.button-preview { + flex-shrink: 0; +} + +.button-info { + flex: 1; + color: var(--text-muted); + font-size: 0.9rem; +} + +.button-actions { + display: flex; + gap: var(--spacing-xs); +} + +.btn-xs { + padding: 4px 8px; + font-size: 0.75rem; +} + +.btn-sm { + padding: 6px 12px; + font-size: 0.85rem; +} + +.empty-message, .loading-message { + text-align: center; + color: var(--text-muted); + padding: var(--spacing-lg); +} + diff --git a/app/public/guild.html b/app/public/guild.html index 0bb4a41..b4d098c 100644 --- a/app/public/guild.html +++ b/app/public/guild.html @@ -82,6 +82,10 @@ 🛡️ Anti-Raid + + 🎭 + Rôles par boutons + @@ -1898,6 +1902,164 @@ + +
+
+
+
+ 🎭 +

Rôles par boutons

+
+
+
+

+ Créez des panneaux de rôles interactifs. Les utilisateurs pourront cliquer sur des boutons pour obtenir ou retirer des rôles. +

+ + +
+
Chargement des panels...
+
+ + + +
+
+ + + + + + +
+ @@ -1920,5 +2082,6 @@ + diff --git a/app/public/guild/rolePanelsForm.js b/app/public/guild/rolePanelsForm.js new file mode 100644 index 0000000..57f049d --- /dev/null +++ b/app/public/guild/rolePanelsForm.js @@ -0,0 +1,448 @@ +// ============================================= +// ========== ROLE PANELS FORM ================= +// ============================================= + +(function() { + let panels = []; + let channels = []; + let roles = []; + let currentPanelId = null; + + // Charger les panels + async function loadRolePanels() { + try { + const res = await fetch(`/api/bot/get-role-panels?guildId=${guildId}`); + const data = await res.json(); + + if (data.success) { + panels = data.panels || []; + channels = data.channels || []; + roles = data.roles || []; + + renderPanelsList(); + populateSelects(); + } + } catch (err) { + console.error('Erreur chargement panels:', err); + } + } + + // Remplir les selects + function populateSelects() { + // Salon du panel + const channelSelect = document.getElementById('panel-channel'); + if (channelSelect) { + channelSelect.innerHTML = channels.map(c => ``).join(''); + } + + // Rôle requis + const requiredRoleSelect = document.getElementById('panel-required-role'); + if (requiredRoleSelect) { + requiredRoleSelect.innerHTML = '' + + roles.map(r => ``).join(''); + } + + // Rôle pour nouveau bouton + const newButtonRoleSelect = document.getElementById('new-button-role'); + if (newButtonRoleSelect) { + newButtonRoleSelect.innerHTML = roles.map(r => ``).join(''); + } + } + + // Afficher la liste des panels + function renderPanelsList() { + const container = document.getElementById('role-panels-list'); + if (!container) return; + + if (panels.length === 0) { + container.innerHTML = '
Aucun panel de rôles configuré.
'; + return; + } + + container.innerHTML = panels.map(panel => { + const statusClass = panel.enabled ? 'status-active' : 'status-inactive'; + const statusText = panel.enabled ? 'Actif' : 'Inactif'; + const buttonCount = panel.buttons ? panel.buttons.length : 0; + const channel = channels.find(c => c.id === panel.channel_id); + + return ` +
+
+
+ +

${panel.name}

+ ${buttonCount} boutons • #${channel?.name || 'inconnu'} +
+
+ + + +
+
+
+
+
${panel.title || '🎭 Choisissez vos rôles'}
+
${panel.description || ''}
+
+ ${(panel.buttons || []).slice(0, 5).map(btn => ` + ${btn.emoji || ''} ${btn.label} + `).join('')} + ${buttonCount > 5 ? `+${buttonCount - 5} autres` : ''} +
+
+
+
+ `; + }).join(''); + } + + // Ouvrir le modal de création + function openCreateModal() { + document.getElementById('panel-modal-title').textContent = 'Créer un panel'; + document.getElementById('edit-panel-id').value = ''; + document.getElementById('panel-name').value = ''; + document.getElementById('panel-name').disabled = false; + document.getElementById('panel-title').value = ''; + document.getElementById('panel-description').value = ''; + document.getElementById('panel-color').value = '#5865F2'; + document.getElementById('panel-mode').value = 'toggle'; + document.getElementById('panel-exclusive').checked = false; + document.getElementById('panel-required-role').value = ''; + document.getElementById('panel-image').value = ''; + document.getElementById('panel-thumbnail').value = ''; + document.getElementById('panel-modal').style.display = 'flex'; + } + + // Ouvrir le modal d'édition + window.editPanel = function(panelId) { + const panel = panels.find(p => p.id === panelId); + if (!panel) return; + + document.getElementById('panel-modal-title').textContent = 'Modifier le panel'; + document.getElementById('edit-panel-id').value = panelId; + document.getElementById('panel-name').value = panel.name; + document.getElementById('panel-name').disabled = true; + document.getElementById('panel-channel').value = panel.channel_id; + document.getElementById('panel-title').value = panel.title || ''; + document.getElementById('panel-description').value = panel.description || ''; + document.getElementById('panel-color').value = panel.color || '#5865F2'; + document.getElementById('panel-mode').value = panel.mode || 'toggle'; + document.getElementById('panel-exclusive').checked = panel.exclusive; + document.getElementById('panel-required-role').value = panel.required_role_id || ''; + document.getElementById('panel-image').value = panel.image_url || ''; + document.getElementById('panel-thumbnail').value = panel.thumbnail_url || ''; + document.getElementById('panel-modal').style.display = 'flex'; + }; + + // Fermer le modal + function closeModal(modalId) { + document.getElementById(modalId).style.display = 'none'; + } + + // Sauvegarder le panel + async function savePanel() { + const panelId = document.getElementById('edit-panel-id').value; + const isEdit = !!panelId; + + const data = { + guildId, + name: document.getElementById('panel-name').value, + channelId: document.getElementById('panel-channel').value, + title: document.getElementById('panel-title').value, + description: document.getElementById('panel-description').value, + color: document.getElementById('panel-color').value, + mode: document.getElementById('panel-mode').value, + exclusive: document.getElementById('panel-exclusive').checked, + requiredRoleId: document.getElementById('panel-required-role').value || null, + imageUrl: document.getElementById('panel-image').value || null, + thumbnailUrl: document.getElementById('panel-thumbnail').value || null, + enabled: true + }; + + if (!data.name) { + showStatus('status-panel-form', '❌ Le nom est requis', 'error'); + return; + } + + const statusEl = document.getElementById('status-panel-form'); + statusEl.textContent = 'Sauvegarde en cours...'; + + try { + let res; + if (isEdit) { + data.panelId = panelId; + res = await fetch('/api/bot/update-role-panel', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(data) + }); + } else { + res = await fetch('/api/bot/create-role-panel', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(data) + }); + } + + const result = await res.json(); + + if (result.success) { + showStatus('status-panel-form', '✅ Panel sauvegardé !', 'success'); + setTimeout(() => { + closeModal('panel-modal'); + loadRolePanels(); + }, 1000); + } else { + showStatus('status-panel-form', '❌ ' + (result.error || 'Erreur'), 'error'); + } + } catch (err) { + console.error('Erreur sauvegarde panel:', err); + showStatus('status-panel-form', '❌ Erreur de connexion', 'error'); + } + } + + // Supprimer un panel + window.deletePanel = async function(panelId) { + if (!confirm('Êtes-vous sûr de vouloir supprimer ce panel ? Le message sera également supprimé.')) { + return; + } + + try { + const res = await fetch('/api/bot/delete-role-panel', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ guildId, panelId }) + }); + + const result = await res.json(); + + if (result.success) { + loadRolePanels(); + } else { + alert('Erreur: ' + (result.error || 'Erreur inconnue')); + } + } catch (err) { + console.error('Erreur suppression panel:', err); + alert('Erreur de connexion'); + } + }; + + // Ouvrir le modal des boutons + window.editPanelButtons = function(panelId) { + const panel = panels.find(p => p.id === panelId); + if (!panel) return; + + currentPanelId = panelId; + document.getElementById('buttons-modal-title').textContent = `Boutons: ${panel.name}`; + document.getElementById('buttons-panel-id').value = panelId; + + renderButtonsList(panel.buttons || []); + document.getElementById('buttons-modal').style.display = 'flex'; + }; + + // Afficher la liste des boutons + function renderButtonsList(buttons) { + const container = document.getElementById('panel-buttons-list'); + + if (buttons.length === 0) { + container.innerHTML = '
Aucun bouton configuré. Ajoutez-en un ci-dessous.
'; + return; + } + + container.innerHTML = buttons.map((btn, index) => { + const role = roles.find(r => r.id === btn.role_id); + const statusClass = btn.enabled ? '' : 'button-disabled'; + + return ` +
+
+ ${btn.emoji || ''} ${btn.label} +
+
+ → @${role?.name || 'Rôle inconnu'} +
+
+ + +
+
+ `; + }).join(''); + } + + // Ajouter un bouton + async function addButton() { + const panelId = document.getElementById('buttons-panel-id').value; + const roleId = document.getElementById('new-button-role').value; + const label = document.getElementById('new-button-label').value; + const emoji = document.getElementById('new-button-emoji').value; + const style = document.getElementById('new-button-style').value; + + if (!roleId) { + showStatus('status-buttons-form', '❌ Sélectionnez un rôle', 'error'); + return; + } + + const role = roles.find(r => r.id === roleId); + const finalLabel = label || role?.name || 'Rôle'; + + try { + const res = await fetch('/api/bot/add-panel-button', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + guildId, + panelId, + roleId, + label: finalLabel, + emoji: emoji || null, + style + }) + }); + + const result = await res.json(); + + if (result.success) { + showStatus('status-buttons-form', '✅ Bouton ajouté !', 'success'); + // Reset le formulaire + document.getElementById('new-button-label').value = ''; + document.getElementById('new-button-emoji').value = ''; + // Recharger les panels + await loadRolePanels(); + const panel = panels.find(p => p.id == panelId); + if (panel) renderButtonsList(panel.buttons || []); + } else { + showStatus('status-buttons-form', '❌ ' + (result.error || 'Erreur'), 'error'); + } + } catch (err) { + console.error('Erreur ajout bouton:', err); + showStatus('status-buttons-form', '❌ Erreur de connexion', 'error'); + } + } + + // Activer/désactiver un bouton + window.toggleButton = async function(buttonId, enabled) { + try { + const res = await fetch('/api/bot/update-panel-button', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + guildId, + buttonId, + enabled, + // On garde les autres valeurs + roleId: panels.flatMap(p => p.buttons || []).find(b => b.id === buttonId)?.role_id, + label: panels.flatMap(p => p.buttons || []).find(b => b.id === buttonId)?.label, + emoji: panels.flatMap(p => p.buttons || []).find(b => b.id === buttonId)?.emoji, + style: panels.flatMap(p => p.buttons || []).find(b => b.id === buttonId)?.style, + position: panels.flatMap(p => p.buttons || []).find(b => b.id === buttonId)?.position + }) + }); + + const result = await res.json(); + + if (result.success) { + await loadRolePanels(); + const panelId = document.getElementById('buttons-panel-id').value; + const panel = panels.find(p => p.id == panelId); + if (panel) renderButtonsList(panel.buttons || []); + } + } catch (err) { + console.error('Erreur toggle bouton:', err); + } + }; + + // Supprimer un bouton + window.deleteButton = async function(buttonId) { + if (!confirm('Supprimer ce bouton ?')) return; + + try { + const res = await fetch('/api/bot/delete-panel-button', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ guildId, buttonId }) + }); + + const result = await res.json(); + + if (result.success) { + await loadRolePanels(); + const panelId = document.getElementById('buttons-panel-id').value; + const panel = panels.find(p => p.id == panelId); + if (panel) renderButtonsList(panel.buttons || []); + } + } catch (err) { + console.error('Erreur suppression bouton:', err); + } + }; + + // Publier/actualiser le panel + async function publishPanel() { + const panelId = document.getElementById('buttons-panel-id').value; + + try { + const res = await fetch('/api/bot/publish-role-panel', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ guildId, panelId }) + }); + + const result = await res.json(); + + if (result.success) { + showStatus('status-buttons-form', '✅ Panel publié/actualisé !', 'success'); + } else { + showStatus('status-buttons-form', '❌ ' + (result.error || 'Erreur'), 'error'); + } + } catch (err) { + console.error('Erreur publication panel:', err); + showStatus('status-buttons-form', '❌ Erreur de connexion', 'error'); + } + } + + // Afficher un message de statut + function showStatus(elementId, message, type) { + const el = document.getElementById(elementId); + if (!el) return; + el.textContent = message; + el.className = 'status-message ' + (type || ''); + setTimeout(() => { + el.textContent = ''; + el.className = 'status-message'; + }, 3000); + } + + // Event Listeners (avec vérification d'existence) + const createPanelBtn = document.getElementById('create-panel-btn'); + const closePanelModalBtn = document.getElementById('close-panel-modal'); + const cancelPanelBtn = document.getElementById('cancel-panel-btn'); + const savePanelBtn = document.getElementById('save-panel-btn'); + const closeButtonsModalBtn = document.getElementById('close-buttons-modal'); + const addButtonBtn = document.getElementById('add-button-btn'); + const publishPanelBtn = document.getElementById('publish-panel-btn'); + const panelModal = document.getElementById('panel-modal'); + const buttonsModal = document.getElementById('buttons-modal'); + + if (createPanelBtn) createPanelBtn.addEventListener('click', openCreateModal); + if (closePanelModalBtn) closePanelModalBtn.addEventListener('click', () => closeModal('panel-modal')); + if (cancelPanelBtn) cancelPanelBtn.addEventListener('click', () => closeModal('panel-modal')); + if (savePanelBtn) savePanelBtn.addEventListener('click', savePanel); + if (closeButtonsModalBtn) closeButtonsModalBtn.addEventListener('click', () => closeModal('buttons-modal')); + if (addButtonBtn) addButtonBtn.addEventListener('click', addButton); + if (publishPanelBtn) publishPanelBtn.addEventListener('click', publishPanel); + + // Fermer les modals en cliquant à l'extérieur + if (panelModal) { + panelModal.addEventListener('click', (e) => { + if (e.target === e.currentTarget) closeModal('panel-modal'); + }); + } + if (buttonsModal) { + buttonsModal.addEventListener('click', (e) => { + if (e.target === e.currentTarget) closeModal('buttons-modal'); + }); + } + + // Charger au démarrage + loadRolePanels(); +})(); diff --git a/app/routes/api.js b/app/routes/api.js index f7924a1..eb458de 100644 --- a/app/routes/api.js +++ b/app/routes/api.js @@ -2037,5 +2037,413 @@ module.exports = (app, db, client) => { } }); + // ============================================= + // ========== ROLE PANELS API ================== + // ============================================= + + // Récupérer tous les panels d'un serveur + router.get("/bot/get-role-panels", async (req, res) => { + const { guildId } = req.query; + + 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 panels = await db.allAsync( + "SELECT * FROM role_panels WHERE guild_id = ? ORDER BY created_at DESC", + [guildId] + ); + + // Récupérer les boutons pour chaque panel + for (const panel of panels) { + panel.buttons = await db.allAsync( + "SELECT * FROM role_panel_buttons WHERE panel_id = ? ORDER BY position", + [panel.id] + ); + } + + // Récupérer les salons et rôles + const guild = client.guilds.cache.get(guildId); + const channels = guild?.channels.cache + .filter(c => c.type === 0) + .map(c => ({ id: c.id, name: c.name })) || []; + const roles = guild?.roles.cache + .filter(r => r.id !== guildId && !r.managed) + .sort((a, b) => b.position - a.position) + .map(r => ({ id: r.id, name: r.name, color: r.hexColor })) || []; + + res.json({ success: true, panels, channels, roles }); + + } catch (err) { + console.error("Erreur get role panels:", err); + res.status(500).json({ success: false, error: err.message }); + } + }); + + // Créer un nouveau panel + router.post("/bot/create-role-panel", express.json(), async (req, res) => { + const { guildId, name, channelId, title, description, color, mode, exclusive, requiredRoleId } = 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 { + // Vérifier si un panel avec ce nom existe + const existing = await db.getAsync( + "SELECT id FROM role_panels WHERE guild_id = ? AND name = ?", + [guildId, name] + ); + + if (existing) { + return res.json({ success: false, error: "Un panel avec ce nom existe déjà" }); + } + + const panelId = await new Promise((resolve, reject) => { + db.run( + `INSERT INTO role_panels (guild_id, channel_id, name, title, description, color, mode, exclusive, required_role_id) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, + [guildId, channelId, name, title, description, color || '#5865F2', mode || 'toggle', exclusive ? 1 : 0, requiredRoleId || null], + function(err) { + if (err) reject(err); + else resolve(this.lastID); + } + ); + }); + + res.json({ success: true, panelId }); + + } catch (err) { + console.error("Erreur create role panel:", err); + res.status(500).json({ success: false, error: err.message }); + } + }); + + // Mettre à jour un panel + router.post("/bot/update-role-panel", express.json(), async (req, res) => { + const { guildId, panelId, channelId, title, description, color, imageUrl, thumbnailUrl, mode, exclusive, requiredRoleId, enabled } = 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 { + await new Promise((resolve, reject) => { + db.run( + `UPDATE role_panels SET + channel_id = ?, + title = ?, + description = ?, + color = ?, + image_url = ?, + thumbnail_url = ?, + mode = ?, + exclusive = ?, + required_role_id = ?, + enabled = ? + WHERE id = ? AND guild_id = ?`, + [channelId, title, description, color, imageUrl || null, thumbnailUrl || null, mode, exclusive ? 1 : 0, requiredRoleId || null, enabled ? 1 : 0, panelId, guildId], + (err) => { + if (err) reject(err); + else resolve(); + } + ); + }); + + // Mettre à jour le message Discord + const panel = await db.getAsync("SELECT * FROM role_panels WHERE id = ?", [panelId]); + const buttons = await db.allAsync("SELECT * FROM role_panel_buttons WHERE panel_id = ?", [panelId]); + + if (panel && enabled) { + const { updatePanelMessage } = require('../commands/🔧 Administration/rolepanel'); + await updatePanelMessage(client, panel, buttons); + } + + res.json({ success: true }); + + } catch (err) { + console.error("Erreur update role panel:", err); + res.status(500).json({ success: false, error: err.message }); + } + }); + + // Supprimer un panel + router.post("/bot/delete-role-panel", express.json(), async (req, res) => { + const { guildId, panelId } = 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 { + // Récupérer le panel pour supprimer le message + const panel = await db.getAsync("SELECT * FROM role_panels WHERE id = ? AND guild_id = ?", [panelId, guildId]); + + if (panel && panel.message_id) { + try { + const guild = client.guilds.cache.get(guildId); + const channel = guild?.channels.cache.get(panel.channel_id); + if (channel) { + const message = await channel.messages.fetch(panel.message_id).catch(() => null); + if (message) await message.delete(); + } + } catch {} + } + + // Supprimer les boutons puis le panel + await new Promise((resolve, reject) => { + db.run("DELETE FROM role_panel_buttons WHERE panel_id = ?", [panelId], (err) => { + if (err) reject(err); + else resolve(); + }); + }); + + await new Promise((resolve, reject) => { + db.run("DELETE FROM role_panels WHERE id = ? AND guild_id = ?", [panelId, guildId], (err) => { + if (err) reject(err); + else resolve(); + }); + }); + + res.json({ success: true }); + + } catch (err) { + console.error("Erreur delete role panel:", err); + res.status(500).json({ success: false, error: err.message }); + } + }); + + // Ajouter un bouton à un panel + router.post("/bot/add-panel-button", express.json(), async (req, res) => { + const { guildId, panelId, roleId, label, emoji, style } = 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 { + // Vérifier le panel + const panel = await db.getAsync("SELECT * FROM role_panels WHERE id = ? AND guild_id = ?", [panelId, guildId]); + if (!panel) { + return res.json({ success: false, error: "Panel non trouvé" }); + } + + // Vérifier le nombre de boutons + const count = await db.getAsync("SELECT COUNT(*) as count FROM role_panel_buttons WHERE panel_id = ?", [panelId]); + if (count.count >= 25) { + return res.json({ success: false, error: "Limite de 25 boutons atteinte" }); + } + + // Ajouter le bouton + const buttonId = await new Promise((resolve, reject) => { + db.run( + "INSERT INTO role_panel_buttons (panel_id, role_id, label, emoji, style, position) VALUES (?, ?, ?, ?, ?, ?)", + [panelId, roleId, label, emoji || null, style || 'primary', count.count], + function(err) { + if (err) reject(err); + else resolve(this.lastID); + } + ); + }); + + // Mettre à jour le message + const buttons = await db.allAsync("SELECT * FROM role_panel_buttons WHERE panel_id = ?", [panelId]); + const { updatePanelMessage } = require('../commands/🔧 Administration/rolepanel'); + await updatePanelMessage(client, panel, buttons); + + res.json({ success: true, buttonId }); + + } catch (err) { + console.error("Erreur add panel button:", err); + res.status(500).json({ success: false, error: err.message }); + } + }); + + // Mettre à jour un bouton + router.post("/bot/update-panel-button", express.json(), async (req, res) => { + const { guildId, buttonId, roleId, label, emoji, style, enabled, position } = 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 { + // Vérifier que le bouton appartient bien à un panel de ce serveur + const button = await db.getAsync( + `SELECT rpb.*, rp.guild_id FROM role_panel_buttons rpb + JOIN role_panels rp ON rpb.panel_id = rp.id + WHERE rpb.id = ? AND rp.guild_id = ?`, + [buttonId, guildId] + ); + + if (!button) { + return res.json({ success: false, error: "Bouton non trouvé" }); + } + + await new Promise((resolve, reject) => { + db.run( + "UPDATE role_panel_buttons SET role_id = ?, label = ?, emoji = ?, style = ?, enabled = ?, position = ? WHERE id = ?", + [roleId, label, emoji || null, style, enabled ? 1 : 0, position, buttonId], + (err) => { + if (err) reject(err); + else resolve(); + } + ); + }); + + // Mettre à jour le message + const panel = await db.getAsync("SELECT * FROM role_panels WHERE id = ?", [button.panel_id]); + const buttons = await db.allAsync("SELECT * FROM role_panel_buttons WHERE panel_id = ?", [button.panel_id]); + const { updatePanelMessage } = require('../commands/🔧 Administration/rolepanel'); + await updatePanelMessage(client, panel, buttons); + + res.json({ success: true }); + + } catch (err) { + console.error("Erreur update panel button:", err); + res.status(500).json({ success: false, error: err.message }); + } + }); + + // Supprimer un bouton + router.post("/bot/delete-panel-button", express.json(), async (req, res) => { + const { guildId, buttonId } = 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 { + // Vérifier que le bouton appartient à un panel de ce serveur + const button = await db.getAsync( + `SELECT rpb.*, rp.guild_id FROM role_panel_buttons rpb + JOIN role_panels rp ON rpb.panel_id = rp.id + WHERE rpb.id = ? AND rp.guild_id = ?`, + [buttonId, guildId] + ); + + if (!button) { + return res.json({ success: false, error: "Bouton non trouvé" }); + } + + const panelId = button.panel_id; + + await new Promise((resolve, reject) => { + db.run("DELETE FROM role_panel_buttons WHERE id = ?", [buttonId], (err) => { + if (err) reject(err); + else resolve(); + }); + }); + + // Mettre à jour le message + const panel = await db.getAsync("SELECT * FROM role_panels WHERE id = ?", [panelId]); + const buttons = await db.allAsync("SELECT * FROM role_panel_buttons WHERE panel_id = ?", [panelId]); + const { updatePanelMessage } = require('../commands/🔧 Administration/rolepanel'); + await updatePanelMessage(client, panel, buttons); + + res.json({ success: true }); + + } catch (err) { + console.error("Erreur delete panel button:", err); + res.status(500).json({ success: false, error: err.message }); + } + }); + + // Publier/actualiser un panel + router.post("/bot/publish-role-panel", express.json(), async (req, res) => { + const { guildId, panelId } = 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 panel = await db.getAsync("SELECT * FROM role_panels WHERE id = ? AND guild_id = ?", [panelId, guildId]); + if (!panel) { + return res.json({ success: false, error: "Panel non trouvé" }); + } + + const buttons = await db.allAsync("SELECT * FROM role_panel_buttons WHERE panel_id = ?", [panelId]); + + const { updatePanelMessage } = require('../commands/🔧 Administration/rolepanel'); + const messageId = await updatePanelMessage(client, panel, buttons); + + if (messageId) { + res.json({ success: true, messageId }); + } else { + res.json({ success: false, error: "Erreur lors de la publication" }); + } + + } catch (err) { + console.error("Erreur publish role panel:", err); + res.status(500).json({ success: false, error: err.message }); + } + }); + app.use("/api", router); };