diff --git a/src/database/AliceDBCollection.ts b/src/database/AliceDBCollection.ts index 485f9bae1..6ca0cba8e 100644 --- a/src/database/AliceDBCollection.ts +++ b/src/database/AliceDBCollection.ts @@ -29,6 +29,7 @@ import { DanCourseLeaderboardScoreCollectionManager } from "./managers/aliceDb/D import { DanCourseScoreCollectionManager } from "./managers/aliceDb/DanCourseScoreCollectionManager"; import { RecentPlaysCollectionManager } from "./managers/aliceDb/RecentPlaysCollectionManager"; import { SupportTicketCollectionManager } from "./managers/aliceDb/SupportTicketCollectionManager"; +import { SupportTicketPresetCollectionManager } from "./managers/aliceDb/SupportTicketPresetCollectionManager"; /** * Contains collections from Alice DB. @@ -187,6 +188,11 @@ export class AliceDBCollection { */ readonly supportTicket: SupportTicketCollectionManager; + /** + * The database collection for support ticket presets. + */ + readonly supportTicketPreset: SupportTicketPresetCollectionManager; + /** * @param aliceDb The database that is only used by this bot (my database). */ @@ -281,5 +287,8 @@ export class AliceDBCollection { this.supportTicket = new SupportTicketCollectionManager( aliceDb.collection("supportticket"), ); + this.supportTicketPreset = new SupportTicketPresetCollectionManager( + aliceDb.collection("supportticketpreset"), + ); } } diff --git a/src/database/managers/aliceDb/SupportTicketPresetCollectionManager.ts b/src/database/managers/aliceDb/SupportTicketPresetCollectionManager.ts new file mode 100644 index 000000000..633a3e16e --- /dev/null +++ b/src/database/managers/aliceDb/SupportTicketPresetCollectionManager.ts @@ -0,0 +1,52 @@ +import { DatabaseSupportTicketPreset } from "@alice-structures/database/aliceDb/DatabaseSupportTicketPreset"; +import { DatabaseCollectionManager } from "../DatabaseCollectionManager"; +import { SupportTicketPreset } from "@alice-database/utils/aliceDb/SupportTicketPreset"; +import { ApplicationCommandOptionChoiceData } from "discord.js"; + +export class SupportTicketPresetCollectionManager extends DatabaseCollectionManager< + DatabaseSupportTicketPreset, + SupportTicketPreset +> { + protected override readonly utilityInstance = SupportTicketPreset; + + override get defaultDocument(): DatabaseSupportTicketPreset { + return { + id: 0, + name: "", + title: "", + description: "", + }; + } + + /** + * Searches presets based on its name for autocomplete response. + * + * @param name The name to search. + * @param amount The maximum amount of names to return. Defaults to 25. + * @returns The name of the presets that match the query. + */ + async searchPresets( + name: string | RegExp, + amount: number = 25, + ): Promise[]> { + let regExp: RegExp; + + try { + regExp = new RegExp(name, "i"); + } catch { + return []; + } + + const result = await this.collection + .find({ name: regExp }, { projection: { _id: 0, name: 1 } }) + .limit(amount) + .toArray(); + + return result.map((v) => { + return { + name: v.name, + value: v.name, + }; + }); + } +} diff --git a/src/database/utils/aliceDb/SupportTicketPreset.ts b/src/database/utils/aliceDb/SupportTicketPreset.ts new file mode 100644 index 000000000..e4d6d740e --- /dev/null +++ b/src/database/utils/aliceDb/SupportTicketPreset.ts @@ -0,0 +1,24 @@ +import { DatabaseSupportTicketPreset } from "@alice-structures/database/aliceDb/DatabaseSupportTicketPreset"; +import { Manager } from "@alice-utils/base/Manager"; +import { ObjectId } from "mongodb"; + +export class SupportTicketPreset + extends Manager + implements DatabaseSupportTicketPreset +{ + readonly id: number; + readonly name: string; + readonly title: string; + readonly description: string; + readonly _id?: ObjectId; + + constructor(data: DatabaseSupportTicketPreset) { + super(); + + this._id = data._id; + this.id = data.id; + this.name = data.name; + this.title = data.title; + this.description = data.description; + } +} diff --git a/src/events/interactionCreate/utils/runCommand.ts b/src/events/interactionCreate/utils/runCommand.ts index 174e97691..3994c563b 100644 --- a/src/events/interactionCreate/utils/runCommand.ts +++ b/src/events/interactionCreate/utils/runCommand.ts @@ -250,6 +250,8 @@ export const run: EventUtil["run"] = async ( ), }); + consola.error(e); + client.emit("error", e); }); }; diff --git a/src/interactions/autocomplete/General/ticket/ticket.ts b/src/interactions/autocomplete/General/ticket/ticket.ts new file mode 100644 index 000000000..8983094c4 --- /dev/null +++ b/src/interactions/autocomplete/General/ticket/ticket.ts @@ -0,0 +1,14 @@ +import { DatabaseManager } from "@alice-database/DatabaseManager"; +import { AutocompleteHandler } from "@alice-structures/core/AutocompleteHandler"; + +export const run: AutocompleteHandler["run"] = async (_, interaction) => { + interaction.respond( + await DatabaseManager.aliceDb.collections.supportTicketPreset.searchPresets( + interaction.options.getFocused(), + ), + ); +}; + +export const config: AutocompleteHandler["config"] = { + name: "ticket", +}; diff --git a/src/interactions/buttons/Support Ticket/createSupportTicket.ts b/src/interactions/buttons/Support Ticket/createSupportTicket.ts index 282437824..7f08e6551 100644 --- a/src/interactions/buttons/Support Ticket/createSupportTicket.ts +++ b/src/interactions/buttons/Support Ticket/createSupportTicket.ts @@ -36,4 +36,5 @@ export const run: ButtonCommand["run"] = async (_, interaction) => { export const config: ButtonCommand["config"] = { cooldown: 300, + instantDeferInDebug: false, }; diff --git a/src/interactions/buttons/Support Ticket/createSupportTicketWithPreset.ts b/src/interactions/buttons/Support Ticket/createSupportTicketWithPreset.ts new file mode 100644 index 000000000..6d9e2d8a1 --- /dev/null +++ b/src/interactions/buttons/Support Ticket/createSupportTicketWithPreset.ts @@ -0,0 +1,103 @@ +import { DatabaseManager } from "@alice-database/DatabaseManager"; +import { CreateSupportTicketWithPresetLocalization } from "@alice-localization/interactions/buttons/Support Ticket/createSupportTicketWithPreset/CreateSupportTicketWithPresetLocalization"; +import { ButtonCommand } from "@alice-structures/core/ButtonCommand"; +import { MessageCreator } from "@alice-utils/creators/MessageCreator"; +import { ModalCreator } from "@alice-utils/creators/ModalCreator"; +import { SelectMenuCreator } from "@alice-utils/creators/SelectMenuCreator"; +import { CommandHelper } from "@alice-utils/helpers/CommandHelper"; +import { InteractionHelper } from "@alice-utils/helpers/InteractionHelper"; +import { TextInputBuilder, TextInputStyle } from "discord.js"; + +export const run: ButtonCommand["run"] = async (_, interaction) => { + const dbManager = DatabaseManager.aliceDb.collections.supportTicketPreset; + const language = await CommandHelper.getLocale(interaction); + const localization = new CreateSupportTicketWithPresetLocalization( + language, + ); + + await InteractionHelper.deferReply(interaction); + + const ticketPresetsSearch = await dbManager.get( + "id", + {}, + { projection: { _id: 0, id: 1, name: 1 } }, + ); + + if (ticketPresetsSearch.size === 0) { + return InteractionHelper.reply(interaction, { + content: MessageCreator.createReject( + localization.getTranslation("noTicketPresetsExist"), + ), + }); + } + + const selectMenuInteraction = + await SelectMenuCreator.createStringSelectMenu( + interaction, + { + content: MessageCreator.createWarn( + localization.getTranslation("selectPresetPrompt"), + ), + }, + ticketPresetsSearch.map((v) => { + return { + value: v.id.toString(), + label: v.name, + }; + }), + [interaction.user.id], + 60, + ); + + if (!selectMenuInteraction) { + return; + } + + const preset = await dbManager.getOne( + { + id: parseInt(selectMenuInteraction.values[0]), + }, + { projection: { _id: 0, title: 1, description: 1 } }, + ); + if (!preset) { + return InteractionHelper.reply(interaction, { + content: MessageCreator.createReject( + localization.getTranslation("presetNotFound"), + ), + }); + } + + await ModalCreator.createModal( + selectMenuInteraction, + "ticket-create", + localization.getTranslation("modalTitle"), + new TextInputBuilder() + .setCustomId("title") + .setRequired(true) + .setStyle(TextInputStyle.Short) + .setMaxLength(100) + .setPlaceholder( + localization.getTranslation("modalTitlePlaceholder"), + ) + .setLabel(localization.getTranslation("modalTitleLabel")) + .setValue(preset.title), + new TextInputBuilder() + .setCustomId("description") + .setRequired(true) + .setStyle(TextInputStyle.Paragraph) + .setMaxLength(1500) + .setPlaceholder( + localization.getTranslation("modalDescriptionPlaceholder"), + ) + .setLabel(localization.getTranslation("modalDescriptionLabel")) + .setValue(preset.description), + ); + + // Delete the left-over select menu, which is a reply to the original interaction. + await interaction.deleteReply(); +}; + +export const config: ButtonCommand["config"] = { + cooldown: 300, + replyEphemeral: true, +}; diff --git a/src/interactions/buttons/Support Ticket/editSupportTicket.ts b/src/interactions/buttons/Support Ticket/editSupportTicket.ts index 4bade0a15..bdc29352a 100644 --- a/src/interactions/buttons/Support Ticket/editSupportTicket.ts +++ b/src/interactions/buttons/Support Ticket/editSupportTicket.ts @@ -83,4 +83,5 @@ export const run: ButtonCommand["run"] = async (_, interaction) => { export const config: ButtonCommand["config"] = { cooldown: 5, replyEphemeral: true, + instantDeferInDebug: false, }; diff --git a/src/interactions/commands/General/ticket/subcommands/ticket-create.ts b/src/interactions/commands/General/ticket/subcommands/ticket-create.ts index 874e4472d..09d381d30 100644 --- a/src/interactions/commands/General/ticket/subcommands/ticket-create.ts +++ b/src/interactions/commands/General/ticket/subcommands/ticket-create.ts @@ -1,7 +1,10 @@ +import { DatabaseManager } from "@alice-database/DatabaseManager"; import { TicketLocalization } from "@alice-localization/interactions/commands/General/ticket/TicketLocalization"; import { SlashSubcommand } from "@alice-structures/core/SlashSubcommand"; +import { MessageCreator } from "@alice-utils/creators/MessageCreator"; import { ModalCreator } from "@alice-utils/creators/ModalCreator"; import { CommandHelper } from "@alice-utils/helpers/CommandHelper"; +import { InteractionHelper } from "@alice-utils/helpers/InteractionHelper"; import { TextInputBuilder, TextInputStyle } from "discord.js"; export const run: SlashSubcommand["run"] = async (_, interaction) => { @@ -9,7 +12,29 @@ export const run: SlashSubcommand["run"] = async (_, interaction) => { await CommandHelper.getLocale(interaction), ); - // TODO: ticket presets + const presetName = interaction.options.getString("preset"); + + let prefilledTitle = ""; + let prefilledDescription = ""; + + if (presetName) { + const preset = + await DatabaseManager.aliceDb.collections.supportTicketPreset.getOne( + { name: presetName }, + { projection: { _id: 0, title: 1, description: 1 } }, + ); + + if (!preset) { + return InteractionHelper.reply(interaction, { + content: MessageCreator.createReject( + localization.getTranslation("presetNotFound"), + ), + }); + } + + prefilledTitle = preset.title; + prefilledDescription = preset.description; + } ModalCreator.createModal( interaction, @@ -20,19 +45,30 @@ export const run: SlashSubcommand["run"] = async (_, interaction) => { .setRequired(true) .setStyle(TextInputStyle.Short) .setMaxLength(100) - .setLabel(localization.getTranslation("ticketModalTitleLabel")), + .setPlaceholder( + localization.getTranslation("ticketModalTitlePlaceholder"), + ) + .setLabel(localization.getTranslation("ticketModalTitleLabel")) + .setValue(prefilledTitle), new TextInputBuilder() .setCustomId("description") .setRequired(true) .setStyle(TextInputStyle.Paragraph) .setMaxLength(1500) + .setPlaceholder( + localization.getTranslation( + "ticketModalDescriptionPlaceholder", + ), + ) .setLabel( localization.getTranslation("ticketModalDescriptionLabel"), - ), + ) + .setValue(prefilledDescription), ); }; export const config: SlashSubcommand["config"] = { permissions: [], cooldown: 30, + instantDeferInDebug: false, }; diff --git a/src/interactions/commands/General/ticket/subcommands/ticket-edit.ts b/src/interactions/commands/General/ticket/subcommands/ticket-edit.ts index 377ce5b05..1853c791b 100644 --- a/src/interactions/commands/General/ticket/subcommands/ticket-edit.ts +++ b/src/interactions/commands/General/ticket/subcommands/ticket-edit.ts @@ -93,4 +93,6 @@ export const run: SlashSubcommand["run"] = async (_, interaction) => { export const config: SlashSubcommand["config"] = { permissions: [], + cooldown: 5, + instantDeferInDebug: false, }; diff --git a/src/interactions/commands/General/ticket/ticket.ts b/src/interactions/commands/General/ticket/ticket.ts index 48ad14c73..2e0608ab3 100644 --- a/src/interactions/commands/General/ticket/ticket.ts +++ b/src/interactions/commands/General/ticket/ticket.ts @@ -41,6 +41,14 @@ export const config: SlashCommand["config"] = { name: "create", type: ApplicationCommandOptionType.Subcommand, description: "Creates a new ticket.", + options: [ + { + name: "preset", + type: ApplicationCommandOptionType.String, + description: "The preset to use.", + autocomplete: true, + }, + ], }, { name: "edit", @@ -81,7 +89,7 @@ export const config: SlashCommand["config"] = { name: "author", type: ApplicationCommandOptionType.User, description: - "The user who originally opened the ticket. If unspecified, will default to the ticket in the channel.", + "The user who opened the ticket. If unspecified, will default to the ticket in the channel.", }, { name: "id", @@ -130,7 +138,7 @@ export const config: SlashCommand["config"] = { name: "author", type: ApplicationCommandOptionType.User, description: - "The user who originally opened the ticket. If unspecified, will default to the ticket in the channel.", + "The user who opened the ticket. If unspecified, will default to the ticket in the channel.", }, { name: "id", diff --git a/src/localization/interactions/buttons/Support Ticket/createSupportTicketWithPreset/CreateSupportTicketWithPresetLocalization.ts b/src/localization/interactions/buttons/Support Ticket/createSupportTicketWithPreset/CreateSupportTicketWithPresetLocalization.ts new file mode 100644 index 000000000..4a31094aa --- /dev/null +++ b/src/localization/interactions/buttons/Support Ticket/createSupportTicketWithPreset/CreateSupportTicketWithPresetLocalization.ts @@ -0,0 +1,25 @@ +import { Localization } from "@alice-localization/base/Localization"; +import { Translations } from "@alice-localization/base/Translations"; +import { CreateSupportTicketWithPresetENTranslation } from "./translations/CreateSupportTicketWithPresetENTranslation"; + +export interface CreateSupportTicketWithPresetStrings { + readonly noTicketPresetsExist: string; + readonly selectPresetPrompt: string; + readonly presetNotFound: string; + readonly modalTitle: string; + readonly modalTitleLabel: string; + readonly modalTitlePlaceholder: string; + readonly modalDescriptionLabel: string; + readonly modalDescriptionPlaceholder: string; +} + +/** + * Localizations for the `createSupportTicketWithPreset` button command. + */ +export class CreateSupportTicketWithPresetLocalization extends Localization { + protected override readonly localizations: Readonly< + Translations + > = { + en: new CreateSupportTicketWithPresetENTranslation(), + }; +} diff --git a/src/localization/interactions/buttons/Support Ticket/createSupportTicketWithPreset/translations/CreateSupportTicketWithPresetENTranslation.ts b/src/localization/interactions/buttons/Support Ticket/createSupportTicketWithPreset/translations/CreateSupportTicketWithPresetENTranslation.ts new file mode 100644 index 000000000..991f84dbe --- /dev/null +++ b/src/localization/interactions/buttons/Support Ticket/createSupportTicketWithPreset/translations/CreateSupportTicketWithPresetENTranslation.ts @@ -0,0 +1,19 @@ +import { Translation } from "@alice-localization/base/Translation"; +import { CreateSupportTicketWithPresetStrings } from "../CreateSupportTicketWithPresetLocalization"; + +/** + * The English translation for the `createSupportTicketWithPreset` button command. + */ +export class CreateSupportTicketWithPresetENTranslation extends Translation { + override readonly translations: CreateSupportTicketWithPresetStrings = { + noTicketPresetsExist: + "I'm sorry, there are no ticket presets as of now!", + selectPresetPrompt: "Please select a preset.", + presetNotFound: "I'm sorry, I could not find the preset!", + modalTitle: "Create Ticket", + modalTitleLabel: "Title", + modalTitlePlaceholder: "Enter the title of the ticket.", + modalDescriptionLabel: "Description", + modalDescriptionPlaceholder: "Enter the description of the ticket.", + }; +} diff --git a/src/localization/interactions/commands/General/ticket/TicketLocalization.ts b/src/localization/interactions/commands/General/ticket/TicketLocalization.ts index 0e172ecc0..d6d20c2bf 100644 --- a/src/localization/interactions/commands/General/ticket/TicketLocalization.ts +++ b/src/localization/interactions/commands/General/ticket/TicketLocalization.ts @@ -4,6 +4,7 @@ import { TicketENTranslation } from "./translations/TicketENTranslation"; export interface TicketStrings { readonly ticketNotFound: string; + readonly presetNotFound: string; readonly noTicketsFound: string; readonly ticketEditModalTitle: string; readonly ticketCreateModalTitle: string; diff --git a/src/localization/interactions/commands/General/ticket/translations/TicketENTranslation.ts b/src/localization/interactions/commands/General/ticket/translations/TicketENTranslation.ts index f391b57fb..5039276e8 100644 --- a/src/localization/interactions/commands/General/ticket/translations/TicketENTranslation.ts +++ b/src/localization/interactions/commands/General/ticket/translations/TicketENTranslation.ts @@ -7,6 +7,7 @@ import { TicketStrings } from "../TicketLocalization"; export class TicketENTranslation extends Translation { override readonly translations: TicketStrings = { ticketNotFound: "I'm sorry, I could not find the ticket!", + presetNotFound: "I'm sorry, I could not find the preset!", noTicketsFound: "I'm sorry, I could not find any tickets!", ticketEditModalTitle: "Edit Ticket", ticketCreateModalTitle: "Create Ticket", diff --git a/src/structures/database/aliceDb/DatabaseSupportTicketPreset.ts b/src/structures/database/aliceDb/DatabaseSupportTicketPreset.ts new file mode 100644 index 000000000..1e7c26795 --- /dev/null +++ b/src/structures/database/aliceDb/DatabaseSupportTicketPreset.ts @@ -0,0 +1,26 @@ +import { BaseDocument } from "../BaseDocument"; + +/** + * Represents a preset of a support ticket. + */ +export interface DatabaseSupportTicketPreset extends BaseDocument { + /** + * The ID of the preset. + */ + readonly id: number; + + /** + * The name of the preset. + */ + readonly name: string; + + /** + * The title of the preset. + */ + readonly title: string; + + /** + * The description of the preset. + */ + readonly description: string; +}