const { SlashCommandBuilder, EmbedBuilder, PermissionFlagsBits, ActionRowBuilder, ButtonBuilder, ButtonStyle } = require('discord.js'); module.exports = { data: new SlashCommandBuilder() .setName('giveaway') .setDescription('Create and manage giveaways') .setDefaultMemberPermissions(PermissionFlagsBits.ManageMessages) .addSubcommand(sub => sub.setName('start') .setDescription('Start a new giveaway') .addStringOption(option => option.setName('prize') .setDescription('What are you giving away?') .setRequired(true) .setMaxLength(256) ) .addIntegerOption(option => option.setName('duration') .setDescription('Duration in minutes') .setRequired(true) .setMinValue(1) .setMaxValue(10080) ) .addIntegerOption(option => option.setName('winners') .setDescription('Number of winners') .setRequired(false) .setMinValue(1) .setMaxValue(20) ) .addRoleOption(option => option.setName('required_role') .setDescription('Role required to enter (optional)') .setRequired(false) ) ) .addSubcommand(sub => sub.setName('end') .setDescription('End a giveaway early') .addStringOption(option => option.setName('message_id') .setDescription('Message ID of the giveaway') .setRequired(true) ) ) .addSubcommand(sub => sub.setName('reroll') .setDescription('Reroll a giveaway winner') .addStringOption(option => option.setName('message_id') .setDescription('Message ID of the giveaway') .setRequired(true) ) ) .addSubcommand(sub => sub.setName('list') .setDescription('List active giveaways') ), async execute(interaction, supabase, client) { const subcommand = interaction.options.getSubcommand(); if (subcommand === 'start') { await handleStart(interaction, supabase, client); } else if (subcommand === 'end') { await handleEnd(interaction, supabase, client); } else if (subcommand === 'reroll') { await handleReroll(interaction, supabase, client); } else if (subcommand === 'list') { await handleList(interaction, supabase); } }, }; async function handleStart(interaction, supabase, client) { await interaction.deferReply({ ephemeral: true }); const prize = interaction.options.getString('prize'); const duration = interaction.options.getInteger('duration'); const winnersCount = interaction.options.getInteger('winners') || 1; const requiredRole = interaction.options.getRole('required_role'); const endTime = Date.now() + (duration * 60 * 1000); const endTimestamp = Math.floor(endTime / 1000); const embed = new EmbedBuilder() .setColor(0xfbbf24) .setTitle('🎉 GIVEAWAY 🎉') .setDescription(`**${prize}**\n\nClick the button below to enter!`) .addFields( { name: '⏰ Ends', value: ``, inline: true }, { name: '🏆 Winners', value: `${winnersCount}`, inline: true }, { name: '👥 Entries', value: '0', inline: true } ) .setFooter({ text: `Hosted by ${interaction.user.tag}` }) .setTimestamp(new Date(endTime)); if (requiredRole) { embed.addFields({ name: '🔒 Required Role', value: `${requiredRole}`, inline: false }); } const button = new ButtonBuilder() .setCustomId('giveaway_enter') .setLabel('🎁 Enter Giveaway') .setStyle(ButtonStyle.Success); const row = new ActionRowBuilder().addComponents(button); try { const message = await interaction.channel.send({ embeds: [embed], components: [row] }); if (supabase) { await supabase.from('giveaways').insert({ message_id: message.id, channel_id: interaction.channelId, guild_id: interaction.guildId, prize: prize, winners_count: winnersCount, required_role: requiredRole?.id || null, host_id: interaction.user.id, end_time: new Date(endTime).toISOString(), entries: [], status: 'active' }); } client.giveaways = client.giveaways || new Map(); client.giveaways.set(message.id, { channelId: interaction.channelId, endTime: endTime, prize: prize, winnersCount: winnersCount, requiredRole: requiredRole?.id, entries: [] }); setTimeout(() => endGiveaway(message.id, client, supabase), duration * 60 * 1000); const successEmbed = new EmbedBuilder() .setColor(0x22c55e) .setTitle('✅ Giveaway Started!') .setDescription(`Giveaway for **${prize}** has started!\n\nEnds `) .setTimestamp(); await interaction.editReply({ embeds: [successEmbed] }); } catch (error) { console.error('Giveaway start error:', error); await interaction.editReply({ content: 'Failed to start giveaway.' }); } } async function handleEnd(interaction, supabase, client) { await interaction.deferReply({ ephemeral: true }); const messageId = interaction.options.getString('message_id'); try { await endGiveaway(messageId, client, supabase, interaction.channel); await interaction.editReply({ content: '✅ Giveaway ended!' }); } catch (error) { console.error('Giveaway end error:', error); await interaction.editReply({ content: 'Failed to end giveaway. Check the message ID.' }); } } async function handleReroll(interaction, supabase, client) { await interaction.deferReply({ ephemeral: true }); const messageId = interaction.options.getString('message_id'); try { if (!supabase) { return interaction.editReply({ content: 'Database not available.' }); } const { data: giveaway, error } = await supabase .from('giveaways') .select('*') .eq('message_id', messageId) .single(); if (error || !giveaway) { return interaction.editReply({ content: 'Giveaway not found.' }); } const entries = giveaway.entries || []; if (entries.length === 0) { return interaction.editReply({ content: 'No entries to reroll.' }); } const winner = entries[Math.floor(Math.random() * entries.length)]; const channel = await client.channels.fetch(giveaway.channel_id); await channel.send({ content: `🎉 New winner: <@${winner}>!\nPrize: **${giveaway.prize}**` }); await interaction.editReply({ content: '✅ Rerolled winner!' }); } catch (error) { console.error('Giveaway reroll error:', error); await interaction.editReply({ content: 'Failed to reroll.' }); } } async function handleList(interaction, supabase) { await interaction.deferReply({ ephemeral: true }); if (!supabase) { return interaction.editReply({ content: 'Database not available.' }); } try { const { data: giveaways, error } = await supabase .from('giveaways') .select('*') .eq('guild_id', interaction.guildId) .eq('status', 'active') .order('end_time', { ascending: true }); if (error) throw error; if (!giveaways || giveaways.length === 0) { return interaction.editReply({ content: 'No active giveaways.' }); } const embed = new EmbedBuilder() .setColor(0xfbbf24) .setTitle('🎉 Active Giveaways') .setDescription(giveaways.map(g => { const endTimestamp = Math.floor(new Date(g.end_time).getTime() / 1000); return `**${g.prize}**\nEnds: \nEntries: ${g.entries?.length || 0}\nMessage ID: \`${g.message_id}\``; }).join('\n\n')) .setTimestamp(); await interaction.editReply({ embeds: [embed] }); } catch (error) { console.error('Giveaway list error:', error); await interaction.editReply({ content: 'Failed to fetch giveaways.' }); } } async function endGiveaway(messageId, client, supabase, channel = null) { try { let giveawayData = client.giveaways?.get(messageId); let entries = giveawayData?.entries || []; let prize = giveawayData?.prize || 'Unknown Prize'; let winnersCount = giveawayData?.winnersCount || 1; let channelId = giveawayData?.channelId; if (supabase) { const { data } = await supabase .from('giveaways') .select('*') .eq('message_id', messageId) .single(); if (data) { entries = data.entries || []; prize = data.prize; winnersCount = data.winners_count; channelId = data.channel_id; await supabase .from('giveaways') .update({ status: 'ended' }) .eq('message_id', messageId); } } if (!channelId) return; const giveawayChannel = channel || await client.channels.fetch(channelId); const message = await giveawayChannel.messages.fetch(messageId).catch(() => null); if (!message) return; const winners = []; const entriesCopy = [...entries]; for (let i = 0; i < winnersCount && entriesCopy.length > 0; i++) { const index = Math.floor(Math.random() * entriesCopy.length); winners.push(entriesCopy.splice(index, 1)[0]); } const endedEmbed = EmbedBuilder.from(message.embeds[0]) .setColor(0x6b7280) .setTitle('🎉 GIVEAWAY ENDED 🎉') .setFields( { name: '🏆 Winner(s)', value: winners.length > 0 ? winners.map(w => `<@${w}>`).join(', ') : 'No valid entries', inline: false }, { name: '👥 Total Entries', value: `${entries.length}`, inline: true } ); const disabledButton = new ButtonBuilder() .setCustomId('giveaway_ended') .setLabel('Giveaway Ended') .setStyle(ButtonStyle.Secondary) .setDisabled(true); const row = new ActionRowBuilder().addComponents(disabledButton); await message.edit({ embeds: [endedEmbed], components: [row] }); if (winners.length > 0) { await giveawayChannel.send({ content: `🎉 Congratulations ${winners.map(w => `<@${w}>`).join(', ')}! You won **${prize}**!` }); } client.giveaways?.delete(messageId); } catch (error) { console.error('End giveaway error:', error); } } module.exports.endGiveaway = endGiveaway;