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
326 lines
10 KiB
JavaScript
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;
|