const { SlashCommandBuilder, EmbedBuilder, PermissionFlagsBits } = require('discord.js'); const { invalidateCooldownCache, DEFAULT_COOLDOWNS } = require('../utils/cooldownManager'); module.exports = { data: new SlashCommandBuilder() .setName('cooldowns') .setDescription('Manage command cooldowns for this server') .setDefaultMemberPermissions(PermissionFlagsBits.Administrator) .addSubcommand(sub => sub.setName('set') .setDescription('Set a custom cooldown for a command') .addStringOption(opt => opt.setName('command') .setDescription('The command to set cooldown for') .setRequired(true) .addChoices( { name: 'work', value: 'work' }, { name: 'daily', value: 'daily' }, { name: 'slots', value: 'slots' }, { name: 'coinflip', value: 'coinflip' }, { name: 'rep', value: 'rep' }, { name: 'trivia', value: 'trivia' }, { name: 'heist', value: 'heist' }, { name: 'duel', value: 'duel' }, { name: 'gift', value: 'gift' }, { name: 'trade', value: 'trade' } )) .addIntegerOption(opt => opt.setName('seconds') .setDescription('Cooldown in seconds (0 = no cooldown, -1 = reset to default)') .setRequired(true) .setMinValue(-1) .setMaxValue(604800))) .addSubcommand(sub => sub.setName('list') .setDescription('View all custom cooldowns for this server')) .addSubcommand(sub => sub.setName('reset') .setDescription('Reset a command to its default cooldown') .addStringOption(opt => opt.setName('command') .setDescription('The command to reset (or "all" for all commands)') .setRequired(true) .addChoices( { name: 'work', value: 'work' }, { name: 'daily', value: 'daily' }, { name: 'slots', value: 'slots' }, { name: 'coinflip', value: 'coinflip' }, { name: 'rep', value: 'rep' }, { name: 'trivia', value: 'trivia' }, { name: 'heist', value: 'heist' }, { name: 'duel', value: 'duel' }, { name: 'gift', value: 'gift' }, { name: 'trade', value: 'trade' }, { name: 'All Commands', value: 'all' } ))), async execute(interaction, client, supabase) { if (!supabase) { return interaction.reply({ content: '❌ Database not configured. Command cooldowns require Supabase.', ephemeral: true }); } const guildId = interaction.guildId; const subcommand = interaction.options.getSubcommand(); switch (subcommand) { case 'set': return handleSet(interaction, supabase, guildId); case 'list': return handleList(interaction, supabase, guildId); case 'reset': return handleReset(interaction, supabase, guildId); } }, DEFAULT_COOLDOWNS }; async function handleSet(interaction, supabase, guildId) { const command = interaction.options.getString('command'); const seconds = interaction.options.getInteger('seconds'); try { if (seconds === -1) { const { error } = await supabase .from('command_cooldowns') .delete() .eq('guild_id', guildId) .eq('command_name', command); if (error) throw error; invalidateCooldownCache(guildId, command); return interaction.reply({ content: `✅ Reset \`/${command}\` to default cooldown (${formatDuration(DEFAULT_COOLDOWNS[command])}).`, ephemeral: true }); } const { error } = await supabase .from('command_cooldowns') .upsert({ guild_id: guildId, command_name: command, cooldown_seconds: seconds, updated_at: new Date().toISOString() }, { onConflict: 'guild_id,command_name' }); if (error) throw error; invalidateCooldownCache(guildId, command); const message = seconds === 0 ? `✅ Disabled cooldown for \`/${command}\`.` : `✅ Set \`/${command}\` cooldown to **${formatDuration(seconds)}**.`; return interaction.reply({ content: message, ephemeral: true }); } catch (error) { console.error('Failed to set cooldown:', error.message); return interaction.reply({ content: '❌ Failed to update cooldown. Please try again.', ephemeral: true }); } } async function handleList(interaction, supabase, guildId) { try { const { data: customCooldowns } = await supabase .from('command_cooldowns') .select('*') .eq('guild_id', guildId) .order('command_name'); const customMap = new Map(); (customCooldowns || []).forEach(c => customMap.set(c.command_name, c.cooldown_seconds)); const lines = Object.entries(DEFAULT_COOLDOWNS).map(([cmd, defaultSec]) => { const customSec = customMap.get(cmd); const isCustom = customSec !== undefined; const currentSec = isCustom ? customSec : defaultSec; if (currentSec === 0) { return `⚡ \`/${cmd}\` - **No cooldown** ${isCustom ? '*(custom)*' : ''}`; } const duration = formatDuration(currentSec); const status = isCustom ? `**${duration}** *(custom)*` : `${duration} *(default)*`; return `⏱️ \`/${cmd}\` - ${status}`; }); const embed = new EmbedBuilder() .setTitle('⏱️ Command Cooldowns') .setColor(0x6366f1) .setDescription(lines.join('\n')) .setFooter({ text: 'Use /cooldowns set to customize' }) .setTimestamp(); return interaction.reply({ embeds: [embed] }); } catch (error) { console.error('Failed to list cooldowns:', error.message); return interaction.reply({ content: '❌ Failed to fetch cooldown settings. Please try again.', ephemeral: true }); } } async function handleReset(interaction, supabase, guildId) { const command = interaction.options.getString('command'); try { let query = supabase.from('command_cooldowns').delete().eq('guild_id', guildId); if (command !== 'all') { query = query.eq('command_name', command); } const { data, error } = await query.select(); if (error) throw error; if (command === 'all') { invalidateCooldownCache(guildId); } else { invalidateCooldownCache(guildId, command); } const count = data?.length || 0; if (command === 'all') { return interaction.reply({ content: `✅ Reset **${count}** command(s) to their default cooldowns.`, ephemeral: true }); } if (count === 0) { return interaction.reply({ content: `ℹ️ \`/${command}\` was already using the default cooldown.`, ephemeral: true }); } return interaction.reply({ content: `✅ Reset \`/${command}\` to default cooldown (${formatDuration(DEFAULT_COOLDOWNS[command])}).`, ephemeral: true }); } catch (error) { console.error('Failed to reset cooldown:', error.message); return interaction.reply({ content: '❌ Failed to reset cooldown. Please try again.', ephemeral: true }); } } function formatDuration(seconds) { if (seconds === 0) return 'No cooldown'; if (seconds < 60) return `${seconds}s`; if (seconds < 3600) return `${Math.floor(seconds / 60)}m`; if (seconds < 86400) { const hours = Math.floor(seconds / 3600); const mins = Math.floor((seconds % 3600) / 60); return mins > 0 ? `${hours}h ${mins}m` : `${hours}h`; } const days = Math.floor(seconds / 86400); const hours = Math.floor((seconds % 86400) / 3600); return hours > 0 ? `${days}d ${hours}h` : `${days}d`; }