mirror of
https://github.com/ahmadk953/poixpixel-discord-bot.git
synced 2025-05-11 19:23:06 +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
|
@ -1,100 +1,213 @@
|
|||
import { Events, Interaction } from 'discord.js';
|
||||
import {
|
||||
Events,
|
||||
Interaction,
|
||||
ButtonInteraction,
|
||||
ModalSubmitInteraction,
|
||||
StringSelectMenuInteraction,
|
||||
} from 'discord.js';
|
||||
|
||||
import { ExtendedClient } from '../structures/ExtendedClient.js';
|
||||
import { Event } from '../types/EventTypes.js';
|
||||
import { approveFact, deleteFact } from '../db/db.js';
|
||||
import { Event } from '@/types/EventTypes.js';
|
||||
import { approveFact, deleteFact } from '@/db/db.js';
|
||||
import * as GiveawayManager from '@/util/giveaways/giveawayManager.js';
|
||||
import { ExtendedClient } from '@/structures/ExtendedClient.js';
|
||||
import { safelyRespond, validateInteraction } from '@/util/helpers.js';
|
||||
|
||||
export default {
|
||||
name: Events.InteractionCreate,
|
||||
execute: async (interaction: Interaction) => {
|
||||
if (interaction.isCommand()) {
|
||||
const client = interaction.client as ExtendedClient;
|
||||
const command = client.commands.get(interaction.commandName);
|
||||
if (!(await validateInteraction(interaction))) return;
|
||||
|
||||
if (!command) {
|
||||
console.error(
|
||||
`No command matching ${interaction.commandName} was found.`,
|
||||
);
|
||||
return;
|
||||
try {
|
||||
if (interaction.isCommand()) {
|
||||
await handleCommand(interaction);
|
||||
} else if (interaction.isButton()) {
|
||||
await handleButton(interaction);
|
||||
} else if (interaction.isModalSubmit()) {
|
||||
await handleModal(interaction);
|
||||
} else if (interaction.isStringSelectMenu()) {
|
||||
await handleSelectMenu(interaction);
|
||||
} else {
|
||||
console.warn('Unhandled interaction type:', interaction);
|
||||
}
|
||||
|
||||
try {
|
||||
await command.execute(interaction);
|
||||
} catch (error: any) {
|
||||
console.error(`Error executing ${interaction.commandName}`);
|
||||
console.error(error);
|
||||
|
||||
const isUnknownInteractionError =
|
||||
error.code === 10062 ||
|
||||
(error.message && error.message.includes('Unknown interaction'));
|
||||
|
||||
if (!isUnknownInteractionError) {
|
||||
try {
|
||||
if (interaction.replied || interaction.deferred) {
|
||||
await interaction
|
||||
.followUp({
|
||||
content: 'There was an error while executing this command!',
|
||||
flags: ['Ephemeral'],
|
||||
})
|
||||
.catch((e) =>
|
||||
console.error('Failed to send error followup:', e),
|
||||
);
|
||||
} else {
|
||||
await interaction
|
||||
.reply({
|
||||
content: 'There was an error while executing this command!',
|
||||
flags: ['Ephemeral'],
|
||||
})
|
||||
.catch((e) => console.error('Failed to send error reply:', e));
|
||||
}
|
||||
} catch (replyError) {
|
||||
console.error('Failed to respond with error message:', replyError);
|
||||
}
|
||||
} else {
|
||||
console.warn(
|
||||
'Interaction expired before response could be sent (code 10062)',
|
||||
);
|
||||
}
|
||||
}
|
||||
} else if (interaction.isButton()) {
|
||||
const { customId } = interaction;
|
||||
|
||||
if (customId.startsWith('approve_fact_')) {
|
||||
if (!interaction.memberPermissions?.has('ModerateMembers')) {
|
||||
await interaction.reply({
|
||||
content: 'You do not have permission to approve facts.',
|
||||
flags: ['Ephemeral'],
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const factId = parseInt(customId.replace('approve_fact_', ''), 10);
|
||||
await approveFact(factId);
|
||||
|
||||
await interaction.update({
|
||||
content: `✅ Fact #${factId} has been approved by <@${interaction.user.id}>`,
|
||||
components: [],
|
||||
});
|
||||
} else if (customId.startsWith('reject_fact_')) {
|
||||
if (!interaction.memberPermissions?.has('ModerateMembers')) {
|
||||
await interaction.reply({
|
||||
content: 'You do not have permission to reject facts.',
|
||||
flags: ['Ephemeral'],
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const factId = parseInt(customId.replace('reject_fact_', ''), 10);
|
||||
await deleteFact(factId);
|
||||
|
||||
await interaction.update({
|
||||
content: `❌ Fact #${factId} has been rejected by <@${interaction.user.id}>`,
|
||||
components: [],
|
||||
});
|
||||
}
|
||||
} else {
|
||||
console.warn('Unhandled interaction type:', interaction);
|
||||
return;
|
||||
} catch (error) {
|
||||
handleInteractionError(error, interaction);
|
||||
}
|
||||
},
|
||||
} as Event<typeof Events.InteractionCreate>;
|
||||
|
||||
async function handleCommand(interaction: Interaction) {
|
||||
if (!interaction.isCommand()) return;
|
||||
|
||||
const client = interaction.client as ExtendedClient;
|
||||
const command = client.commands.get(interaction.commandName);
|
||||
|
||||
if (!command) {
|
||||
console.error(`No command matching ${interaction.commandName} was found.`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (interaction.isChatInputCommand()) {
|
||||
await command.execute(interaction);
|
||||
} else if (
|
||||
interaction.isUserContextMenuCommand() ||
|
||||
interaction.isMessageContextMenuCommand()
|
||||
) {
|
||||
// @ts-expect-error
|
||||
await command.execute(interaction);
|
||||
}
|
||||
}
|
||||
|
||||
async function handleButton(interaction: Interaction) {
|
||||
if (!interaction.isButton()) return;
|
||||
|
||||
const { customId } = interaction;
|
||||
|
||||
try {
|
||||
const giveawayHandlers: Record<
|
||||
string,
|
||||
(buttonInteraction: ButtonInteraction) => Promise<void>
|
||||
> = {
|
||||
giveaway_start_builder: GiveawayManager.builder.startGiveawayBuilder,
|
||||
giveaway_next: GiveawayManager.builder.nextBuilderStep,
|
||||
giveaway_previous: GiveawayManager.builder.previousBuilderStep,
|
||||
giveaway_set_prize: GiveawayManager.modals.showPrizeModal,
|
||||
giveaway_set_duration: GiveawayManager.dropdowns.showDurationSelect,
|
||||
giveaway_set_winners: GiveawayManager.dropdowns.showWinnerSelect,
|
||||
giveaway_set_requirements: GiveawayManager.modals.showRequirementsModal,
|
||||
giveaway_toggle_logic: GiveawayManager.toggleRequirementLogic,
|
||||
giveaway_set_channel:
|
||||
(interaction.guild?.channels.cache.size ?? 0) > 25
|
||||
? GiveawayManager.modals.showChannelSelectModal
|
||||
: GiveawayManager.dropdowns.showChannelSelect,
|
||||
giveaway_bonus_entries: GiveawayManager.modals.showBonusEntriesModal,
|
||||
giveaway_set_ping_role:
|
||||
(interaction.guild?.roles.cache.size ?? 0) > 25
|
||||
? GiveawayManager.modals.showPingRoleSelectModal
|
||||
: GiveawayManager.dropdowns.showPingRoleSelect,
|
||||
giveaway_publish: GiveawayManager.publishGiveaway,
|
||||
enter_giveaway: GiveawayManager.handlers.handleGiveawayEntry,
|
||||
};
|
||||
|
||||
if (giveawayHandlers[customId]) {
|
||||
await giveawayHandlers[customId](interaction);
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
customId.startsWith('approve_fact_') ||
|
||||
customId.startsWith('reject_fact_')
|
||||
) {
|
||||
await handleFactModeration(interaction, customId);
|
||||
return;
|
||||
}
|
||||
|
||||
console.warn('Unhandled button interaction:', customId);
|
||||
} catch (error) {
|
||||
throw new Error(`Button interaction failed: ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function handleFactModeration(
|
||||
interaction: Interaction,
|
||||
customId: string,
|
||||
) {
|
||||
if (!interaction.isButton()) return;
|
||||
if (!interaction.memberPermissions?.has('ModerateMembers')) {
|
||||
await interaction.reply({
|
||||
content: 'You do not have permission to moderate facts.',
|
||||
ephemeral: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const factId = parseInt(customId.replace(/^(approve|reject)_fact_/, ''), 10);
|
||||
const isApproval = customId.startsWith('approve_fact_');
|
||||
|
||||
if (isApproval) {
|
||||
await approveFact(factId);
|
||||
await interaction.update({
|
||||
content: `✅ Fact #${factId} has been approved by <@${interaction.user.id}>`,
|
||||
components: [],
|
||||
});
|
||||
} else {
|
||||
await deleteFact(factId);
|
||||
await interaction.update({
|
||||
content: `❌ Fact #${factId} has been rejected by <@${interaction.user.id}>`,
|
||||
components: [],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function handleModal(interaction: Interaction) {
|
||||
if (!interaction.isModalSubmit()) return;
|
||||
|
||||
const { customId } = interaction;
|
||||
const modalHandlers: Record<
|
||||
string,
|
||||
(modalInteraction: ModalSubmitInteraction) => Promise<void>
|
||||
> = {
|
||||
giveaway_prize_modal: GiveawayManager.handlers.handlePrizeSubmit,
|
||||
giveaway_custom_duration:
|
||||
GiveawayManager.handlers.handleCustomDurationSubmit,
|
||||
giveaway_requirements_modal:
|
||||
GiveawayManager.handlers.handleRequirementsSubmit,
|
||||
giveaway_bonus_entries_modal:
|
||||
GiveawayManager.handlers.handleBonusEntriesSubmit,
|
||||
giveaway_ping_role_id_modal:
|
||||
GiveawayManager.handlers.handlePingRoleIdSubmit,
|
||||
giveaway_channel_id_modal: GiveawayManager.handlers.handleChannelIdSubmit,
|
||||
};
|
||||
|
||||
try {
|
||||
if (modalHandlers[customId]) {
|
||||
await modalHandlers[customId](interaction);
|
||||
} else {
|
||||
console.warn('Unhandled modal submission interaction:', customId);
|
||||
}
|
||||
} catch (error) {
|
||||
throw new Error(`Modal submission failed: ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function handleSelectMenu(interaction: Interaction) {
|
||||
if (!interaction.isStringSelectMenu()) return;
|
||||
|
||||
const { customId } = interaction;
|
||||
const selectHandlers: Record<
|
||||
string,
|
||||
(selectInteraction: StringSelectMenuInteraction) => Promise<void>
|
||||
> = {
|
||||
giveaway_duration_select: GiveawayManager.handlers.handleDurationSelect,
|
||||
giveaway_winners_select: GiveawayManager.handlers.handleWinnerSelect,
|
||||
giveaway_channel_select: GiveawayManager.handlers.handleChannelSelect,
|
||||
giveaway_ping_role_select: GiveawayManager.handlers.handlePingRoleSelect,
|
||||
};
|
||||
|
||||
try {
|
||||
if (selectHandlers[customId]) {
|
||||
await selectHandlers[customId](interaction);
|
||||
} else {
|
||||
console.warn('Unhandled string select menu interaction:', customId);
|
||||
}
|
||||
} catch (error) {
|
||||
throw new Error(`Select menu interaction failed: ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
function handleInteractionError(error: unknown, interaction: Interaction) {
|
||||
console.error('Interaction error:', error);
|
||||
|
||||
const isUnknownInteractionError =
|
||||
(error as { code?: number })?.code === 10062 ||
|
||||
String(error).includes('Unknown interaction');
|
||||
|
||||
if (isUnknownInteractionError) {
|
||||
console.warn(
|
||||
'Interaction expired before response could be sent (code 10062)',
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const errorMessage = 'An error occurred while processing your request.';
|
||||
safelyRespond(interaction, errorMessage).catch(console.error);
|
||||
}
|
||||
|
|
|
@ -1,22 +1,23 @@
|
|||
import { Client, Events } from 'discord.js';
|
||||
|
||||
import { ensureDbInitialized, setMembers } from '../db/db.js';
|
||||
import { loadConfig } from '../util/configLoader.js';
|
||||
import { Event } from '../types/EventTypes.js';
|
||||
import { scheduleFactOfTheDay } from '../util/factManager.js';
|
||||
import { ensureDbInitialized, setMembers } from '@/db/db.js';
|
||||
import { loadConfig } from '@/util/configLoader.js';
|
||||
import { Event } from '@/types/EventTypes.js';
|
||||
import { scheduleFactOfTheDay } from '@/util/factManager.js';
|
||||
import { scheduleGiveaways } from '@/util/giveaways/giveawayManager.js';
|
||||
|
||||
import {
|
||||
ensureRedisConnection,
|
||||
setDiscordClient as setRedisDiscordClient,
|
||||
} from '../db/redis.js';
|
||||
import { setDiscordClient as setDbDiscordClient } from '../db/db.js';
|
||||
} from '@/db/redis.js';
|
||||
import { setDiscordClient as setDbDiscordClient } from '@/db/db.js';
|
||||
|
||||
export default {
|
||||
name: Events.ClientReady,
|
||||
once: true,
|
||||
execute: async (client: Client) => {
|
||||
const config = loadConfig();
|
||||
try {
|
||||
const config = loadConfig();
|
||||
setRedisDiscordClient(client);
|
||||
setDbDiscordClient(client);
|
||||
|
||||
|
@ -36,10 +37,11 @@ export default {
|
|||
await setMembers(nonBotMembers);
|
||||
|
||||
await scheduleFactOfTheDay(client);
|
||||
await scheduleGiveaways(client);
|
||||
|
||||
console.log(`Ready! Logged in as ${client.user?.tag}`);
|
||||
} catch (error) {
|
||||
console.error('Failed to initialize the bot:', error);
|
||||
}
|
||||
|
||||
console.log(`Ready! Logged in as ${client.user?.tag}`);
|
||||
},
|
||||
} as Event<typeof Events.ClientReady>;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue