1import {
2 ChatInputCommandInteraction,
3 SlashCommandBuilder,
4 EmbedBuilder,
5} from 'discord.js'
6import { Command } from '@/types/bot'
7import { supabase } from '@/configs/supabase'
8import ms from 'ms'
9
10export default {
11 data: new SlashCommandBuilder()
12 .setName('giveaway')
13 .setDescription('Start or manage giveaways')
14 .addSubcommand((subcommand) =>
15 subcommand
16 .setName('start')
17 .setDescription('Start a new giveaway')
18 .addStringOption((option) =>
19 option
20 .setName('prize')
21 .setDescription('What are you giving away?')
22 .setRequired(true),
23 )
24 .addStringOption((option) =>
25 option
26 .setName('duration')
27 .setDescription('How long should the giveaway last? (e.g., 1h, 1d)')
28 .setRequired(true),
29 )
30 .addIntegerOption((option) =>
31 option
32 .setName('winners')
33 .setDescription('Number of winners')
34 .setMinValue(1)
35 .setMaxValue(10)
36 .setRequired(false),
37 ),
38 )
39 .addSubcommand((subcommand) =>
40 subcommand
41 .setName('end')
42 .setDescription('End a giveaway early')
43 .addStringOption((option) =>
44 option
45 .setName('message_id')
46 .setDescription('ID of the giveaway message')
47 .setRequired(true),
48 ),
49 ),
50
51 async execute(interaction: ChatInputCommandInteraction) {
52 const subcommand = interaction.options.getSubcommand()
53
54 if (subcommand === 'start') {
55 const prize = interaction.options.getString('prize', true)
56 const duration = interaction.options.getString('duration', true)
57 const winners = interaction.options.getInteger('winners') || 1
58
59 const durationMs = ms(duration)
60 if (!durationMs) {
61 return interaction.reply({
62 content: 'Invalid duration format! Use formats like: 1h, 30m, 1d',
63 ephemeral: true,
64 })
65 }
66
67 const endsAt = new Date(Date.now() + durationMs)
68
69 const embed = new EmbedBuilder()
70 .setTitle('🎉 GIVEAWAY 🎉')
71 .setDescription(
72 `
73 **Prize:** ${prize}
74 **Winners:** ${winners}
75 **Ends:** <t:${Math.floor(endsAt.getTime() / 1000)}:R>
76
77 React with 🎉 to enter!
78 `,
79 )
80 .setColor('#FF9300')
81 .setFooter({ text: `Hosted by ${interaction.user.tag}` })
82
83 const message = await interaction.reply({
84 embeds: [embed],
85 fetchReply: true,
86 })
87 await message.react('🎉')
88
89 // Store giveaway in database
90 await supabase.from('giveaways').insert({
91 message_id: message.id,
92 channel_id: interaction.channelId,
93 prize,
94 winner_count: winners,
95 host_id: interaction.user.id,
96 ends_at: endsAt.toISOString(),
97 })
98 } else if (subcommand === 'end') {
99 const messageId = interaction.options.getString('message_id', true)
100 await endGiveaway(interaction, messageId)
101 }
102 },
103} as Command
104
105async function endGiveaway(
106 interaction: ChatInputCommandInteraction,
107 messageId: string,
108) {
109 const { data: giveaway } = await supabase
110 .from('giveaways')
111 .select('*')
112 .eq('message_id', messageId)
113 .single()
114
115 if (!giveaway || giveaway.ended) {
116 return interaction.reply({
117 content: 'Giveaway not found or already ended!',
118 ephemeral: true,
119 })
120 }
121
122 try {
123 const message = await interaction.channel?.messages.fetch(messageId)
124 if (!message) throw new Error('Message not found')
125
126 const reaction = message.reactions.cache.get('🎉')
127 if (!reaction) throw new Error('Reaction not found')
128
129 const users = await reaction.users.fetch()
130 const validParticipants = users.filter((user) => !user.bot)
131
132 if (validParticipants.size < 1) {
133 await interaction.reply('No valid participants found!')
134 return
135 }
136
137 const winners = validParticipants.random(
138 Math.min(giveaway.winner_count, validParticipants.size),
139 )
140
141 const winnerAnnouncement = new EmbedBuilder()
142 .setTitle('🎉 Giveaway Ended! 🎉')
143 .setDescription(
144 `
145 **Prize:** ${giveaway.prize}
146 **Winners:** ${winners.map((w) => `<@${w.id}>`).join(', ')}
147 `,
148 )
149 .setColor('#00FF00')
150 .setFooter({ text: `Giveaway ID: ${messageId}` })
151
152 await interaction.reply({ embeds: [winnerAnnouncement] })
153
154 // Update giveaway as ended
155 await supabase
156 .from('giveaways')
157 .update({ ended: true })
158 .eq('message_id', messageId)
159 } catch (error) {
160 await interaction.reply({
161 content: 'Failed to end giveaway!',
162 ephemeral: true,
163 })
164 }
165}
166