export class BeginQuestModal extends ModalBuilder { public customId: string; public filter: (i: ModalSubmitInteraction) => boolean; constructor(title: string, inputFields: string[]) { super(); if (inputFields.length === 0 || inputFields.some(field => field.length === 0)) { throw new Error('Input fields are improperly configured. Check the configuration.'); } this.customId = `modal:beginQuest:${randomUUID()}`; this.filter = i => { i.deferUpdate(); return i.customId === this.customId } this.setCustomId(this.customId) .setTitle(title); const textInputComponents: TextInputBuilder[] = []; for (const field of inputFields) { textInputComponents.push( new TextInputBuilder() .setCustomId(field) .setLabel(field) .setStyle(TextInputStyle.Short) .setRequired(true) ); } for (const component of textInputComponents) { const actionRow = new ActionRowBuilder().addComponents(component); this.addComponents(actionRow); } } } export const handleJoinQuestInteraction = async (interaction: StringSelectMenuInteraction | ChatInputCommandInteraction | ButtonInteraction, questName: string) => { const guildId = interaction.guildId; const discordUserId = interaction.user.id; const client = getClient(); if (!guildId) { await interaction.reply({ embeds: [new ErrorEmbed('Command Error', 'This command can only be used in a server.')], flags: MessageFlags.Ephemeral }); return; } try { const registrationConfig = ConfigService.getInstance().getRegistrationConfig(guildId, questName); if (!registrationConfig) { await interaction.reply({ embeds: [new ErrorEmbed('Quest Not Found', `This quest, \`${questName}\`, is not registered in this server.\n\nStaff should post an updated embed to the channel.`)], flags: MessageFlags.Ephemeral }); return; } if (!registrationConfig.registration_channel_url) { await interaction.reply({ embeds: [new ConfigErrorEmbed('Cannot find registration channel', `You must set a registration channel in the config, or applications will be sent to the void.`)], flags: MessageFlags.Ephemeral }); return; } const modal = new BeginQuestModal(`Quest: ${questName}`, registrationConfig.fields.map(field => field.field_title)); await interaction.showModal(modal); const modalInteraction = await interaction.awaitModalSubmit({ time: 60_000, filter: modal.filter }); const modalFields = modalInteraction.fields.fields; // Key is field title, value is field value // First Create a new registration record for the quest. const { error } = await client.from('registration_record').upsert({ guild_id: guildId, discord_id: discordUserId, game_name: questName, }); if (error) { throw new Error(error.message); } // Create a new registration_record_entry for each field. for (const [fieldTitle, field] of modalFields) { const { error } = await client.from('registration_record_entries').upsert({ guild_id: guildId, discord_id: discordUserId, game_name: questName, field_title: fieldTitle, field_value: field.value }); if (error) { throw new Error(error.message); } } // If we've made it here the application has been successfully saved to the database. // Now we need to send the application to the registration channel with the status of "PENDING" const channelId = registrationConfig.registration_channel_url.split('/').pop(); if (!channelId) { throw new Error('Cannot find registration channel'); } const channel = await interaction.client.channels.fetch(channelId); if (!channel) { throw new Error('Cannot find registration channel'); } if (!('send' in channel)) { throw new Error('Configured channel does not support sending messages'); } const fieldsMap = new Map(); for (const [key, field] of modalFields) { fieldsMap.set(key, field.value); } const statusEmbed = new EmbedBuilder() .setColor(Colors.Blurple) .setTitle('Pending Quest Application') const applicationEmbed = new QuestApplicationEmbed( interaction, { gameNames: [questName], status: ApplicationStatus.PENDING, discordId: discordUserId, fields: fieldsMap }); const approveData = new ComponentData(ComponentType.Button, ComponentAction.APPROVE_QUEST, discordUserId, questName); const delayData = new ComponentData(ComponentType.Button, ComponentAction.DELAY_QUEST, discordUserId, questName); const reportData = new ComponentData(ComponentType.Button, ComponentAction.GET_USER_REPORT_HISTORY, discordUserId, questName); const deleteData = new ComponentData(ComponentType.Button, ComponentAction.DELETE_QUEST, discordUserId, questName); const reportUserData = new ComponentData(ComponentType.Button, ComponentAction.REPORT_USER, discordUserId, questName); const approveQuestButton = new ApproveQuestButton(approveData); const delayQuestButton = new DelayQuestButton(delayData); const reportInfoButton = new ReportInfoButton(reportData); const deleteQuestButton = new DeleteQuestButton(deleteData); const reportUserButton = new ReportUserButton(reportUserData); const message = await channel.send({ embeds: [statusEmbed, applicationEmbed], components: [ new ActionRowBuilder() .addComponents( approveQuestButton, delayQuestButton, reportInfoButton ), new ActionRowBuilder() .addComponents( deleteQuestButton, reportUserButton ) ] }); approveData.messageId = message.id; delayData.messageId = message.id; reportData.messageId = message.id; deleteData.messageId = message.id; reportUserData.messageId = message.id; // Update the message id for each button and save it to the component. approveQuestButton.setCustomId(approveData.customId); delayQuestButton.setCustomId(delayData.customId); reportInfoButton.setCustomId(reportData.customId); deleteQuestButton.setCustomId(deleteData.customId); reportUserButton.setCustomId(reportUserData.customId); await message.edit({ components: [ new ActionRowBuilder() .addComponents( approveQuestButton, delayQuestButton, reportInfoButton ), new ActionRowBuilder() .addComponents( deleteQuestButton, reportUserButton ) ] }); await handleSendQuestStatusDM(modalInteraction, ComponentAction.BEGIN_QUEST, approveData); } catch (e: any) { console.info(e.message); if (!interaction.replied && !interaction.deferred) { await interaction.reply({ embeds: [new ErrorEmbed('Modal Error', `The modal timed out or encountered an error.\n\n${e.message}`)], flags: MessageFlags.Ephemeral }); } await client.from('registration_record').delete().eq('discord_id', discordUserId).eq('game_name', questName); await client.from('registration_record_entries').delete().eq('discord_id', discordUserId).eq('game_name', questName); } }