diff --git a/aethex-bot/commands/cooldowns.js b/aethex-bot/commands/cooldowns.js new file mode 100644 index 0000000..a71e2a8 --- /dev/null +++ b/aethex-bot/commands/cooldowns.js @@ -0,0 +1,234 @@ +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`; +} diff --git a/aethex-bot/public/dashboard.html b/aethex-bot/public/dashboard.html index 85a4fcb..5ed8710 100644 --- a/aethex-bot/public/dashboard.html +++ b/aethex-bot/public/dashboard.html @@ -1186,6 +1186,9 @@
+ @@ -2055,6 +2058,30 @@ + +