AeThex-Bot-Master/aethex-bot/commands/cooldowns.js
sirpiglr 467fd8467f Add command cooldown management to the dashboard interface
Implement new API endpoints and UI elements for managing command cooldowns, including setting, listing, and resetting them. Also, refactor cooldown cache invalidation logic to resolve circular dependency issues.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: aed2e46d-25bb-4b73-81a1-bb9e8437c261
Replit-Commit-Checkpoint-Type: intermediate_checkpoint
Replit-Commit-Event-Id: 10157b12-ee9f-4185-90e0-2ab9db23a6da
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/3bdfff67-975a-46ad-9845-fbb6b4a4c4b5/aed2e46d-25bb-4b73-81a1-bb9e8437c261/bakeZwZ
Replit-Helium-Checkpoint-Created: true
2025-12-12 23:53:31 +00:00

234 lines
7.6 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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`;
}