diff --git a/src/buttons/close-mod-ticket.ts b/src/buttons/close-mod-ticket.ts new file mode 100644 index 0000000..9cdde0f --- /dev/null +++ b/src/buttons/close-mod-ticket.ts @@ -0,0 +1,30 @@ +import { ButtonInteraction, Client, MessageFlags, ChannelType, PermissionFlagsBits } from 'discord.js'; + +export default { + name: 'close-mod-ticket', + handler: async function (client: Client, interaction: ButtonInteraction) { + const channel = await interaction.channel?.fetch(); + const guild = await interaction.guild?.fetch(); + const member = await guild?.members.fetch(interaction.user.id); + + if (!channel || !channel.isThread() || !channel.isSendable() || channel.type != ChannelType.PrivateThread || !member) + return interaction.reply({ flags: [MessageFlags.Ephemeral], content: 'Oops. Something went wrong. Please report this to a staff member.' }); + + if (!member.permissions.has(PermissionFlagsBits.KickMembers)) + return interaction.reply({ flags: [MessageFlags.Ephemeral], content: 'Only staff members may close mod tickets.' }); + + await interaction.message.edit({ + content: interaction.message.content, + components: [] + }); + + await channel.send('This ticket has been closed by staff.'); + + await interaction.reply({ flags: [MessageFlags.Ephemeral], content: 'Ticket closed.' }); + + channel.edit({ + archived: true, + locked: true + }); + } +}; \ No newline at end of file diff --git a/src/commands/mod-ticket.ts b/src/commands/mod-ticket.ts new file mode 100644 index 0000000..fb9c58b --- /dev/null +++ b/src/commands/mod-ticket.ts @@ -0,0 +1,74 @@ +import { ApplicationIntegrationType, ChatInputCommandInteraction, Client, InteractionContextType, LabelBuilder, ModalBuilder, PermissionFlagsBits, SlashCommandBuilder, StringSelectMenuBuilder, StringSelectMenuOptionBuilder, TextInputBuilder, TextInputStyle } from 'discord.js'; + +export default { + name: 'mod-ticket', + data: new SlashCommandBuilder() + .setName('mod-ticket') + .setDescription('Opens a mod private ticket and pulls the user into it.') + .setIntegrationTypes(ApplicationIntegrationType.GuildInstall) + .setContexts(InteractionContextType.Guild) + .setDefaultMemberPermissions(PermissionFlagsBits.KickMembers) + .addUserOption(option => + option + .setName('user') + .setDescription('The user to pull in to the ticket.') + .setRequired(true) + ), + handler: async function (client: Client, interaction: ChatInputCommandInteraction) { + if (!interaction.guild) return interaction.editReply('This command must be used in a server.'); + + const user = interaction.options.getUser('user', true); + const member = await interaction.guild.members.fetch(user.id); + + if (!member) return interaction.editReply('Could not find member.'); + + const modal = new ModalBuilder() + .setCustomId(`open-mod-ticket_${user.id}`) + .setTitle(`Opening A Mod Ticket With ${member.displayName}`); + + const titleInput = new TextInputBuilder() + .setCustomId('title') + .setMaxLength(100) + .setStyle(TextInputStyle.Short) + .setRequired(false); + + const titleLabel = new LabelBuilder() + .setLabel('Title') + .setDescription(`The name of the thread. Defaults to "Mod Ticket For ${member.displayName}" if left empty`) + .setTextInputComponent(titleInput); + + const initialMessageInput = new TextInputBuilder() + .setCustomId('initial-message') + .setMaxLength(1800) + .setStyle(TextInputStyle.Paragraph) + .setRequired(false); + + const initialMessageLabel = new LabelBuilder() + .setLabel('Inital Message') + .setDescription('The inital message sent in the thread') + .setTextInputComponent(initialMessageInput); + + const yesNoMenu = new StringSelectMenuBuilder() + .setCustomId('auto-join-thread') + .setRequired(false) + .addOptions( + new StringSelectMenuOptionBuilder() + .setLabel('Yes') + .setDefault(true) + .setValue('yes'), + new StringSelectMenuOptionBuilder() + .setLabel('No') + .setDefault(false) + .setValue('no') + ); + + const autoJoinLabel = new LabelBuilder() + .setLabel('Auto Join Thread') + .setDescription('Whether or not to join you to the thread. Selecting no will not notify you of messages sent!') + .setStringSelectMenuComponent(yesNoMenu); + + modal.addLabelComponents(titleLabel, initialMessageLabel, autoJoinLabel); + + interaction.showModal(modal); + } +}; \ No newline at end of file diff --git a/src/modals/open-mod-ticket.ts b/src/modals/open-mod-ticket.ts new file mode 100644 index 0000000..6833730 --- /dev/null +++ b/src/modals/open-mod-ticket.ts @@ -0,0 +1,31 @@ +import { Client, MessageFlags, ModalSubmitInteraction } from 'discord.js'; +import { Database } from '../shared/Database'; +import { createPrivateHelpTicketThread } from '../utils'; + +const warning = '\n\n\nLeaving this thread without acknowledgement may result in punishment. Staff will close the thread when they deem your response acceptable.'; + +export default { + name: 'open-mod-ticket', + handler: async function (client: Client, interaction: ModalSubmitInteraction, userId: string) { + const guild = await client.guilds.fetch(interaction.guildId!); + const member = await guild.members.fetch(userId); + + const guildSettings = await Database.getGuildSettings(guild.id); + + if (!guildSettings || !guildSettings.private_help_channel_id) + return interaction.reply({ flags: [MessageFlags.Ephemeral], content: 'Failed to create ticket. Please report this to a developer.' }); + + const title = interaction.fields.getTextInputValue('title') ?? `Mod Ticket For ${member.displayName}`; + const reason = interaction.fields.getTextInputValue('initial-message') + warning; + const autoJoin = interaction.fields.getStringSelectValues('auto-join-thread')[0] == 'yes'; + + const membersToAdd = [userId]; + + if (autoJoin) membersToAdd.push(interaction.user.id); + + const thread = await createPrivateHelpTicketThread(client, guild, null, reason, title, membersToAdd); + + if (thread) interaction.reply({ flags: [MessageFlags.Ephemeral], content: `Mod ticket created: ${thread}.${!autoJoin ? "You've selected not to auto join the thread. You will not be notified of messages sent there. You will have to check the thread periodically for user response." : ''}` }); + else interaction.reply({ flags: [MessageFlags.Ephemeral], content: 'Failed to create ticket. Please report this to a developer.' }); + } +}; \ No newline at end of file