AeThex-Bot-Master/aethex-bot/commands/giveaway.js
sirpiglr 6f5c37959f Add new commands and improve existing ones for better user experience
Introduces new commands like `/automod`, `/giveaway`, `/rolepanel`, and `/schedule`. Enhances existing commands such as `/announce`, `/help`, `/leaderboard`, `/profile`, and `/serverinfo` with new features and improved embed designs. Updates welcome and goodbye listeners with rich embeds. Fixes a critical issue in the `/rolepanel` command regarding channel fetching. Adds interaction handling for role buttons and giveaway entries.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: aed2e46d-25bb-4b73-81a1-bb9e8437c261
Replit-Commit-Checkpoint-Type: intermediate_checkpoint
Replit-Commit-Event-Id: eefee140-1301-4b6f-9439-2b0b883aa40a
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/3bdfff67-975a-46ad-9845-fbb6b4a4c4b5/aed2e46d-25bb-4b73-81a1-bb9e8437c261/qAaysIh
Replit-Helium-Checkpoint-Created: true
2025-12-08 07:24:49 +00:00

326 lines
10 KiB
JavaScript

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: `<t:${endTimestamp}:R>`, 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 <t:${endTimestamp}:R>`)
.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: <t:${endTimestamp}:R>\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;