poixpixel-discord-bot/src/events/interactionCreate.ts
Ahmad 2f5c3499e7
feat: add achievement system
Signed-off-by: Ahmad <103906421+ahmadk953@users.noreply.github.com>
2025-04-16 16:52:44 -04:00

224 lines
7.1 KiB
TypeScript

import {
Events,
Interaction,
ButtonInteraction,
ModalSubmitInteraction,
StringSelectMenuInteraction,
} from 'discord.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';
import { processCommandAchievements } from '@/util/achievementManager.js';
export default {
name: Events.InteractionCreate,
execute: async (interaction: Interaction) => {
if (!(await validateInteraction(interaction))) 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);
}
} 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);
await processCommandAchievements(
interaction.user.id,
command.data.name,
interaction.guild!,
);
} else if (
interaction.isUserContextMenuCommand() ||
interaction.isMessageContextMenuCommand()
) {
// @ts-expect-error
await command.execute(interaction);
await processCommandAchievements(
interaction.user.id,
command.data.name,
interaction.guild!,
);
}
}
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);
}