const { SlashCommandBuilder, EmbedBuilder, PermissionFlagsBits } = require('discord.js'); module.exports = { data: new SlashCommandBuilder() .setName('level-roles') .setDescription('Manage automatic role rewards for milestones') .setDefaultMemberPermissions(PermissionFlagsBits.Administrator) .addSubcommand(sub => sub.setName('add') .setDescription('Add a role reward for a milestone') .addRoleOption(opt => opt.setName('role') .setDescription('The role to award') .setRequired(true)) .addStringOption(opt => opt.setName('type') .setDescription('Type of milestone') .setRequired(true) .addChoices( { name: 'Level - Awarded at specific level', value: 'level' }, { name: 'Prestige - Awarded at prestige level', value: 'prestige' }, { name: 'Total XP - Awarded at XP milestone', value: 'total_xp' } )) .addIntegerOption(opt => opt.setName('value') .setDescription('Milestone value (level #, prestige #, or XP amount)') .setRequired(true) .setMinValue(1)) .addBooleanOption(opt => opt.setName('stack') .setDescription('Keep previous milestone roles (true) or replace them (false)') .setRequired(false))) .addSubcommand(sub => sub.setName('remove') .setDescription('Remove a role reward') .addRoleOption(opt => opt.setName('role') .setDescription('The role to remove from rewards') .setRequired(true))) .addSubcommand(sub => sub.setName('list') .setDescription('View all configured role rewards')) .addSubcommand(sub => sub.setName('clear') .setDescription('Clear all role rewards for a milestone type') .addStringOption(opt => opt.setName('type') .setDescription('Type of milestone to clear') .setRequired(true) .addChoices( { name: 'Level roles', value: 'level' }, { name: 'Prestige roles', value: 'prestige' }, { name: 'Total XP roles', value: 'total_xp' }, { name: 'All roles', value: 'all' } ))), async execute(interaction, client, supabase) { if (!supabase) { return interaction.reply({ content: '❌ Database not configured. Level roles require Supabase.', ephemeral: true }); } const guildId = interaction.guildId; const subcommand = interaction.options.getSubcommand(); switch (subcommand) { case 'add': return handleAdd(interaction, supabase, guildId); case 'remove': return handleRemove(interaction, supabase, guildId); case 'list': return handleList(interaction, supabase, guildId); case 'clear': return handleClear(interaction, supabase, guildId); } } }; async function handleAdd(interaction, supabase, guildId) { const role = interaction.options.getRole('role'); const type = interaction.options.getString('type'); const value = interaction.options.getInteger('value'); const stack = interaction.options.getBoolean('stack') ?? true; if (role.managed) { return interaction.reply({ content: '❌ Cannot use managed roles (bot roles, integration roles) as rewards.', ephemeral: true }); } const botMember = interaction.guild.members.me; if (role.position >= botMember.roles.highest.position) { return interaction.reply({ content: '❌ I cannot assign this role. It is higher than or equal to my highest role.', ephemeral: true }); } try { const { error } = await supabase .from('level_roles') .upsert({ guild_id: guildId, role_id: role.id, milestone_type: type, milestone_value: value, stack_roles: stack }, { onConflict: 'guild_id,role_id' }); if (error) throw error; const typeNames = { level: 'Level', prestige: 'Prestige', total_xp: 'Total XP' }; const valueDisplay = type === 'total_xp' ? `${value.toLocaleString()} XP` : value; return interaction.reply({ content: `✅ ${role} will be awarded at **${typeNames[type]} ${valueDisplay}**.\nRole stacking: ${stack ? 'Enabled (keep previous roles)' : 'Disabled (replace previous roles)'}`, ephemeral: true }); } catch (error) { console.error('Failed to add level role:', error.message); return interaction.reply({ content: '❌ Failed to add role reward. Please try again.', ephemeral: true }); } } async function handleRemove(interaction, supabase, guildId) { const role = interaction.options.getRole('role'); try { const { data, error } = await supabase .from('level_roles') .delete() .eq('guild_id', guildId) .eq('role_id', role.id) .select(); if (error) throw error; if (!data || data.length === 0) { return interaction.reply({ content: `❌ ${role} is not configured as a role reward.`, ephemeral: true }); } return interaction.reply({ content: `✅ Removed ${role} from role rewards.`, ephemeral: true }); } catch (error) { console.error('Failed to remove level role:', error.message); return interaction.reply({ content: '❌ Failed to remove role reward. Please try again.', ephemeral: true }); } } async function handleList(interaction, supabase, guildId) { try { const { data: roles, error } = await supabase .from('level_roles') .select('*') .eq('guild_id', guildId) .order('milestone_type') .order('milestone_value', { ascending: true }); if (error) throw error; if (!roles || roles.length === 0) { return interaction.reply({ content: '📋 No role rewards configured. Use `/level-roles add` to create some!', ephemeral: true }); } const levelRoles = roles.filter(r => r.milestone_type === 'level'); const prestigeRoles = roles.filter(r => r.milestone_type === 'prestige'); const xpRoles = roles.filter(r => r.milestone_type === 'total_xp'); const formatRoles = (roleList, prefix) => { if (roleList.length === 0) return 'None configured'; return roleList.map(r => { const stackIcon = r.stack_roles ? '📚' : '🔄'; const value = r.milestone_type === 'total_xp' ? `${r.milestone_value.toLocaleString()} XP` : r.milestone_value; return `${stackIcon} <@&${r.role_id}> → ${prefix}${value}`; }).join('\n'); }; const embed = new EmbedBuilder() .setTitle('🏆 Role Rewards Configuration') .setColor(0x5865F2) .addFields( { name: '📊 Level Roles', value: formatRoles(levelRoles, 'Level '), inline: false }, { name: '⭐ Prestige Roles', value: formatRoles(prestigeRoles, 'Prestige '), inline: false }, { name: '💎 Total XP Roles', value: formatRoles(xpRoles, ''), inline: false } ) .setFooter({ text: '📚 = Stack roles | 🔄 = Replace previous' }) .setTimestamp(); return interaction.reply({ embeds: [embed] }); } catch (error) { console.error('Failed to list level roles:', error.message); return interaction.reply({ content: '❌ Failed to fetch role rewards. Please try again.', ephemeral: true }); } } async function handleClear(interaction, supabase, guildId) { const type = interaction.options.getString('type'); try { let query = supabase.from('level_roles').delete().eq('guild_id', guildId); if (type !== 'all') { query = query.eq('milestone_type', type); } const { data, error } = await query.select(); if (error) throw error; const count = data?.length || 0; const typeNames = { level: 'level', prestige: 'prestige', total_xp: 'total XP', all: '' }; return interaction.reply({ content: `✅ Cleared **${count}** ${typeNames[type]} role reward(s).`, ephemeral: true }); } catch (error) { console.error('Failed to clear level roles:', error.message); return interaction.reply({ content: '❌ Failed to clear role rewards. Please try again.', ephemeral: true }); } }