diff --git a/src/modules/core/set.command.ts b/src/modules/core/set.command.ts index d01fe4b6..4a5c6967 100644 --- a/src/modules/core/set.command.ts +++ b/src/modules/core/set.command.ts @@ -8,10 +8,12 @@ import { Colors, type ComponentType, GuildMember, + MessageFlags, } from "discord.js"; import type { Command } from "djs-slash-helper"; import { type DDUser, getOrCreateUserById } from "../../store/models/DDUser.js"; import { createStandardEmbed } from "../../util/embeds.js"; + import { mentionIfPingable } from "../../util/users.js"; import { levelForXp } from "../xp/xpRoles.util.js"; @@ -120,12 +122,12 @@ export const SetCommand: Command = { const event = await collector.next; if (event.customId === "cancel") { await event.reply({ - flags: ["Ephemeral"], + flags: MessageFlags.Ephemeral, content: "**Cancelled**", }); } else if (event.customId === "confirm") { await event.deferReply({ - flags: "Ephemeral", + flags: MessageFlags.Ephemeral, }); setter(user, value); await user.save(); @@ -141,7 +143,7 @@ export const SetCommand: Command = { ], }); await event.followUp({ - flags: "Ephemeral", + flags: MessageFlags.Ephemeral, content: "Success!", }); } diff --git a/src/modules/faq/faq.command.ts b/src/modules/faq/faq.command.ts index 3b395c5d..84d6ffe6 100644 --- a/src/modules/faq/faq.command.ts +++ b/src/modules/faq/faq.command.ts @@ -2,12 +2,14 @@ import { ApplicationCommandOptionType, ApplicationCommandType, type GuildMember, + MessageFlags, PermissionFlagsBits, } from "discord.js"; import type { Command, ExecutableSubcommand } from "djs-slash-helper"; import { moduleManager } from "../../index.js"; import { logger } from "../../logging.js"; import { FAQ } from "../../store/models/FAQ.js"; + import createFaqModal from "./faq.modal.js"; import { createFaqEmbed } from "./faq.util.js"; @@ -45,7 +47,7 @@ const GetSubcommand: ExecutableSubcommand = { }); if (faq == null) { return await interaction.reply({ - flags: ["Ephemeral"], + flags: MessageFlags.Ephemeral, content: "No FAQ found with this name", }); } @@ -69,14 +71,14 @@ const EditSubcommand: ExecutableSubcommand = { const member = interaction.member as GuildMember; if (!member.permissions.has(PermissionFlagsBits.ManageMessages)) { return await interaction.reply({ - flags: ["Ephemeral"], + flags: MessageFlags.Ephemeral, content: "No permission", }); } const name = interaction.options.get("name")?.value as string | null; if (name == null) { return await interaction.reply({ - flags: ["Ephemeral"], + flags: MessageFlags.Ephemeral, content: "No FAQ name provided", }); } @@ -97,7 +99,7 @@ const EditSubcommand: ExecutableSubcommand = { author: BigInt(interaction.user.id), }); await response.reply({ - flags: ["Ephemeral"], + flags: MessageFlags.Ephemeral, content: `FAQ named ${name} created`, }); @@ -123,14 +125,14 @@ const DeleteSubcommand: ExecutableSubcommand = { const member = interaction.member as GuildMember; if (!member.permissions.has(PermissionFlagsBits.ManageMessages)) { return await interaction.reply({ - flags: ["Ephemeral"], + flags: MessageFlags.Ephemeral, content: "No permission", }); } const name = interaction.options.get("name")?.value as string | null; if (name == null) { return await interaction.reply({ - flags: ["Ephemeral"], + flags: MessageFlags.Ephemeral, content: "No FAQ name provided", }); } @@ -138,7 +140,7 @@ const DeleteSubcommand: ExecutableSubcommand = { const faq = await FAQ.findOne({ where: { name } }); if (faq == null) { return await interaction.reply({ - flags: ["Ephemeral"], + flags: MessageFlags.Ephemeral, content: "No FAQ found with this name", }); } @@ -146,7 +148,7 @@ const DeleteSubcommand: ExecutableSubcommand = { await updateChoices(); await moduleManager.refreshCommands(); return await interaction.reply({ - flags: ["Ephemeral"], + flags: MessageFlags.Ephemeral, content: `FAQ named ${name} deleted`, }); }, diff --git a/src/modules/information/informationMessage.command.ts b/src/modules/information/informationMessage.command.ts index 9c542e4a..1ffa18d7 100644 --- a/src/modules/information/informationMessage.command.ts +++ b/src/modules/information/informationMessage.command.ts @@ -3,6 +3,7 @@ import { type APIEmbed, ApplicationCommandType, ButtonBuilder, + MessageFlags, } from "discord.js"; import type { Command } from "djs-slash-helper"; import { config } from "../../Config.js"; @@ -30,7 +31,7 @@ export const InformationMessageCommand: Command async handle(interaction) { if (!interaction.targetMessage.editable) { await interaction.reply({ - flags: ["Ephemeral"], + flags: MessageFlags.Ephemeral, content: "I can't edit that message.", }); return; @@ -38,7 +39,7 @@ export const InformationMessageCommand: Command if (config.informationMessage == null) { await interaction.reply({ - flags: ["Ephemeral"], + flags: MessageFlags.Ephemeral, content: "There is no information message configured.", }); return; @@ -67,7 +68,7 @@ export const InformationMessageCommand: Command )}`, ); await interaction.reply({ - flags: ["Ephemeral"], + flags: MessageFlags.Ephemeral, content: "Information message set.", }); }, diff --git a/src/modules/information/informationMessage.listener.ts b/src/modules/information/informationMessage.listener.ts index f03ead76..fa88a3be 100644 --- a/src/modules/information/informationMessage.listener.ts +++ b/src/modules/information/informationMessage.listener.ts @@ -3,11 +3,13 @@ import { type ButtonInteraction, type GuildMember, type Interaction, + MessageFlags, StringSelectMenuBuilder, StringSelectMenuOptionBuilder, } from "discord.js"; import { FAQ } from "../../store/models/FAQ.js"; import { getEmoji, toAPIMessageComponentEmoji } from "../../util/emojis.js"; + import { truncateTo } from "../../util/strings.js"; import { createFaqEmbed } from "../faq/faq.util.js"; import { getResourceEmbed } from "../learning/learning.command.js"; @@ -24,7 +26,7 @@ export const InformationButtonListener: EventListener = { interaction.customId === "learningResourcePicker" ) { const resourceName = interaction.values[0]; - await interaction.deferReply({ flags: "Ephemeral" }); + await interaction.deferReply({ flags: MessageFlags.Ephemeral }); const resource = await getResource(resourceName); if (resource == null) { return; // shouldn't ever happen @@ -38,7 +40,7 @@ export const InformationButtonListener: EventListener = { await interaction.followUp({ embeds: [embed], - flags: ["Ephemeral"], + flags: MessageFlags.Ephemeral, }); return; } @@ -71,7 +73,7 @@ export const InformationButtonListener: EventListener = { (interaction.member as GuildMember) ?? undefined, ); await interaction.followUp({ - flags: ["Ephemeral"], + flags: MessageFlags.Ephemeral, embeds: [embed], }); }, @@ -101,7 +103,7 @@ async function sendLearningResourcesPicker(interaction: ButtonInteraction) { components: [ new ActionRowBuilder().addComponents(selectMenu), ], - flags: ["Ephemeral"], + flags: MessageFlags.Ephemeral, withResponse: false, }); } diff --git a/src/modules/learning/learning.command.ts b/src/modules/learning/learning.command.ts index 9b02c098..2be5938b 100644 --- a/src/modules/learning/learning.command.ts +++ b/src/modules/learning/learning.command.ts @@ -3,6 +3,7 @@ import { ApplicationCommandType, type Client, type GuildMember, + MessageFlags, PermissionFlagsBits, type User, } from "discord.js"; @@ -11,6 +12,7 @@ import { moduleManager } from "../../index.js"; import { logger } from "../../logging.js"; import { createStandardEmbed, standardFooter } from "../../util/embeds.js"; import { getEmoji, stringifyEmoji } from "../../util/emojis.js"; + import { fakeMention } from "../../util/users.js"; import type { LearningResource } from "./learningResource.model.js"; import { @@ -132,12 +134,14 @@ const LearningUpdateSubcommand: ExecutableSubcommand = { const member = interaction.member as GuildMember; if (!member.permissions.has(PermissionFlagsBits.ManageMessages)) { return await interaction.reply({ - flags: ["Ephemeral"], + flags: MessageFlags.Ephemeral, content: "No permission", }); } - await interaction.deferReply({ flags: "Ephemeral" }); + await interaction.deferReply({ + flags: MessageFlags.Ephemeral, + }); await updateResourcesForCommands(); await moduleManager.refreshCommands(); await interaction.followUp("Updated learning resources cache"); @@ -167,7 +171,7 @@ const LearningListSubcommand: ExecutableSubcommand = { }); await interaction.reply({ - flags: ["Ephemeral"], + flags: MessageFlags.Ephemeral, embeds: [embed], }); }, diff --git a/src/modules/moderation/kick.command.ts b/src/modules/moderation/kick.command.ts index 3fcd24e6..973ca7db 100644 --- a/src/modules/moderation/kick.command.ts +++ b/src/modules/moderation/kick.command.ts @@ -1,6 +1,7 @@ import { ApplicationCommandOptionType, ApplicationCommandType, + MessageFlags, } from "discord.js"; import type { Command } from "djs-slash-helper"; import { logger } from "../../logging.js"; @@ -68,7 +69,7 @@ export const KickCommand: Command = { await interaction.editReply("Something went wrong!"); } else { await interaction.followUp({ - flags: "Ephemeral", + flags: MessageFlags.Ephemeral, content: "Failed to kick member!", }); } diff --git a/src/modules/modmail/modmail.command.ts b/src/modules/modmail/modmail.command.ts index 3896ad45..48bca98b 100644 --- a/src/modules/modmail/modmail.command.ts +++ b/src/modules/modmail/modmail.command.ts @@ -6,6 +6,7 @@ import { ButtonBuilder, ButtonStyle, type EmbedBuilder, + MessageFlags, PermissionFlagsBits, } from "discord.js"; import type { Command, ExecutableSubcommand } from "djs-slash-helper"; @@ -13,6 +14,7 @@ import { config } from "../../Config.js"; import { logger } from "../../logging.js"; import { ModMailNote } from "../../store/models/ModMailNote.js"; import { getMemberFromInteraction } from "../../util/member.js"; + import { safelyFetchUser } from "../../util/users.js"; import { archiveModmailTicket, @@ -32,7 +34,7 @@ const ArchiveSubCommand: ExecutableSubcommand = { name: "archive", description: "Archive and close the current modmail thread.", async handle(interaction) { - await interaction.deferReply({ flags: ["Ephemeral"] }); + await interaction.deferReply({ flags: MessageFlags.Ephemeral }); try { // Validate permissions @@ -50,7 +52,7 @@ const ArchiveSubCommand: ExecutableSubcommand = { logger.error(`Error creating modmail archive:`, error); await interaction.followUp({ content: "An error occurred while creating the archive.", - flags: ["Ephemeral"], + flags: MessageFlags.Ephemeral, }); } }, @@ -70,13 +72,13 @@ const NoteSubCommand: ExecutableSubcommand = { ], async handle(interaction) { await interaction.deferReply({ - flags: ["Ephemeral"], + flags: MessageFlags.Ephemeral, }); if (!interaction.inGuild()) { await interaction.followUp({ content: "This command can only be used in a guild", - flags: ["Ephemeral"], + flags: MessageFlags.Ephemeral, }); return; } @@ -86,7 +88,7 @@ const NoteSubCommand: ExecutableSubcommand = { if (!member?.permissions.has(PermissionFlagsBits.ManageMessages)) { await interaction.followUp({ content: "You don't have permission to add notes", - flags: ["Ephemeral"], + flags: MessageFlags.Ephemeral, }); return; } @@ -94,7 +96,7 @@ const NoteSubCommand: ExecutableSubcommand = { if (!interaction.channel?.isThread()) { await interaction.followUp({ content: "This command can only be used in a modmail thread", - flags: ["Ephemeral"], + flags: MessageFlags.Ephemeral, }); return; } @@ -106,7 +108,7 @@ const NoteSubCommand: ExecutableSubcommand = { if (!modMail) { await interaction.followUp({ content: "This command can only be used in a modmail thread", - flags: ["Ephemeral"], + flags: MessageFlags.Ephemeral, }); return; } @@ -148,7 +150,7 @@ const NoteSubCommand: ExecutableSubcommand = { await interaction.followUp({ content: "✅ Note added successfully", - flags: ["Ephemeral"], + flags: MessageFlags.Ephemeral, }); }, }; @@ -159,7 +161,7 @@ const ListNotesSubCommand: ExecutableSubcommand = { description: "List all notes for this ticket.", async handle(interaction) { await interaction.deferReply({ - flags: ["Ephemeral"], + flags: MessageFlags.Ephemeral, }); try { @@ -168,7 +170,7 @@ const ListNotesSubCommand: ExecutableSubcommand = { logger.error("Failed to show notes:", error); await interaction.followUp({ content: "An error occurred while showing notes.", - flags: ["Ephemeral"], + flags: MessageFlags.Ephemeral, }); } }, @@ -180,7 +182,7 @@ const DetailsSubCommand: ExecutableSubcommand = { description: "Get details about the current thread.", async handle(interaction) { await interaction.deferReply({ - flags: ["Ephemeral"], + flags: MessageFlags.Ephemeral, }); // Handle DM usage @@ -189,7 +191,7 @@ const DetailsSubCommand: ExecutableSubcommand = { if (!modMail) { await interaction.followUp({ content: "You don't have an active ticket.", - flags: ["Ephemeral"], + flags: MessageFlags.Ephemeral, }); return; } @@ -207,7 +209,7 @@ const DetailsSubCommand: ExecutableSubcommand = { await interaction.followUp({ embeds: [ticketDetails.embed], components: [ticketDetails.row], - flags: ["Ephemeral"], + flags: MessageFlags.Ephemeral, }); return; } @@ -216,7 +218,7 @@ const DetailsSubCommand: ExecutableSubcommand = { if (!interaction.channel?.isThread()) { await interaction.followUp({ content: "This command can only be used in a modmail thread", - flags: ["Ephemeral"], + flags: MessageFlags.Ephemeral, }); return; } @@ -228,7 +230,7 @@ const DetailsSubCommand: ExecutableSubcommand = { if (!modMail) { await interaction.followUp({ content: "This command can only be used in a modmail thread", - flags: ["Ephemeral"], + flags: MessageFlags.Ephemeral, }); return; } @@ -258,7 +260,7 @@ const CloseSubCommand: ExecutableSubcommand = { description: "Close your ticket or the current modmail thread.", async handle(interaction) { await interaction.deferReply({ - flags: ["Ephemeral"], + flags: MessageFlags.Ephemeral, }); // Handle DM usage - user closing their own ticket @@ -267,7 +269,7 @@ const CloseSubCommand: ExecutableSubcommand = { if (!modMail) { await interaction.followUp({ content: "You don't have an active ticket.", - flags: ["Ephemeral"], + flags: MessageFlags.Ephemeral, }); return; } @@ -298,7 +300,7 @@ const CloseSubCommand: ExecutableSubcommand = { await interaction.followUp({ content: "✅ Your ticket has been closed. Thank you for contacting us!", - flags: ["Ephemeral"], + flags: MessageFlags.Ephemeral, }); return; } @@ -308,7 +310,7 @@ const CloseSubCommand: ExecutableSubcommand = { if (!member?.permissions.has(PermissionFlagsBits.ManageMessages)) { await interaction.followUp({ content: "You don't have permission to close tickets", - flags: ["Ephemeral"], + flags: MessageFlags.Ephemeral, }); return; } @@ -316,7 +318,7 @@ const CloseSubCommand: ExecutableSubcommand = { if (!interaction.channel?.isThread()) { await interaction.followUp({ content: "This command can only be used in a modmail thread", - flags: ["Ephemeral"], + flags: MessageFlags.Ephemeral, }); return; } @@ -328,7 +330,7 @@ const CloseSubCommand: ExecutableSubcommand = { if (!modMail) { await interaction.followUp({ content: "This command can only be used in a modmail thread", - flags: ["Ephemeral"], + flags: MessageFlags.Ephemeral, }); return; } @@ -361,7 +363,7 @@ const CloseSubCommand: ExecutableSubcommand = { await interaction.followUp({ content: `✅ Successfully closed ticket`, - flags: ["Ephemeral"], + flags: MessageFlags.Ephemeral, }); }, }; @@ -380,13 +382,13 @@ const AssignSubCommand: ExecutableSubcommand = { ], async handle(interaction) { await interaction.deferReply({ - flags: ["Ephemeral"], + flags: MessageFlags.Ephemeral, }); if (!interaction.inGuild()) { await interaction.followUp({ content: "This command can only be used in a guild", - flags: ["Ephemeral"], + flags: MessageFlags.Ephemeral, }); return; } @@ -396,7 +398,7 @@ const AssignSubCommand: ExecutableSubcommand = { if (!member?.permissions.has(PermissionFlagsBits.ManageMessages)) { await interaction.followUp({ content: "You don't have permission to assign tickets", - flags: ["Ephemeral"], + flags: MessageFlags.Ephemeral, }); return; } @@ -404,7 +406,7 @@ const AssignSubCommand: ExecutableSubcommand = { if (!interaction.channel?.isThread()) { await interaction.followUp({ content: "This command can only be used in a modmail thread", - flags: ["Ephemeral"], + flags: MessageFlags.Ephemeral, }); return; } @@ -416,7 +418,7 @@ const AssignSubCommand: ExecutableSubcommand = { if (!modMail) { await interaction.followUp({ content: "This command can only be used in a modmail thread", - flags: ["Ephemeral"], + flags: MessageFlags.Ephemeral, }); return; } @@ -428,7 +430,7 @@ const AssignSubCommand: ExecutableSubcommand = { if (!targetMember?.permissions.has(PermissionFlagsBits.ManageMessages)) { await interaction.followUp({ content: "The selected user doesn't have moderator permissions", - flags: ["Ephemeral"], + flags: MessageFlags.Ephemeral, }); return; } @@ -463,7 +465,7 @@ const AssignSubCommand: ExecutableSubcommand = { await interaction.followUp({ content: `✅ Successfully assigned ticket to ${targetUser.displayName}`, - flags: ["Ephemeral"], + flags: MessageFlags.Ephemeral, }); }, }; diff --git a/src/modules/modmail/modmail.listener.ts b/src/modules/modmail/modmail.listener.ts index ea5b016e..1371a408 100644 --- a/src/modules/modmail/modmail.listener.ts +++ b/src/modules/modmail/modmail.listener.ts @@ -9,6 +9,7 @@ import { type EmbedBuilder, type Interaction, type Message, + MessageFlags, ModalBuilder, type ModalSubmitInteraction, type OmitPartialGroupDMChannel, @@ -24,6 +25,7 @@ import { ModMailTicket, ModMailTicketCategory, } from "../../store/models/ModMailTicket.js"; + import { mentionRoleById } from "../../util/role.js"; import { safelyFetchUser } from "../../util/users.js"; import type { EventListener } from "../module.js"; @@ -295,13 +297,13 @@ const handleThreadMessage = async (client: Client, message: Message) => { const handleModmailAssignSelect = async ( interaction: UserSelectMenuInteraction, ) => { - await interaction.deferReply({ flags: ["Ephemeral"] }); + await interaction.deferReply({ flags: MessageFlags.Ephemeral }); try { if (!interaction.inGuild()) { await interaction.followUp({ content: "This command can only be used in a guild.", - flags: ["Ephemeral"], + flags: MessageFlags.Ephemeral, }); return; } @@ -312,7 +314,7 @@ const handleModmailAssignSelect = async ( await interaction.followUp({ content: "This thread has been archived and the ticket is no longer active.", - flags: ["Ephemeral"], + flags: MessageFlags.Ephemeral, }); return; } @@ -323,7 +325,7 @@ const handleModmailAssignSelect = async ( if (!ticketId) { await interaction.followUp({ content: "Invalid ticket ID.", - flags: ["Ephemeral"], + flags: MessageFlags.Ephemeral, }); return; } @@ -336,7 +338,7 @@ const handleModmailAssignSelect = async ( if (!modMail) { await interaction.followUp({ content: "Ticket not found.", - flags: ["Ephemeral"], + flags: MessageFlags.Ephemeral, }); return; } @@ -348,7 +350,7 @@ const handleModmailAssignSelect = async ( if (!targetMember?.permissions.has(PermissionFlagsBits.ManageMessages)) { await interaction.followUp({ content: "The selected user doesn't have moderator permissions.", - flags: ["Ephemeral"], + flags: MessageFlags.Ephemeral, }); return; } @@ -390,13 +392,13 @@ const handleModmailAssignSelect = async ( await interaction.followUp({ content: `✅ Successfully assigned ticket to ${targetMember.displayName}`, - flags: ["Ephemeral"], + flags: MessageFlags.Ephemeral, }); } catch (error) { logger.error("Failed to assign ticket:", error); await interaction.followUp({ content: "An error occurred while assigning the ticket.", - flags: ["Ephemeral"], + flags: MessageFlags.Ephemeral, }); } }; @@ -413,7 +415,7 @@ const handleModmailSubmit = async ( await interaction.message.delete().catch(() => {}); await interaction.followUp({ content: "You already have an open ticket", - flags: ["Ephemeral"], + flags: MessageFlags.Ephemeral, }); return; } @@ -431,7 +433,7 @@ const handleModmailSubmit = async ( if (!channel) { await interaction.followUp({ content: "Modmail channel not found. Please contact an administrator.", - flags: ["Ephemeral"], + flags: MessageFlags.Ephemeral, }); logger.error("Modmail channel not found"); return; @@ -441,7 +443,7 @@ const handleModmailSubmit = async ( await interaction.followUp({ content: "Modmail channel configuration error. Please contact an administrator.", - flags: ["Ephemeral"], + flags: MessageFlags.Ephemeral, }); logger.error("Modmail channel is not a text channel"); return; @@ -507,7 +509,7 @@ const handleModmailSubmit = async ( await interaction.followUp({ content: "An error occurred while creating your ticket. Please try again or contact an administrator.", - flags: ["Ephemeral"], + flags: MessageFlags.Ephemeral, }); } }; @@ -551,7 +553,7 @@ const handleModmailNoteDelete = async (interaction: ButtonInteraction) => { if (!noteId) { await interaction.followUp({ content: "Invalid Message you are trying to fake as Note.", - flags: ["Ephemeral"], + flags: MessageFlags.Ephemeral, }); return; } else { @@ -572,7 +574,7 @@ const handleModmailNoteDelete = async (interaction: ButtonInteraction) => { message.delete().catch(() => {}); await interaction.followUp({ content: "Note deleted.", - flags: ["Ephemeral"], + flags: MessageFlags.Ephemeral, }); } } @@ -586,7 +588,7 @@ const handleModmailNoteEdit = async (interaction: ButtonInteraction) => { if (!noteId) { await interaction.reply({ content: "Invalid Message you are trying to fake as Note.", - flags: ["Ephemeral"], + flags: MessageFlags.Ephemeral, }); return; } @@ -595,7 +597,7 @@ const handleModmailNoteEdit = async (interaction: ButtonInteraction) => { if (!note) { await interaction.reply({ content: "Note not found.", - flags: ["Ephemeral"], + flags: MessageFlags.Ephemeral, }); return; } @@ -622,7 +624,7 @@ const handleModmailNoteEdit = async (interaction: ButtonInteraction) => { const handleModmailNoteEditModal = async ( interaction: ModalSubmitInteraction, ) => { - await interaction.deferReply({ flags: ["Ephemeral"] }); + await interaction.deferReply({ flags: MessageFlags.Ephemeral }); if (!interaction.inGuild()) return; @@ -631,7 +633,7 @@ const handleModmailNoteEditModal = async ( if (!noteId) { await interaction.followUp({ content: "Invalid note ID.", - flags: ["Ephemeral"], + flags: MessageFlags.Ephemeral, }); return; } @@ -640,7 +642,7 @@ const handleModmailNoteEditModal = async ( if (!note) { await interaction.followUp({ content: "Note not found.", - flags: ["Ephemeral"], + flags: MessageFlags.Ephemeral, }); return; } @@ -682,7 +684,7 @@ const handleModmailNoteEditModal = async ( await interaction.followUp({ content: "Note updated successfully.", - flags: ["Ephemeral"], + flags: MessageFlags.Ephemeral, }); }; @@ -788,14 +790,14 @@ export const ModMailListener: EventListener[] = [ await interaction .followUp({ content: errorMessage, - flags: ["Ephemeral"], + flags: MessageFlags.Ephemeral, }) .catch(() => {}); } else { await interaction .reply({ content: errorMessage, - flags: ["Ephemeral"], + flags: MessageFlags.Ephemeral, }) .catch(() => {}); } diff --git a/src/modules/modmail/modmail.ts b/src/modules/modmail/modmail.ts index 6b4496e4..c87a51a2 100644 --- a/src/modules/modmail/modmail.ts +++ b/src/modules/modmail/modmail.ts @@ -15,6 +15,7 @@ import { type GuildMember, type JSONEncodable, type Message, + MessageFlags, type PartialGuildMember, PermissionFlagsBits, type SelectMenuComponentOptionData, @@ -750,7 +751,7 @@ export async function archiveModmailTicket( if (!channel?.isThread()) { await interaction.followUp({ content: "This command can only be used in a modmail thread.", - flags: ["Ephemeral"], + flags: MessageFlags.Ephemeral, }); return; } @@ -759,7 +760,7 @@ export async function archiveModmailTicket( if (!modMail) { await interaction.followUp({ content: "This is not an active modmail thread.", - flags: ["Ephemeral"], + flags: MessageFlags.Ephemeral, }); return; } @@ -770,7 +771,7 @@ export async function archiveModmailTicket( if (!archiveResult.success || !archiveResult.attachment) { await interaction.followUp({ content: `Failed to create archive: ${archiveResult.error}`, - flags: ["Ephemeral"], + flags: MessageFlags.Ephemeral, }); return; } @@ -805,7 +806,7 @@ export async function archiveModmailTicket( await interaction.followUp({ content: statusMessage, - flags: ["Ephemeral"], + flags: MessageFlags.Ephemeral, }); // Auto-delete thread after successful archiving @@ -849,7 +850,7 @@ export async function validateModmailPermissions( if (!interaction.inGuild()) { await interaction.followUp({ content: "This command can only be used in a guild.", - flags: ["Ephemeral"], + flags: MessageFlags.Ephemeral, }); return false; } @@ -859,7 +860,7 @@ export async function validateModmailPermissions( if (!member?.permissions.has(PermissionFlagsBits.ManageMessages)) { await interaction.followUp({ content: "You don't have permission to archive tickets.", - flags: ["Ephemeral"], + flags: MessageFlags.Ephemeral, }); return false; } @@ -871,7 +872,7 @@ export async function validateModmailPermissions( * Creates an archive, sends it to user and archive channel, then closes the ticket */ export async function handleModmailArchive(interaction: ButtonInteraction) { - await interaction.deferReply({ flags: ["Ephemeral"] }); + await interaction.deferReply({ flags: MessageFlags.Ephemeral }); try { // Validate permissions @@ -889,7 +890,7 @@ export async function handleModmailArchive(interaction: ButtonInteraction) { logger.error(`Error creating modmail archive:`, error); await interaction.followUp({ content: "An error occurred while creating the archive.", - flags: ["Ephemeral"], + flags: MessageFlags.Ephemeral, }); } } @@ -922,7 +923,7 @@ export async function showModmailNotes( if (!modMail) { await interaction.followUp({ content: "Ticket not found.", - flags: ["Ephemeral"], + flags: MessageFlags.Ephemeral, }); return; } @@ -931,7 +932,7 @@ export async function showModmailNotes( if (!interaction.channel?.isThread()) { await interaction.followUp({ content: "This command can only be used in a modmail thread", - flags: ["Ephemeral"], + flags: MessageFlags.Ephemeral, }); return; } @@ -941,7 +942,7 @@ export async function showModmailNotes( if (!modMail) { await interaction.followUp({ content: "This command can only be used in a modmail thread", - flags: ["Ephemeral"], + flags: MessageFlags.Ephemeral, }); return; } @@ -958,7 +959,7 @@ export async function showModmailNotes( if (notes.length === 0) { await interaction.followUp({ content: `No notes found for this ${ticketId ? "archived " : ""}ticket.`, - flags: ["Ephemeral"], + flags: MessageFlags.Ephemeral, }); return; } @@ -973,7 +974,7 @@ export async function showModmailNotes( await interaction.followUp({ embeds: [noteEmbed.embed], - flags: ["Ephemeral"], + flags: MessageFlags.Ephemeral, }); } @@ -982,14 +983,14 @@ export async function showModmailNotes( * Displays all notes associated with the current modmail ticket */ export async function handleModmailShowNotes(interaction: ButtonInteraction) { - await interaction.deferReply({ flags: ["Ephemeral"] }); + await interaction.deferReply({ flags: MessageFlags.Ephemeral }); try { await showModmailNotes(interaction); } catch (error) { logger.error("Failed to show notes:", error); await interaction.followUp({ content: "An error occurred while showing notes.", - flags: ["Ephemeral"], + flags: MessageFlags.Ephemeral, }); } } @@ -1001,7 +1002,7 @@ export async function handleModmailShowNotes(interaction: ButtonInteraction) { export async function handleArchivedModmailShowNotes( interaction: ButtonInteraction, ) { - await interaction.deferReply({ flags: ["Ephemeral"] }); + await interaction.deferReply({ flags: MessageFlags.Ephemeral }); try { // Extract ticket ID from custom ID (format: "modmail-list-notes-archived-{ticketId}") @@ -1009,7 +1010,7 @@ export async function handleArchivedModmailShowNotes( if (!ticketId) { await interaction.followUp({ content: "Invalid ticket ID.", - flags: ["Ephemeral"], + flags: MessageFlags.Ephemeral, }); return; } @@ -1019,7 +1020,7 @@ export async function handleArchivedModmailShowNotes( logger.error("Failed to show archived ticket notes:", error); await interaction.followUp({ content: "An error occurred while showing notes.", - flags: ["Ephemeral"], + flags: MessageFlags.Ephemeral, }); } } @@ -1029,13 +1030,13 @@ export async function handleArchivedModmailShowNotes( * Creates a user selection menu for assigning moderators to tickets */ export async function handleModmailAssign(interaction: ButtonInteraction) { - await interaction.deferReply({ flags: ["Ephemeral"] }); + await interaction.deferReply({ flags: MessageFlags.Ephemeral }); try { if (!interaction.inGuild()) { await interaction.followUp({ content: "This command can only be used in a guild.", - flags: ["Ephemeral"], + flags: MessageFlags.Ephemeral, }); return; } @@ -1045,7 +1046,7 @@ export async function handleModmailAssign(interaction: ButtonInteraction) { if (!member?.permissions.has(PermissionFlagsBits.ManageMessages)) { await interaction.followUp({ content: "You don't have permission to assign tickets.", - flags: ["Ephemeral"], + flags: MessageFlags.Ephemeral, }); return; } @@ -1053,7 +1054,7 @@ export async function handleModmailAssign(interaction: ButtonInteraction) { if (!interaction.channel?.isThread()) { await interaction.followUp({ content: "This command can only be used in a modmail thread.", - flags: ["Ephemeral"], + flags: MessageFlags.Ephemeral, }); return; } @@ -1064,7 +1065,7 @@ export async function handleModmailAssign(interaction: ButtonInteraction) { if (!modMail) { await interaction.followUp({ content: "This is not an active modmail thread.", - flags: ["Ephemeral"], + flags: MessageFlags.Ephemeral, }); return; } @@ -1082,13 +1083,13 @@ export async function handleModmailAssign(interaction: ButtonInteraction) { await interaction.followUp({ content: "Please select a moderator to assign this ticket to:", components: [selectRow], - flags: ["Ephemeral"], + flags: MessageFlags.Ephemeral, }); } catch (error) { logger.error("Failed to create assign selection:", error); await interaction.followUp({ content: "An error occurred while creating the assignment selection.", - flags: ["Ephemeral"], + flags: MessageFlags.Ephemeral, }); } } @@ -1098,13 +1099,13 @@ export async function handleModmailAssign(interaction: ButtonInteraction) { * Directs users to use the slash command for adding notes */ export async function handleModmailAddNote(interaction: ButtonInteraction) { - await interaction.deferReply({ flags: ["Ephemeral"] }); + await interaction.deferReply({ flags: MessageFlags.Ephemeral }); try { if (!interaction.inGuild()) { await interaction.followUp({ content: "This command can only be used in a guild.", - flags: ["Ephemeral"], + flags: MessageFlags.Ephemeral, }); return; } @@ -1114,7 +1115,7 @@ export async function handleModmailAddNote(interaction: ButtonInteraction) { if (!member?.permissions.has(PermissionFlagsBits.ManageMessages)) { await interaction.followUp({ content: "You don't have permission to add notes.", - flags: ["Ephemeral"], + flags: MessageFlags.Ephemeral, }); return; } @@ -1122,7 +1123,7 @@ export async function handleModmailAddNote(interaction: ButtonInteraction) { if (!interaction.channel?.isThread()) { await interaction.followUp({ content: "This command can only be used in a modmail thread.", - flags: ["Ephemeral"], + flags: MessageFlags.Ephemeral, }); return; } @@ -1133,7 +1134,7 @@ export async function handleModmailAddNote(interaction: ButtonInteraction) { if (!modMail) { await interaction.followUp({ content: "This is not an active modmail thread.", - flags: ["Ephemeral"], + flags: MessageFlags.Ephemeral, }); return; } @@ -1141,13 +1142,13 @@ export async function handleModmailAddNote(interaction: ButtonInteraction) { await interaction.followUp({ content: "Please use the `/ticket note` command to add a note with your desired content.", - flags: ["Ephemeral"], + flags: MessageFlags.Ephemeral, }); } catch (error) { logger.error("Failed to handle add note button:", error); await interaction.followUp({ content: "An error occurred while processing the add note request.", - flags: ["Ephemeral"], + flags: MessageFlags.Ephemeral, }); } } @@ -1161,14 +1162,14 @@ export async function handleModmailAddNote(interaction: ButtonInteraction) { * Shows ticket information to the user who created the ticket */ export async function handleModmailUserDetails(interaction: ButtonInteraction) { - await interaction.deferReply({ flags: ["Ephemeral"] }); + await interaction.deferReply({ flags: MessageFlags.Ephemeral }); try { // Only allow this in DMs if (interaction.inGuild()) { await interaction.followUp({ content: "This action can only be used in DMs.", - flags: ["Ephemeral"], + flags: MessageFlags.Ephemeral, }); return; } @@ -1177,7 +1178,7 @@ export async function handleModmailUserDetails(interaction: ButtonInteraction) { if (!modMail) { await interaction.followUp({ content: "You don't have an active ticket.", - flags: ["Ephemeral"], + flags: MessageFlags.Ephemeral, }); return; } @@ -1229,13 +1230,13 @@ export async function handleModmailUserDetails(interaction: ButtonInteraction) { await interaction.followUp({ embeds: [ticketDetails.embed], components: [ticketDetails.row], - flags: ["Ephemeral"], + flags: MessageFlags.Ephemeral, }); } catch (error) { logger.error("Failed to show user ticket details:", error); await interaction.followUp({ content: "An error occurred while retrieving your ticket details.", - flags: ["Ephemeral"], + flags: MessageFlags.Ephemeral, }); } } @@ -1245,14 +1246,14 @@ export async function handleModmailUserDetails(interaction: ButtonInteraction) { * Allows users to close their own tickets, creates an archive, and notifies the thread */ export async function handleModmailUserClose(interaction: ButtonInteraction) { - await interaction.deferReply({ flags: ["Ephemeral"] }); + await interaction.deferReply({ flags: MessageFlags.Ephemeral }); try { // Only allow this in DMs if (interaction.inGuild()) { await interaction.followUp({ content: "This action can only be used in DMs.", - flags: ["Ephemeral"], + flags: MessageFlags.Ephemeral, }); return; } @@ -1261,7 +1262,7 @@ export async function handleModmailUserClose(interaction: ButtonInteraction) { if (!modMail) { await interaction.followUp({ content: "You don't have an active ticket.", - flags: ["Ephemeral"], + flags: MessageFlags.Ephemeral, }); return; } @@ -1361,13 +1362,13 @@ export async function handleModmailUserClose(interaction: ButtonInteraction) { await interaction.followUp({ content: `✅ Your ticket has been closed. Thank you for contacting us!${archiveCreated ? " An archive of your conversation has been sent above." : ""}`, - flags: ["Ephemeral"], + flags: MessageFlags.Ephemeral, }); } catch (error) { logger.error("Failed to close user ticket:", error); await interaction.followUp({ content: "An error occurred while closing your ticket.", - flags: ["Ephemeral"], + flags: MessageFlags.Ephemeral, }); } } diff --git a/src/modules/roles/roleColour.command.ts b/src/modules/roles/roleColour.command.ts index 1edcf555..f285e410 100644 --- a/src/modules/roles/roleColour.command.ts +++ b/src/modules/roles/roleColour.command.ts @@ -1,7 +1,10 @@ -import type { ColorResolvable, GuildMember, Role } from "discord.js"; import { ApplicationCommandOptionType, ApplicationCommandType, + type ColorResolvable, + type GuildMember, + MessageFlags, + type Role, } from "discord.js"; import type { Command, ExecutableSubcommand } from "djs-slash-helper"; import { config } from "../../Config.js"; @@ -13,7 +16,7 @@ const ResetSubcommand: ExecutableSubcommand = { name: "reset", description: "Reset your role colour", async handle(interaction) { - await interaction.deferReply({ flags: ["Ephemeral"] }); + await interaction.deferReply({ flags: MessageFlags.Ephemeral }); const user = interaction.user; const member = interaction.member as GuildMember; const roleInfo = await ColourRoles.findOne({ @@ -75,12 +78,12 @@ const SetSubcommand: ExecutableSubcommand = { if (!colour.startsWith("#") || colour.length !== 7) { await interaction.reply({ content: "Not a valid colour", - flags: ["Ephemeral"], + flags: MessageFlags.Ephemeral, }); return; } - await interaction.deferReply({ flags: ["Ephemeral"] }); + await interaction.deferReply({ flags: MessageFlags.Ephemeral }); const user = interaction.user; const member = interaction.member as GuildMember; const roleInfo = await ColourRoles.findOne({ diff --git a/src/modules/suggest/manageSuggestion.command.ts b/src/modules/suggest/manageSuggestion.command.ts index 5558a0ee..3256ac85 100644 --- a/src/modules/suggest/manageSuggestion.command.ts +++ b/src/modules/suggest/manageSuggestion.command.ts @@ -1,5 +1,6 @@ -import { ApplicationCommandType } from "discord.js"; +import { ApplicationCommandType, MessageFlags } from "discord.js"; import type { Command } from "djs-slash-helper"; + import { createSuggestionManageButtons } from "./suggest.js"; export const ManageSuggestionCommand: Command = @@ -13,7 +14,7 @@ export const ManageSuggestionCommand: Command = await interaction.reply({ content: "Manage Suggestion", components: [row], - flags: ["Ephemeral"], + flags: MessageFlags.Ephemeral, }); }, }; diff --git a/src/modules/suggest/suggest.command.ts b/src/modules/suggest/suggest.command.ts index 3ed705c6..f34ebad7 100644 --- a/src/modules/suggest/suggest.command.ts +++ b/src/modules/suggest/suggest.command.ts @@ -6,9 +6,11 @@ import { ButtonStyle, type ChatInputCommandInteraction, type GuildMember, + MessageFlags, } from "discord.js"; import type { Command } from "djs-slash-helper"; import { config } from "../../Config.js"; + import { createSuggestion, createSuggestionEmbed, @@ -32,7 +34,7 @@ export const SuggestCommand: Command = { handle: async (interaction: ChatInputCommandInteraction) => { if (!interaction.member || !interaction.inGuild()) { await interaction.reply({ - flags: ["Ephemeral"], + flags: MessageFlags.Ephemeral, content: "We are not in a guild?", }); } @@ -40,7 +42,7 @@ export const SuggestCommand: Command = { const member = interaction.member as GuildMember; await interaction.deferReply({ - flags: ["Ephemeral"], + flags: MessageFlags.Ephemeral, }); // Get the suggestion and optional image const suggestionText = interaction.options.get("suggestion") @@ -53,7 +55,7 @@ export const SuggestCommand: Command = { if (!suggestionChannel) { await interaction.followUp({ content: "There is no Suggestion channel!", - flags: ["Ephemeral"], + flags: MessageFlags.Ephemeral, }); return; } @@ -61,7 +63,7 @@ export const SuggestCommand: Command = { await interaction.followUp({ content: "The suggestion channel is either not writeable or not a text channel!", - flags: ["Ephemeral"], + flags: MessageFlags.Ephemeral, }); return; } @@ -101,7 +103,7 @@ export const SuggestCommand: Command = { await interaction.followUp({ content: `Suggestion with the ID \`${suggestionId}\` successfully submitted! See [here](${response.url})`, - flags: ["Ephemeral"], + flags: MessageFlags.Ephemeral, }); await createSuggestion( diff --git a/src/modules/suggest/suggest.listener.ts b/src/modules/suggest/suggest.listener.ts index abc0f16b..5bdecf35 100644 --- a/src/modules/suggest/suggest.listener.ts +++ b/src/modules/suggest/suggest.listener.ts @@ -1,28 +1,23 @@ import { - ActionRowBuilder, - ButtonBuilder, type ButtonInteraction, ButtonStyle, type GuildMember, type Interaction, - type Message, - ModalBuilder, + MessageFlags, type ModalSubmitInteraction, - type OmitPartialGroupDMChannel, - type SendableChannels, - TextInputBuilder, TextInputStyle, } from "discord.js"; import { config } from "../../Config.js"; -import { - type Suggestion, - SuggestionStatus, -} from "../../store/models/Suggestion.js"; +import { SuggestionStatus } from "../../store/models/Suggestion.js"; + import type { EventListener } from "../module.js"; import { + createReasonModal, createSuggestionEmbedFromEntity, createVotesEmbed, + generateVoteMessage, getSuggestionByMessageIdOrRecoverFromMessage, + respondToSuggestionInteraction, SUGGESTION_MANAGE_APPROVE_ID, SUGGESTION_MANAGE_APPROVE_MODAL_ID, SUGGESTION_MANAGE_REJECT_ID, @@ -36,314 +31,230 @@ import { } from "./suggest.js"; const SUGGESTION_BUTTON_MAP: { - [key: string]: SuggestionVoteType; + [SUGGESTION_NO_ID]: SuggestionVoteType; + [SUGGESTION_YES_ID]: SuggestionVoteType; + [key: string]: SuggestionVoteType | undefined; } = { - "suggestion-no": -1, - "suggestion-yes": 1, + [SUGGESTION_NO_ID]: -1, + [SUGGESTION_YES_ID]: 1, }; -async function respondToSuggestionInteraction( - interaction: ButtonInteraction | ModalSubmitInteraction, - suggestion: Suggestion, - suggestionArchive: SendableChannels, - initialMessage: OmitPartialGroupDMChannel, - moderatorReason?: string, -) { - if (!interaction.guild) { - await interaction.followUp({ - content: "This can only be done in a guild!", - flags: "Ephemeral", +// Handler functions +async function handleVoteButtonInteraction( + interaction: ButtonInteraction, + member: GuildMember, +): Promise { + if (!interaction.message.editable) { + await interaction.reply({ + content: "This suggestion is no longer editable!", + flags: MessageFlags.Ephemeral, + }); + return; + } + const votingValue = SUGGESTION_BUTTON_MAP[interaction.customId]; + if (!votingValue) { + await interaction.reply({ + content: "This is not a valid Button!", + flags: MessageFlags.Ephemeral, }); return; } - // Find the thread associated with the original message - let threadUrl: string | undefined; - if (initialMessage.hasThread) { - const thread = initialMessage.thread; - if (thread) { - threadUrl = `https://discord.com/channels/${interaction.guild.id}/${thread.id}`; - } + await interaction.deferReply({ flags: MessageFlags.Ephemeral }); + + const suggestion = await getSuggestionByMessageIdOrRecoverFromMessage( + interaction.message, + ); + + if (!suggestion) { + await interaction.followUp({ + content: "No Suggestion found for this message", + flags: MessageFlags.Ephemeral, + }); + return; } - const embed = await createSuggestionEmbedFromEntity( - interaction.client, - suggestion, - moderatorReason, - threadUrl, + const previousVoteValue = await upsertVote( + suggestion.id, + BigInt(member.id), + votingValue, ); - const newMessage = await suggestionArchive.send({ - embeds: [embed], - components: [ - new ActionRowBuilder().addComponents( - new ButtonBuilder() - .setCustomId(SUGGESTION_VIEW_VOTES_ID) - .setStyle(ButtonStyle.Secondary) - .setEmoji("👁") - .setLabel("View Votes"), - ), + await suggestion.reload(); + await interaction.message.edit({ + embeds: [ + await createSuggestionEmbedFromEntity(interaction.client, suggestion), ], }); - if (initialMessage.deletable) await initialMessage.delete(); - suggestion.messageId = BigInt(newMessage.id); - await suggestion.save(); -} - -export const SuggestionButtonListener: EventListener = { - async interactionCreate(client, interaction: Interaction) { - if (!interaction.member || !interaction.inGuild()) return; - const member = interaction.member as GuildMember; - - // Handle button interactions - if (interaction.isButton()) { - if ( - interaction.customId === SUGGESTION_NO_ID || - interaction.customId === SUGGESTION_YES_ID - ) { - if (!interaction.message.editable) { - await interaction.reply({ - content: "This suggestion is no longer editable!", - flags: ["Ephemeral"], - }); - return; - } - await interaction.deferReply({ flags: ["Ephemeral"] }); + const content = generateVoteMessage(votingValue, previousVoteValue); + await interaction.followUp({ + content, + flags: MessageFlags.Ephemeral, + }); +} - const votingValue = SUGGESTION_BUTTON_MAP[ - interaction.customId as keyof typeof SUGGESTION_BUTTON_MAP - ] as SuggestionVoteType; +async function handleViewVotesInteraction( + interaction: ButtonInteraction, + member: GuildMember, +): Promise { + await interaction.deferReply({ flags: MessageFlags.Ephemeral }); - const suggestion = await getSuggestionByMessageIdOrRecoverFromMessage( - interaction.message, - ); + const suggestion = await getSuggestionByMessageIdOrRecoverFromMessage( + interaction.message, + ); + if (!suggestion) { + await interaction.followUp({ + content: "No Suggestion found for this message", + flags: MessageFlags.Ephemeral, + }); + return; + } - if (suggestion == null) { - await interaction.followUp({ - content: "No Suggestion found for this message", - flags: ["Ephemeral"], - }); - return; - } + const yesVotes = suggestion.votes?.filter((vote) => vote.vote === 1) || []; + const noVotes = suggestion.votes?.filter((vote) => vote.vote === -1) || []; + const embed = createVotesEmbed(member, yesVotes, noVotes); - const previousVoteValue = await upsertVote( - suggestion.id, - BigInt(member.id), - votingValue, - ); + await interaction.followUp({ + embeds: [embed], + flags: MessageFlags.Ephemeral, + }); +} - await suggestion.reload(); - await interaction.message.edit({ - embeds: [await createSuggestionEmbedFromEntity(client, suggestion)], - }); +async function handleManageModalSubmission( + interaction: ModalSubmitInteraction, + member: GuildMember, + status: SuggestionStatus, + successMessage: string, +): Promise { + await interaction.deferUpdate(); - let content = `You ${previousVoteValue && previousVoteValue === votingValue ? "already " : ""}voted ${votingValue === 1 ? "**Yes**" : "**No**"} on this suggestion`; - if (previousVoteValue && previousVoteValue !== votingValue) { - content = `You changed your vote from ${previousVoteValue === 1 ? "**Yes**" : "**No**"} to ${votingValue === 1 ? "**Yes**" : "**No**"} on this suggestion`; - } - await interaction.followUp({ - content: content, - flags: ["Ephemeral"], - }); - } else if (interaction.customId === SUGGESTION_VIEW_VOTES_ID) { - await interaction.deferReply({ flags: ["Ephemeral"] }); - const suggestion = await getSuggestionByMessageIdOrRecoverFromMessage( - interaction.message, - ); - if (!suggestion) { - await interaction.followUp({ - content: "No Suggestion found for this message", - flags: ["Ephemeral"], - }); - return; - } - const yesVotes = - suggestion.votes?.filter((vote) => vote.vote === 1) || []; - const noVotes = - suggestion.votes?.filter((vote) => vote.vote === -1) || []; + const reason = interaction.fields.getTextInputValue( + SUGGESTION_REASON_INPUT_ID, + ); + const initialMessage = await interaction.message?.fetchReference(); - const embed = createVotesEmbed(member, yesVotes, noVotes); + if (!initialMessage) { + await interaction.followUp({ + content: "Could not find the original suggestion message!", + flags: MessageFlags.Ephemeral, + }); + return; + } - await interaction.followUp({ - embeds: [embed], - flags: ["Ephemeral"], - }); - } else if (interaction.customId === SUGGESTION_MANAGE_APPROVE_ID) { - const modal = new ModalBuilder() - .setCustomId(SUGGESTION_MANAGE_APPROVE_MODAL_ID) - .setTitle("Approve Suggestion"); + const suggestion = + await getSuggestionByMessageIdOrRecoverFromMessage(initialMessage); + if (!suggestion) { + await interaction.followUp({ + content: "No Suggestion found for this message", + flags: MessageFlags.Ephemeral, + }); + return; + } - const reasonInput = new TextInputBuilder() - .setCustomId(SUGGESTION_REASON_INPUT_ID) - .setLabel("Reason (Optional)") - .setStyle(TextInputStyle.Paragraph) - .setPlaceholder("Enter an optional reason for approval...") - .setRequired(false) - .setMaxLength(1024); + suggestion.status = status; + suggestion.moderatorId = BigInt(member.id); + await suggestion.save(); - const actionRow = - new ActionRowBuilder().addComponents(reasonInput); + const suggestionArchive = await interaction.client.channels.fetch( + config.suggest.archiveChannel, + ); - modal.addComponents(actionRow); + if (!suggestionArchive) { + await interaction.followUp({ + content: "Could not find the suggestion archive channel!", + flags: MessageFlags.Ephemeral, + }); + return; + } - await interaction.showModal(modal); - } else if (interaction.customId === SUGGESTION_MANAGE_REJECT_ID) { - const modal = new ModalBuilder() - .setCustomId(SUGGESTION_MANAGE_REJECT_MODAL_ID) - .setTitle("Reject Suggestion"); + if (!suggestionArchive.isSendable() || !suggestionArchive.isTextBased()) { + await interaction.followUp({ + content: + "The suggestion channel is either not writeable or not a text channel!", + flags: MessageFlags.Ephemeral, + }); + return; + } - const reasonInput = new TextInputBuilder() - .setCustomId(SUGGESTION_REASON_INPUT_ID) - .setLabel("Reason (Optional)") - .setStyle(TextInputStyle.Paragraph) - .setPlaceholder("Enter an optional reason for rejection...") - .setRequired(false) - .setMaxLength(1024); + try { + await respondToSuggestionInteraction( + interaction, + suggestion, + suggestionArchive, + initialMessage, + reason.trim() || undefined, + ); + await interaction.editReply({ + content: successMessage, + }); + } catch (error) { + console.error(error); + await interaction.followUp({ + content: + "Something went wrong while archiving the suggestion! Please try again later!", + flags: MessageFlags.Ephemeral, + }); + } +} - const actionRow = - new ActionRowBuilder().addComponents(reasonInput); +export const SuggestionButtonListener: EventListener = { + async interactionCreate(_client, interaction: Interaction) { + if (!interaction.member || !interaction.inGuild()) return; + const member = interaction.member as GuildMember; - modal.addComponents(actionRow); + if (interaction.isButton()) { + switch (interaction.customId) { + case SUGGESTION_NO_ID: + case SUGGESTION_YES_ID: + await handleVoteButtonInteraction(interaction, member); + break; + + case SUGGESTION_VIEW_VOTES_ID: + await handleViewVotesInteraction(interaction, member); + break; + + case SUGGESTION_MANAGE_APPROVE_ID: { + const modal = createReasonModal( + SUGGESTION_MANAGE_APPROVE_MODAL_ID, + "Approve Suggestion", + "Enter an optional reason for approval...", + ); + await interaction.showModal(modal); + break; + } - await interaction.showModal(modal); + case SUGGESTION_MANAGE_REJECT_ID: { + const modal = createReasonModal( + SUGGESTION_MANAGE_REJECT_MODAL_ID, + "Reject Suggestion", + "Enter an optional reason for rejection...", + ); + await interaction.showModal(modal); + break; + } } } - // Handle modal submissions if (interaction.isModalSubmit()) { - if (interaction.customId === SUGGESTION_MANAGE_APPROVE_MODAL_ID) { - await interaction.deferUpdate(); - - const reason = interaction.fields.getTextInputValue( - SUGGESTION_REASON_INPUT_ID, - ); - const initialMessage = await interaction.message?.fetchReference(); - - if (!initialMessage) { - await interaction.followUp({ - content: "Could not find the original suggestion message!", - flags: ["Ephemeral"], - }); - return; - } - - const suggestion = - await getSuggestionByMessageIdOrRecoverFromMessage(initialMessage); - if (!suggestion) { - await interaction.followUp({ - content: "No Suggestion found for this message", - flags: ["Ephemeral"], - }); - return; - } - - suggestion.status = SuggestionStatus.APPROVED; - suggestion.moderatorId = BigInt(member.id); - await suggestion.save(); - - const suggestionArchive = await client.channels.fetch( - config.suggest.archiveChannel, - ); - if (suggestionArchive) { - if ( - !suggestionArchive.isSendable() || - !suggestionArchive.isTextBased() - ) { - await interaction.followUp({ - content: - "The suggestion channel is either not writeable or not a text channel!", - flags: ["Ephemeral"], - }); - return; - } - - try { - await respondToSuggestionInteraction( - interaction, - suggestion, - suggestionArchive, - initialMessage, - reason.trim() || undefined, - ); - await interaction.editReply({ - content: "Suggestion approved!", - }); - } catch (e) { - console.error(e); - await interaction.followUp({ - content: - "Something went wrong while archiving the suggestion! Please try again later!", - flags: ["Ephemeral"], - }); - } - } - } else if (interaction.customId === SUGGESTION_MANAGE_REJECT_MODAL_ID) { - await interaction.deferUpdate(); - - const reason = interaction.fields.getTextInputValue( - SUGGESTION_REASON_INPUT_ID, - ); - const initialMessage = await interaction.message?.fetchReference(); - - if (!initialMessage) { - await interaction.followUp({ - content: "Could not find the original suggestion message!", - flags: ["Ephemeral"], - }); - return; - } - - const suggestion = - await getSuggestionByMessageIdOrRecoverFromMessage(initialMessage); - if (!suggestion) { - await interaction.followUp({ - content: "No Suggestion found for this message", - flags: ["Ephemeral"], - }); - return; - } - - suggestion.status = SuggestionStatus.REJECTED; - suggestion.moderatorId = BigInt(member.id); - await suggestion.save(); - - const suggestionArchive = await client.channels.fetch( - config.suggest.archiveChannel, - ); - if (suggestionArchive) { - if ( - !suggestionArchive.isSendable() || - !suggestionArchive.isTextBased() - ) { - await interaction.followUp({ - content: - "The suggestion channel is either not writeable or not a text channel!", - flags: ["Ephemeral"], - }); - return; - } - - try { - await respondToSuggestionInteraction( - interaction, - suggestion, - suggestionArchive, - initialMessage, - reason.trim() || undefined, - ); - await interaction.editReply({ - content: "Suggestion rejected!", - }); - } catch (e) { - console.error(e); - await interaction.followUp({ - content: - "Something went wrong while archiving the suggestion! Please try again later!", - flags: ["Ephemeral"], - }); - } - } + switch (interaction.customId) { + case SUGGESTION_MANAGE_APPROVE_MODAL_ID: + await handleManageModalSubmission( + interaction, + member, + SuggestionStatus.APPROVED, + "Suggestion approved!", + ); + break; + + case SUGGESTION_MANAGE_REJECT_MODAL_ID: + await handleManageModalSubmission( + interaction, + member, + SuggestionStatus.REJECTED, + "Suggestion rejected!", + ); + break; } } }, diff --git a/src/modules/suggest/suggest.ts b/src/modules/suggest/suggest.ts index 6141fa3e..43730699 100644 --- a/src/modules/suggest/suggest.ts +++ b/src/modules/suggest/suggest.ts @@ -1,11 +1,19 @@ import { ActionRowBuilder, ButtonBuilder, + type ButtonInteraction, ButtonStyle, type Client, type EmbedBuilder, type GuildMember, type Message, + MessageFlags, + ModalBuilder, + type ModalSubmitInteraction, + type OmitPartialGroupDMChannel, + type SendableChannels, + TextInputBuilder, + TextInputStyle, type UserResolvable, } from "discord.js"; import { config } from "../../Config.js"; @@ -331,3 +339,88 @@ export const createSuggestionManageButtons: () => ActionRowBuilder().addComponents( + reasonInput, + ); + + return new ModalBuilder() + .setCustomId(customId) + .setTitle(title) + .addComponents(actionRow); +} + +export async function respondToSuggestionInteraction( + interaction: ButtonInteraction | ModalSubmitInteraction, + suggestion: Suggestion, + suggestionArchive: SendableChannels, + initialMessage: OmitPartialGroupDMChannel, + moderatorReason?: string, +): Promise { + if (!interaction.guild) { + await interaction.followUp({ + content: "This can only be done in a guild!", + flags: MessageFlags.Ephemeral, + }); + return; + } + + const threadUrl = initialMessage.thread?.url; + const embed = await createSuggestionEmbedFromEntity( + interaction.client, + suggestion, + moderatorReason, + threadUrl, + ); + + const newMessage = await suggestionArchive.send({ + embeds: [embed], + components: [ + new ActionRowBuilder().addComponents( + new ButtonBuilder() + .setCustomId(SUGGESTION_VIEW_VOTES_ID) + .setStyle(ButtonStyle.Secondary) + .setEmoji("👁") + .setLabel("View Votes"), + ), + ], + }); + + if (initialMessage.deletable) { + await initialMessage.delete(); + } + + suggestion.messageId = BigInt(newMessage.id); + await suggestion.save(); +} diff --git a/src/modules/xp/dailyReward.command.ts b/src/modules/xp/dailyReward.command.ts index b5a42415..364c8cf7 100644 --- a/src/modules/xp/dailyReward.command.ts +++ b/src/modules/xp/dailyReward.command.ts @@ -1,6 +1,7 @@ import { ApplicationCommandType, type GuildMember, + MessageFlags, type Snowflake, } from "discord.js"; import type { Command } from "djs-slash-helper"; @@ -9,6 +10,7 @@ import { logger } from "../../logging.js"; import { wrapInTransaction } from "../../sentry.js"; import { type DDUser, getOrCreateUserById } from "../../store/models/DDUser.js"; import { createStandardEmbed } from "../../util/embeds.js"; + import { isSpecialUser } from "../../util/users.js"; import { scheduleReminder } from "./dailyReward.reminder.js"; import { giveXp } from "./xpForMessage.util.js"; @@ -31,7 +33,7 @@ export const DailyRewardCommand: Command = { await interaction.deferReply(); if (dailiesInProgress.has(user.id)) { await interaction.followUp({ - flags: ["Ephemeral"], + flags: MessageFlags.Ephemeral, content: "You are already claiming your daily reward!", }); return; @@ -48,7 +50,7 @@ export const DailyRewardCommand: Command = { } const nextClaimTime = getNextDailyTimeFrom(lastClaimTime); await interaction.followUp({ - flags: ["Ephemeral"], + flags: MessageFlags.Ephemeral, content: `You can only claim your daily reward once every 24 hours. You can claim it again .`, @@ -76,7 +78,7 @@ export const DailyRewardCommand: Command = { : 0; await Promise.all([ interaction.followUp({ - flags: ["Ephemeral"], + flags: MessageFlags.Ephemeral, embeds: [ createStandardEmbed(user) .setTitle("Daily Reward Claimed!")