const { SlashCommandBuilder, EmbedBuilder, PermissionFlagsBits } = require('discord.js'); module.exports = { data: new SlashCommandBuilder() .setName('activity-roles') .setDescription('Manage automatic role rewards for activity milestones') .setDefaultMemberPermissions(PermissionFlagsBits.Administrator) .addSubcommand(sub => sub.setName('add') .setDescription('Add a role reward for an activity milestone') .addRoleOption(opt => opt.setName('role') .setDescription('The role to award') .setRequired(true)) .addStringOption(opt => opt.setName('type') .setDescription('Type of activity milestone') .setRequired(true) .addChoices( { name: 'Messages - Total messages sent', value: 'messages' }, { name: 'Voice Hours - Time in voice channels', value: 'voice_hours' }, { name: 'Daily Streak - Consecutive daily claims', value: 'daily_streak' }, { name: 'Reactions Given - Reactions added to messages', value: 'reactions_given' }, { name: 'Reactions Received - Reactions on your messages', value: 'reactions_received' }, { name: 'Commands Used - Bot commands used', value: 'commands_used' } )) .addIntegerOption(opt => opt.setName('value') .setDescription('Milestone value (message count, hours, streak days, etc.)') .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 an activity 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 activity role rewards')) .addSubcommand(sub => sub.setName('clear') .setDescription('Clear all role rewards for an activity type') .addStringOption(opt => opt.setName('type') .setDescription('Type of activity to clear') .setRequired(true) .addChoices( { name: 'Message roles', value: 'messages' }, { name: 'Voice hour roles', value: 'voice_hours' }, { name: 'Daily streak roles', value: 'daily_streak' }, { name: 'Reactions given roles', value: 'reactions_given' }, { name: 'Reactions received roles', value: 'reactions_received' }, { name: 'Commands used roles', value: 'commands_used' }, { name: 'All activity roles', value: 'all' } ))), async execute(interaction, client, supabase) { if (!supabase) { return interaction.reply({ content: '❌ Database not configured. Activity 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); } } }; const ACTIVITY_TYPES = ['messages', 'voice_hours', 'daily_streak', 'reactions_given', 'reactions_received', 'commands_used']; 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('activity_roles') .upsert({ guild_id: guildId, role_id: role.id, milestone_type: type, milestone_value: value, stack_roles: stack, created_at: new Date().toISOString() }, { onConflict: 'guild_id,role_id' }); if (error) throw error; const typeNames = { messages: 'Messages', voice_hours: 'Voice Hours', daily_streak: 'Daily Streak', reactions_given: 'Reactions Given', reactions_received: 'Reactions Received', commands_used: 'Commands Used' }; const valueDisplay = type === 'voice_hours' ? `${value} hour(s)` : type === 'daily_streak' ? `${value} day(s)` : value.toLocaleString(); return interaction.reply({ content: `✅ ${role} will be awarded at **${valueDisplay} ${typeNames[type]}**.\nRole stacking: ${stack ? 'Enabled (keep previous roles)' : 'Disabled (replace previous roles)'}`, ephemeral: true }); } catch (error) { console.error('Failed to add activity role:', error.message); return interaction.reply({ content: '❌ Failed to add activity 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('activity_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 an activity role reward.`, ephemeral: true }); } return interaction.reply({ content: `✅ Removed ${role} from activity role rewards.`, ephemeral: true }); } catch (error) { console.error('Failed to remove activity role:', error.message); return interaction.reply({ content: '❌ Failed to remove activity role reward. Please try again.', ephemeral: true }); } } async function handleList(interaction, supabase, guildId) { try { const { data: roles, error } = await supabase .from('activity_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 activity role rewards configured. Use `/activity-roles add` to create some!', ephemeral: true }); } const messageRoles = roles.filter(r => r.milestone_type === 'messages'); const voiceRoles = roles.filter(r => r.milestone_type === 'voice_hours'); const streakRoles = roles.filter(r => r.milestone_type === 'daily_streak'); const reactGivenRoles = roles.filter(r => r.milestone_type === 'reactions_given'); const reactReceivedRoles = roles.filter(r => r.milestone_type === 'reactions_received'); const commandRoles = roles.filter(r => r.milestone_type === 'commands_used'); const formatRoles = (roleList, suffix) => { if (roleList.length === 0) return 'None configured'; return roleList.map(r => { const stackIcon = r.stack_roles ? '📚' : '🔄'; return `${stackIcon} <@&${r.role_id}> → ${r.milestone_value.toLocaleString()}${suffix}`; }).join('\n'); }; const embed = new EmbedBuilder() .setTitle('🎯 Activity Role Rewards') .setColor(0x10B981) .setDescription('Roles awarded automatically when users reach activity milestones.') .addFields( { name: '💬 Message Roles', value: formatRoles(messageRoles, ' messages'), inline: false }, { name: '🎤 Voice Hour Roles', value: formatRoles(voiceRoles, ' hours'), inline: false }, { name: '🔥 Daily Streak Roles', value: formatRoles(streakRoles, ' day streak'), inline: false } ) .setFooter({ text: '📚 = Stack roles | 🔄 = Replace previous' }) .setTimestamp(); if (reactGivenRoles.length > 0 || reactReceivedRoles.length > 0 || commandRoles.length > 0) { embed.addFields( { name: '👍 Reactions Given Roles', value: formatRoles(reactGivenRoles, ' reactions'), inline: false }, { name: '❤️ Reactions Received Roles', value: formatRoles(reactReceivedRoles, ' reactions'), inline: false }, { name: '⚡ Command Usage Roles', value: formatRoles(commandRoles, ' commands'), inline: false } ); } return interaction.reply({ embeds: [embed] }); } catch (error) { console.error('Failed to list activity roles:', error.message); return interaction.reply({ content: '❌ Failed to fetch activity role rewards. Please try again.', ephemeral: true }); } } async function handleClear(interaction, supabase, guildId) { const type = interaction.options.getString('type'); try { let query = supabase.from('activity_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 = { messages: 'message', voice_hours: 'voice hour', daily_streak: 'daily streak', reactions_given: 'reactions given', reactions_received: 'reactions received', commands_used: 'command usage', all: 'activity' }; return interaction.reply({ content: `✅ Cleared **${count}** ${typeNames[type]} role reward(s).`, ephemeral: true }); } catch (error) { console.error('Failed to clear activity roles:', error.message); return interaction.reply({ content: '❌ Failed to clear activity role rewards. Please try again.', ephemeral: true }); } }