mirror of
https://github.com/ahmadk953/poixpixel-discord-bot.git
synced 2025-05-14 20:53:05 +00:00
feat: add giveaway system
Signed-off-by: Ahmad <103906421+ahmadk953@users.noreply.github.com>
This commit is contained in:
parent
e898a9238d
commit
d9d5f087e7
23 changed files with 2811 additions and 168 deletions
294
src/commands/fun/giveaway.ts
Normal file
294
src/commands/fun/giveaway.ts
Normal file
|
@ -0,0 +1,294 @@
|
|||
import {
|
||||
SlashCommandBuilder,
|
||||
PermissionsBitField,
|
||||
EmbedBuilder,
|
||||
ChatInputCommandInteraction,
|
||||
} from 'discord.js';
|
||||
|
||||
import { SubcommandCommand } from '@/types/CommandTypes.js';
|
||||
import {
|
||||
getGiveaway,
|
||||
getActiveGiveaways,
|
||||
endGiveaway,
|
||||
rerollGiveaway,
|
||||
} from '@/db/db.js';
|
||||
import {
|
||||
createGiveawayEmbed,
|
||||
formatWinnerMentions,
|
||||
builder,
|
||||
} from '@/util/giveaways/giveawayManager.js';
|
||||
|
||||
const command: SubcommandCommand = {
|
||||
data: new SlashCommandBuilder()
|
||||
.setName('giveaway')
|
||||
.setDescription('Create and manage giveaways')
|
||||
.addSubcommand((sub) =>
|
||||
sub.setName('create').setDescription('Start creating a new giveaway'),
|
||||
)
|
||||
.addSubcommand((sub) =>
|
||||
sub.setName('list').setDescription('List all active giveaways'),
|
||||
)
|
||||
.addSubcommand((sub) =>
|
||||
sub
|
||||
.setName('end')
|
||||
.setDescription('End a giveaway early')
|
||||
.addStringOption((opt) =>
|
||||
opt
|
||||
.setName('id')
|
||||
.setDescription('Id of the giveaway')
|
||||
.setRequired(true),
|
||||
),
|
||||
)
|
||||
.addSubcommand((sub) =>
|
||||
sub
|
||||
.setName('reroll')
|
||||
.setDescription('Reroll winners for a giveaway')
|
||||
.addStringOption((opt) =>
|
||||
opt
|
||||
.setName('id')
|
||||
.setDescription('Id of the giveaway')
|
||||
.setRequired(true),
|
||||
),
|
||||
),
|
||||
|
||||
execute: async (interaction) => {
|
||||
if (!interaction.isChatInputCommand()) return;
|
||||
|
||||
if (
|
||||
!interaction.memberPermissions?.has(
|
||||
PermissionsBitField.Flags.ModerateMembers,
|
||||
)
|
||||
) {
|
||||
await interaction.reply({
|
||||
content: 'You do not have permission to manage giveaways.',
|
||||
ephemeral: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const subcommand = interaction.options.getSubcommand();
|
||||
|
||||
switch (subcommand) {
|
||||
case 'create':
|
||||
await handleCreateGiveaway(interaction);
|
||||
break;
|
||||
case 'list':
|
||||
await handleListGiveaways(interaction);
|
||||
break;
|
||||
case 'end':
|
||||
await handleEndGiveaway(interaction);
|
||||
break;
|
||||
case 'reroll':
|
||||
await handleRerollGiveaway(interaction);
|
||||
break;
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Initialize the giveaway creation process
|
||||
*/
|
||||
async function handleCreateGiveaway(interaction: ChatInputCommandInteraction) {
|
||||
await builder.startGiveawayBuilder(interaction);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the list giveaways subcommand
|
||||
*/
|
||||
async function handleListGiveaways(interaction: ChatInputCommandInteraction) {
|
||||
await interaction.deferReply();
|
||||
|
||||
const activeGiveaways = await getActiveGiveaways();
|
||||
|
||||
if (activeGiveaways.length === 0) {
|
||||
await interaction.editReply('There are no active giveaways at the moment.');
|
||||
return;
|
||||
}
|
||||
|
||||
const embed = new EmbedBuilder()
|
||||
.setTitle('🎉 Active Giveaways')
|
||||
.setColor(0x00ff00)
|
||||
.setTimestamp();
|
||||
|
||||
const giveawayDetails = activeGiveaways.map((g) => {
|
||||
const channel = interaction.guild?.channels.cache.get(g.channelId);
|
||||
const channelMention = channel ? `<#${channel.id}>` : 'Unknown channel';
|
||||
|
||||
return [
|
||||
`**Prize**: ${g.prize}`,
|
||||
`**ID**: ${g.id}`,
|
||||
`**Winners**: ${g.winnerCount}`,
|
||||
`**Ends**: <t:${Math.floor(g.endAt.getTime() / 1000)}:R>`,
|
||||
`**Channel**: ${channelMention}`,
|
||||
`**Entries**: ${g.participants?.length || 0}`,
|
||||
'───────────────────',
|
||||
].join('\n');
|
||||
});
|
||||
|
||||
embed.setDescription(giveawayDetails.join('\n'));
|
||||
|
||||
await interaction.editReply({ embeds: [embed] });
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the end giveaway subcommand
|
||||
*/
|
||||
async function handleEndGiveaway(interaction: ChatInputCommandInteraction) {
|
||||
await interaction.deferReply();
|
||||
|
||||
const id = interaction.options.getString('id', true);
|
||||
const giveaway = await getGiveaway(id, true);
|
||||
|
||||
if (!giveaway) {
|
||||
await interaction.editReply(`Giveaway with ID ${id} not found.`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (giveaway.status !== 'active') {
|
||||
await interaction.editReply('This giveaway has already ended.');
|
||||
return;
|
||||
}
|
||||
|
||||
const endedGiveaway = await endGiveaway(id, true);
|
||||
if (!endedGiveaway) {
|
||||
await interaction.editReply(
|
||||
'Failed to end the giveaway. Please try again.',
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const channel = interaction.guild?.channels.cache.get(giveaway.channelId);
|
||||
if (!channel?.isTextBased()) {
|
||||
await interaction.editReply(
|
||||
'Giveaway channel not found or is not a text channel.',
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const messageId = giveaway.messageId;
|
||||
const giveawayMessage = await channel.messages.fetch(messageId);
|
||||
|
||||
if (!giveawayMessage) {
|
||||
await interaction.editReply('Giveaway message not found.');
|
||||
return;
|
||||
}
|
||||
|
||||
await giveawayMessage.edit({
|
||||
embeds: [
|
||||
createGiveawayEmbed({
|
||||
id: endedGiveaway.id,
|
||||
prize: endedGiveaway.prize,
|
||||
hostId: endedGiveaway.hostId,
|
||||
winnersIds: endedGiveaway.winnersIds ?? [],
|
||||
isEnded: true,
|
||||
footerText: 'Ended early by a moderator',
|
||||
}),
|
||||
],
|
||||
components: [],
|
||||
});
|
||||
|
||||
if (endedGiveaway.winnersIds?.length) {
|
||||
const winnerMentions = formatWinnerMentions(endedGiveaway.winnersIds);
|
||||
await channel.send({
|
||||
content: `Congratulations ${winnerMentions}! You won **${endedGiveaway.prize}**!`,
|
||||
allowedMentions: { users: endedGiveaway.winnersIds },
|
||||
});
|
||||
} else {
|
||||
await channel.send(
|
||||
`No one entered the giveaway for **${endedGiveaway.prize}**!`,
|
||||
);
|
||||
}
|
||||
|
||||
await interaction.editReply('Giveaway ended successfully!');
|
||||
} catch (error) {
|
||||
console.error('Error ending giveaway:', error);
|
||||
await interaction.editReply('Failed to update the giveaway message.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the reroll giveaway subcommand
|
||||
*/
|
||||
async function handleRerollGiveaway(interaction: ChatInputCommandInteraction) {
|
||||
await interaction.deferReply({ flags: ['Ephemeral'] });
|
||||
const id = interaction.options.getString('id', true);
|
||||
|
||||
const originalGiveaway = await getGiveaway(id, true);
|
||||
|
||||
if (!originalGiveaway) {
|
||||
await interaction.editReply(`Giveaway with ID ${id} not found.`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (originalGiveaway.status !== 'ended') {
|
||||
await interaction.editReply(
|
||||
'This giveaway is not yet ended. You can only reroll ended giveaways.',
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!originalGiveaway.participants?.length) {
|
||||
await interaction.editReply(
|
||||
'Cannot reroll because no one entered this giveaway.',
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const rerolledGiveaway = await rerollGiveaway(id);
|
||||
|
||||
if (!rerolledGiveaway) {
|
||||
await interaction.editReply(
|
||||
'Failed to reroll the giveaway. An internal error occurred.',
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const previousWinners = originalGiveaway.winnersIds ?? [];
|
||||
const newWinners = rerolledGiveaway.winnersIds ?? [];
|
||||
|
||||
const winnersChanged = !(
|
||||
previousWinners.length === newWinners.length &&
|
||||
previousWinners.every((w) => newWinners.includes(w))
|
||||
);
|
||||
|
||||
if (!winnersChanged && newWinners.length > 0) {
|
||||
await interaction.editReply(
|
||||
'Could not reroll: No other eligible participants found besides the previous winner(s).',
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (newWinners.length === 0) {
|
||||
await interaction.editReply(
|
||||
'Could not reroll: No eligible participants found.',
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const channel = interaction.guild?.channels.cache.get(
|
||||
rerolledGiveaway.channelId,
|
||||
);
|
||||
if (!channel?.isTextBased()) {
|
||||
await interaction.editReply(
|
||||
'Giveaway channel not found or is not a text channel. Reroll successful but announcement failed.',
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const winnerMentions = formatWinnerMentions(newWinners);
|
||||
await channel.send({
|
||||
content: `🎉 The giveaway for **${rerolledGiveaway.prize}** has been rerolled! New winner(s): ${winnerMentions}`,
|
||||
allowedMentions: { users: newWinners },
|
||||
});
|
||||
|
||||
await interaction.editReply('Giveaway rerolled successfully!');
|
||||
} catch (error) {
|
||||
console.error('Error announcing rerolled giveaway:', error);
|
||||
await interaction.editReply(
|
||||
'Giveaway rerolled, but failed to announce the new winners.',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default command;
|
Loading…
Add table
Add a link
Reference in a new issue