poixpixel-discord-bot/src/util/giveaways/builder.ts
Ahmad d9d5f087e7
feat: add giveaway system
Signed-off-by: Ahmad <103906421+ahmadk953@users.noreply.github.com>
2025-04-13 16:13:14 -04:00

375 lines
11 KiB
TypeScript

import {
ActionRowBuilder,
ButtonBuilder,
ButtonInteraction,
ButtonStyle,
ChatInputCommandInteraction,
EmbedBuilder,
} from 'discord.js';
import { GiveawaySession } from './types.js';
import { DEFAULT_REQUIRE_ALL, DEFAULT_WINNER_COUNT } from './constants.js';
import { getSession, saveSession } from './utils.js';
/**
* Handles the start of the giveaway builder.
* @param interaction The interaction object from the command or button click.
*/
export async function startGiveawayBuilder(
interaction: ChatInputCommandInteraction | ButtonInteraction,
): Promise<void> {
await interaction.deferReply({ flags: ['Ephemeral'] });
const session: GiveawaySession = {
step: 1,
winnerCount: DEFAULT_WINNER_COUNT,
requirements: {
requireAll: DEFAULT_REQUIRE_ALL,
},
};
await saveSession(interaction.user.id, session);
await showBuilderStep(interaction, session);
}
/**
* Handles the display of the current step in the giveaway builder.
* @param interaction The interaction object from the command or button click.
* @param session The current giveaway session.
*/
export async function showBuilderStep(
interaction: any,
session: GiveawaySession,
): Promise<void> {
if (!interaction.isCommand() && interaction.responded) {
return;
}
try {
let embed: EmbedBuilder;
const components: ActionRowBuilder<ButtonBuilder>[] = [];
switch (session.step) {
case 1:
embed = createStep1Embed(session);
components.push(createStep1Buttons(session));
break;
case 2:
embed = createStep2Embed(session);
components.push(...createStep2Buttons(session));
break;
case 3:
embed = createStep3Embed(session);
components.push(...createStep3Buttons(session));
break;
case 4:
embed = createStep4Embed(session);
components.push(...createStep4Buttons());
break;
case 5:
embed = createStep5Embed(session);
components.push(...createStep5Buttons());
break;
default:
embed = new EmbedBuilder()
.setTitle('🎉 Giveaway Creation')
.setDescription('Setting up your giveaway...')
.setColor(0x3498db);
}
if (interaction.replied || interaction.deferred) {
await interaction.editReply({ embeds: [embed], components });
} else {
await interaction.update({ embeds: [embed], components });
}
} catch (error) {
console.error('Error in showBuilderStep:', error);
if (!interaction.replied) {
try {
await interaction.reply({
content: 'There was an error updating the giveaway builder.',
flags: ['Ephemeral'],
});
} catch (replyError) {
console.error('Failed to send error reply:', replyError);
}
}
}
}
/**
* Handles the next step in the giveaway builder.
* @param interaction The interaction object from the button click.
*/
export async function nextBuilderStep(
interaction: ButtonInteraction,
): Promise<void> {
const session = await getSession(interaction.user.id);
if (!session) {
await interaction.reply({
content: 'Your giveaway session has expired. Please start over.',
flags: ['Ephemeral'],
});
return;
}
if (session.step === 1) {
if (!session.prize || !session.endTime) {
await interaction.reply({
content: 'Please set both prize and duration before continuing.',
flags: ['Ephemeral'],
});
return;
}
if (!(session.endTime instanceof Date)) {
await interaction.reply({
content: 'Invalid duration setting. Please set the duration again.',
flags: ['Ephemeral'],
});
return;
}
}
session.step = Math.min(session.step + 1, 5);
await saveSession(interaction.user.id, session);
await showBuilderStep(interaction, session);
}
/**
* Handles the previous step in the giveaway builder.
* @param interaction The interaction object from the button click.
*/
export async function previousBuilderStep(
interaction: ButtonInteraction,
): Promise<void> {
const session = await getSession(interaction.user.id);
if (!session) {
await interaction.reply({
content: 'Your giveaway session has expired. Please start over.',
flags: ['Ephemeral'],
});
return;
}
session.step = Math.max(session.step - 1, 1);
await saveSession(interaction.user.id, session);
await showBuilderStep(interaction, session);
}
function createStep1Embed(session: GiveawaySession): EmbedBuilder {
const endTimeValue =
session.endTime instanceof Date
? `${session.duration} (ends <t:${Math.floor(session.endTime.getTime() / 1000)}:R>)`
: 'Not set';
return new EmbedBuilder()
.setTitle(' Giveaway Creation - Step 1/5')
.setDescription('Set the basic details for your giveaway.')
.setColor(0x3498db)
.addFields([
{ name: 'Prize', value: session.prize || 'Not set', inline: true },
{ name: 'Duration', value: endTimeValue, inline: true },
{ name: 'Winners', value: session.winnerCount.toString(), inline: true },
]);
}
function createStep1Buttons(
session: GiveawaySession,
): ActionRowBuilder<ButtonBuilder> {
return new ActionRowBuilder<ButtonBuilder>().addComponents(
new ButtonBuilder()
.setCustomId('giveaway_set_prize')
.setLabel('Set Prize')
.setStyle(ButtonStyle.Primary),
new ButtonBuilder()
.setCustomId('giveaway_set_duration')
.setLabel('Set Duration')
.setStyle(ButtonStyle.Primary),
new ButtonBuilder()
.setCustomId('giveaway_set_winners')
.setLabel('Set Winners')
.setStyle(ButtonStyle.Primary),
new ButtonBuilder()
.setCustomId('giveaway_next')
.setLabel('Next Step')
.setStyle(ButtonStyle.Success)
.setDisabled(!session.prize || !session.endTime),
);
}
function createStep2Embed(session: GiveawaySession): EmbedBuilder {
const requirementsList = [];
if (session.requirements?.level) {
requirementsList.push(`• Level ${session.requirements.level}+`);
}
if (session.requirements?.roleId) {
requirementsList.push(`• Role <@&${session.requirements.roleId}>`);
}
if (session.requirements?.messageCount) {
requirementsList.push(`${session.requirements.messageCount}+ messages`);
}
const requirementsText = requirementsList.length
? `${session.requirements.requireAll ? 'ALL requirements must be met' : 'ANY ONE requirement must be met'}\n${requirementsList.join('\n')}`
: 'No requirements set';
return new EmbedBuilder()
.setTitle('🎉 Giveaway Creation - Step 2/5')
.setDescription('Set entry requirements for your giveaway (optional).')
.setColor(0x3498db)
.addFields([
{ name: 'Prize', value: session.prize || 'Not set' },
{ name: 'Requirements', value: requirementsText },
]);
}
function createStep2Buttons(
session: GiveawaySession,
): ActionRowBuilder<ButtonBuilder>[] {
return [
new ActionRowBuilder<ButtonBuilder>().addComponents(
new ButtonBuilder()
.setCustomId('giveaway_set_requirements')
.setLabel('Set Requirements')
.setStyle(ButtonStyle.Primary),
new ButtonBuilder()
.setCustomId('giveaway_toggle_logic')
.setLabel(
session.requirements.requireAll ? 'Require ANY' : 'Require ALL',
)
.setStyle(ButtonStyle.Secondary),
),
new ActionRowBuilder<ButtonBuilder>().addComponents(
new ButtonBuilder()
.setCustomId('giveaway_previous')
.setLabel('Previous Step')
.setStyle(ButtonStyle.Secondary),
new ButtonBuilder()
.setCustomId('giveaway_next')
.setLabel('Next Step')
.setStyle(ButtonStyle.Success),
),
];
}
function createStep3Embed(session: GiveawaySession): EmbedBuilder {
const embed = new EmbedBuilder()
.setTitle('🎉 Giveaway Creation - Step 3/5')
.setDescription('Select Giveaway Channel (optional).')
.setColor(0x3498db)
.addFields([
{
name: 'Channel',
value: session.channelId
? `<#${session.channelId}>`
: 'Current Channel',
},
]);
return embed;
}
function createStep3Buttons(
session: GiveawaySession,
): ActionRowBuilder<ButtonBuilder>[] {
return [
new ActionRowBuilder<ButtonBuilder>().addComponents(
new ButtonBuilder()
.setCustomId('giveaway_set_channel')
.setLabel(session.channelId ? 'Change Channel' : 'Set Channel')
.setStyle(ButtonStyle.Primary),
),
new ActionRowBuilder<ButtonBuilder>().addComponents(
new ButtonBuilder()
.setCustomId('giveaway_previous')
.setLabel('Previous Step')
.setStyle(ButtonStyle.Secondary),
new ButtonBuilder()
.setCustomId('giveaway_next')
.setLabel('Next Step')
.setStyle(ButtonStyle.Success),
),
];
}
function createStep4Embed(session: GiveawaySession): EmbedBuilder {
const bonusEntries = session.bonusEntries || {};
const rolesText =
bonusEntries.roles?.map((r) => `<@&${r.id}>: +${r.entries}`).join('\n') ||
'None';
const levelsText =
bonusEntries.levels
?.map((l) => `Level ${l.threshold}+: +${l.entries}`)
.join('\n') || 'None';
const messagesText =
bonusEntries.messages
?.map((m) => `${m.threshold}+ messages: +${m.entries}`)
.join('\n') || 'None';
return new EmbedBuilder()
.setTitle('🎉 Giveaway Creation - Step 4/5')
.setDescription('Configure bonus entries for your giveaway.')
.setColor(0x3498db)
.addFields([
{ name: 'Role Bonuses', value: rolesText, inline: true },
{ name: 'Level Bonuses', value: levelsText, inline: true },
{ name: 'Message Bonuses', value: messagesText, inline: true },
]);
}
function createStep4Buttons(): ActionRowBuilder<ButtonBuilder>[] {
return [
new ActionRowBuilder<ButtonBuilder>().addComponents(
new ButtonBuilder()
.setCustomId('giveaway_bonus_entries')
.setLabel('Set Bonus Entries')
.setStyle(ButtonStyle.Primary),
),
new ActionRowBuilder<ButtonBuilder>().addComponents(
new ButtonBuilder()
.setCustomId('giveaway_previous')
.setLabel('Previous Step')
.setStyle(ButtonStyle.Secondary),
new ButtonBuilder()
.setCustomId('giveaway_next')
.setLabel('Next Step')
.setStyle(ButtonStyle.Success),
),
];
}
function createStep5Embed(session: GiveawaySession): EmbedBuilder {
return new EmbedBuilder()
.setTitle('🎉 Giveaway Creation - Step 5/5')
.setDescription('Finalize your giveaway settings.')
.setColor(0x3498db)
.addFields([
{
name: 'Role to Ping',
value: session.pingRoleId ? `<@&${session.pingRoleId}>` : 'None',
},
]);
}
function createStep5Buttons(): ActionRowBuilder<ButtonBuilder>[] {
return [
new ActionRowBuilder<ButtonBuilder>().addComponents(
new ButtonBuilder()
.setCustomId('giveaway_set_ping_role')
.setLabel('Set Ping Role')
.setStyle(ButtonStyle.Primary),
),
new ActionRowBuilder<ButtonBuilder>().addComponents(
new ButtonBuilder()
.setCustomId('giveaway_previous')
.setLabel('Previous Step')
.setStyle(ButtonStyle.Secondary),
new ButtonBuilder()
.setCustomId('giveaway_publish')
.setLabel('Create Giveaway')
.setStyle(ButtonStyle.Success),
),
];
}