const { SlashCommandBuilder, EmbedBuilder, PermissionFlagsBits, ActionRowBuilder, ButtonBuilder, ButtonStyle, ChannelType } = require('discord.js'); module.exports = { data: new SlashCommandBuilder() .setName('rolepanel') .setDescription('Create and manage role button panels') .setDefaultMemberPermissions(PermissionFlagsBits.ManageRoles) .addSubcommand(sub => sub.setName('create') .setDescription('Create a new role panel') .addChannelOption(option => option.setName('channel') .setDescription('Channel to send the panel to') .setRequired(true) .addChannelTypes(ChannelType.GuildText) ) .addStringOption(option => option.setName('title') .setDescription('Panel title') .setRequired(true) .setMaxLength(256) ) .addStringOption(option => option.setName('description') .setDescription('Panel description') .setRequired(false) .setMaxLength(2000) ) .addStringOption(option => option.setName('color') .setDescription('Embed color') .setRequired(false) .addChoices( { name: '🟣 Purple', value: '7c3aed' }, { name: '🟢 Green', value: '22c55e' }, { name: '🔵 Blue', value: '3b82f6' }, { name: '🟡 Yellow', value: 'eab308' }, { name: '🟠 Orange', value: 'f97316' } ) ) ) .addSubcommand(sub => sub.setName('addrole') .setDescription('Add a role button to a panel') .addStringOption(option => option.setName('message_id') .setDescription('Message ID of the role panel') .setRequired(true) ) .addRoleOption(option => option.setName('role') .setDescription('Role to add') .setRequired(true) ) .addStringOption(option => option.setName('label') .setDescription('Button label (optional, uses role name if not set)') .setRequired(false) .setMaxLength(80) ) .addStringOption(option => option.setName('emoji') .setDescription('Button emoji (optional)') .setRequired(false) ) .addStringOption(option => option.setName('style') .setDescription('Button style') .setRequired(false) .addChoices( { name: 'Blue (Primary)', value: 'primary' }, { name: 'Gray (Secondary)', value: 'secondary' }, { name: 'Green (Success)', value: 'success' }, { name: 'Red (Danger)', value: 'danger' } ) ) ) .addSubcommand(sub => sub.setName('removerole') .setDescription('Remove a role button from a panel') .addStringOption(option => option.setName('message_id') .setDescription('Message ID of the role panel') .setRequired(true) ) .addRoleOption(option => option.setName('role') .setDescription('Role to remove') .setRequired(true) ) ) .addSubcommand(sub => sub.setName('list') .setDescription('List all role panels in this server') ), async execute(interaction, supabase, client) { const subcommand = interaction.options.getSubcommand(); if (subcommand === 'create') { await handleCreate(interaction, supabase); } else if (subcommand === 'addrole') { await handleAddRole(interaction, supabase, client); } else if (subcommand === 'removerole') { await handleRemoveRole(interaction, supabase, client); } else if (subcommand === 'list') { await handleList(interaction, supabase); } }, }; async function handleCreate(interaction, supabase) { await interaction.deferReply({ ephemeral: true }); const channel = interaction.options.getChannel('channel'); const title = interaction.options.getString('title'); const description = interaction.options.getString('description') || 'Click a button below to get or remove a role!'; const color = parseInt(interaction.options.getString('color') || '7c3aed', 16); const embed = new EmbedBuilder() .setColor(color) .setTitle(`🎭 ${title}`) .setDescription(description) .setFooter({ text: 'Click a button to toggle the role' }) .setTimestamp(); try { const message = await channel.send({ embeds: [embed] }); if (supabase) { await supabase.from('role_panels').insert({ message_id: message.id, channel_id: channel.id, guild_id: interaction.guildId, title: title, description: description, color: color.toString(16), roles: [], created_by: interaction.user.id }); } const successEmbed = new EmbedBuilder() .setColor(0x22c55e) .setTitle('✅ Role Panel Created') .setDescription(`Panel created in ${channel}!\n\nUse \`/rolepanel addrole\` with message ID:\n\`${message.id}\``) .setTimestamp(); await interaction.editReply({ embeds: [successEmbed] }); } catch (error) { console.error('Role panel create error:', error); await interaction.editReply({ content: 'Failed to create role panel. Check bot permissions.', ephemeral: true }); } } async function handleAddRole(interaction, supabase, client) { await interaction.deferReply({ ephemeral: true }); const messageId = interaction.options.getString('message_id'); const role = interaction.options.getRole('role'); const label = interaction.options.getString('label') || role.name; const emoji = interaction.options.getString('emoji'); const styleStr = interaction.options.getString('style') || 'primary'; const styleMap = { 'primary': ButtonStyle.Primary, 'secondary': ButtonStyle.Secondary, 'success': ButtonStyle.Success, 'danger': ButtonStyle.Danger }; const style = styleMap[styleStr]; try { let channel = null; let message = null; if (supabase) { const { data: panel } = await supabase .from('role_panels') .select('channel_id') .eq('message_id', messageId) .eq('guild_id', interaction.guildId) .single(); if (panel?.channel_id) { channel = await interaction.guild.channels.fetch(panel.channel_id).catch(() => null); } } if (!channel) { channel = interaction.channel; } message = await channel.messages.fetch(messageId).catch(() => null); if (!message) { return interaction.editReply({ content: 'Could not find that message. Make sure the message ID is correct and the panel exists.' }); } if (message.author.id !== client.user.id) { return interaction.editReply({ content: 'That message was not sent by me. I can only edit my own messages.' }); } const buttonData = { role_id: role.id, role_name: role.name, label: label, emoji: emoji, style: styleStr }; const existingRows = message.components.map(row => ActionRowBuilder.from(row)); const button = new ButtonBuilder() .setCustomId(`role_${role.id}`) .setLabel(label) .setStyle(style); if (emoji) { button.setEmoji(emoji); } let added = false; for (const row of existingRows) { if (row.components.length < 5) { row.addComponents(button); added = true; break; } } if (!added) { if (existingRows.length >= 5) { return interaction.editReply({ content: 'Maximum buttons reached (25). Remove some roles first.' }); } const newRow = new ActionRowBuilder().addComponents(button); existingRows.push(newRow); } await message.edit({ components: existingRows }); if (supabase) { const { data: panel } = await supabase .from('role_panels') .select('roles') .eq('message_id', messageId) .single(); const roles = panel?.roles || []; roles.push(buttonData); await supabase .from('role_panels') .update({ roles: roles }) .eq('message_id', messageId); } const successEmbed = new EmbedBuilder() .setColor(0x22c55e) .setTitle('✅ Role Added') .setDescription(`Added ${role} to the panel!`) .setTimestamp(); await interaction.editReply({ embeds: [successEmbed] }); } catch (error) { console.error('Add role error:', error); await interaction.editReply({ content: `Failed to add role: ${error.message}` }); } } async function handleRemoveRole(interaction, supabase, client) { await interaction.deferReply({ ephemeral: true }); const messageId = interaction.options.getString('message_id'); const role = interaction.options.getRole('role'); try { let channel = null; let message = null; if (supabase) { const { data: panel } = await supabase .from('role_panels') .select('channel_id') .eq('message_id', messageId) .eq('guild_id', interaction.guildId) .single(); if (panel?.channel_id) { channel = await interaction.guild.channels.fetch(panel.channel_id).catch(() => null); } } if (!channel) { channel = interaction.channel; } message = await channel.messages.fetch(messageId).catch(() => null); if (!message) { return interaction.editReply({ content: 'Could not find that message. Make sure the message ID is correct and the panel exists.' }); } const existingRows = message.components.map(row => ActionRowBuilder.from(row)); for (const row of existingRows) { const buttonIndex = row.components.findIndex(btn => btn.data.custom_id === `role_${role.id}`); if (buttonIndex !== -1) { row.components.splice(buttonIndex, 1); } } const filteredRows = existingRows.filter(row => row.components.length > 0); await message.edit({ components: filteredRows }); if (supabase) { const { data: panel } = await supabase .from('role_panels') .select('roles') .eq('message_id', messageId) .single(); const roles = (panel?.roles || []).filter(r => r.role_id !== role.id); await supabase .from('role_panels') .update({ roles: roles }) .eq('message_id', messageId); } const successEmbed = new EmbedBuilder() .setColor(0x22c55e) .setTitle('✅ Role Removed') .setDescription(`Removed ${role} from the panel!`) .setTimestamp(); await interaction.editReply({ embeds: [successEmbed] }); } catch (error) { console.error('Remove role error:', error); await interaction.editReply({ content: `Failed to remove role: ${error.message}` }); } } async function handleList(interaction, supabase) { await interaction.deferReply({ ephemeral: true }); if (!supabase) { return interaction.editReply({ content: 'This feature requires database connection.' }); } try { const { data: panels, error } = await supabase .from('role_panels') .select('*') .eq('guild_id', interaction.guildId) .order('created_at', { ascending: false }); if (error) throw error; if (!panels || panels.length === 0) { return interaction.editReply({ content: 'No role panels found in this server.' }); } const embed = new EmbedBuilder() .setColor(0x7c3aed) .setTitle('🎭 Role Panels') .setDescription(panels.map(p => `**${p.title}**\n` + `Channel: <#${p.channel_id}>\n` + `Message ID: \`${p.message_id}\`\n` + `Roles: ${p.roles?.length || 0}` ).join('\n\n')) .setFooter({ text: `${panels.length} panel(s)` }) .setTimestamp(); await interaction.editReply({ embeds: [embed] }); } catch (error) { console.error('List panels error:', error); await interaction.editReply({ content: 'Failed to fetch panels.' }); } }