import { ApplicationCommandOptionType, ApplicationIntegrationType, AutocompleteInteraction, ChatInputCommandInteraction, Client, GuildBasedChannel, InteractionContextType, MessageFlags, MessageMentions, PermissionFlagsBits, SlashCommandBuilder, User } from 'discord.js'; import { Database } from '../shared/Database'; import { channelIsInStaffCategory, deferInteraction, logCustomEvent } from '../utils'; import { getNoteMessage } from '../utils/note-utils'; const mentionRegex = new RegExp(MessageMentions.UsersPattern); export default { name: 'notes', data: new SlashCommandBuilder() .setName('notes') .setDescription('Add, view, or remove user notes.') .setIntegrationTypes(ApplicationIntegrationType.GuildInstall) .setContexts(InteractionContextType.Guild) .setDefaultMemberPermissions(PermissionFlagsBits.BanMembers) .addSubcommand(subcommand => subcommand .setName('add') .setDescription('Add notes to a user.') .addStringOption(option => option .setName('user') .setDescription('The discord user mention, or ID, to add a note to.') .setRequired(true) ) .addStringOption(option => option .setName('reason') .setDescription('The reason for the note.') .setRequired(true) ) ) .addSubcommand(subcommand => subcommand .setName('edit') .setDescription('Edit notes on a user.') .addStringOption(option => option .setName('user') .setDescription('The discord user mention, or ID, to edit the notes of.') .setRequired(true) ) .addIntegerOption(option => option .setName('note') .setDescription('The note to edit.') .setRequired(true) .setAutocomplete(true) ) .addStringOption(option => option .setName('new-reason') .setDescription('The new reason for the note.') .setRequired(true) ) ) .addSubcommand(subcommand => subcommand .setName('remove') .setDescription('Remove notes from a user.') .addStringOption(option => option .setName('user') .setDescription('The discord user mention, or ID, to remove a note from.') .setRequired(true) ) .addIntegerOption(option => option .setName('note') .setDescription('The note to remove.') .setRequired(true) .setAutocomplete(true) ) ) .addSubcommand(subcommand => subcommand .setName('list') .setDescription("List a user's notes") .addStringOption(option => option .setName('user') .setDescription('The discord user mention, or ID, to list the notes of.') .setRequired(true) ) ), handler: async function (client: Client, interaction: ChatInputCommandInteraction) { const subcommand = interaction.options.getSubcommand(true); const input = interaction.options.getString('user', true); const matches = mentionRegex.exec(input); mentionRegex.lastIndex = 0; const idToUse = matches ? matches.groups!.id : input; await deferInteraction(interaction); let user: User | null = null; try { user = await client.users.fetch(idToUse); } catch (e) { return interaction.editReply('An error has occurred. Are you using the right format? You can provide a user ID (ex `324007235137044482`) or a user mention (you must have text previews enabled, Settings -> Chat -> Text Box).'); } if (!user) return interaction.editReply('User not found'); if (subcommand == 'add') { const reason = interaction.options.getString('reason', true); logCustomEvent(interaction.guild!, { title: 'Note Added', description: null, color: 0x00FF00, timestamp: new Date(), fields: [ { name: 'User', value: `<@${interaction.user.id}>\n${interaction.user.username}`, inline: true }, { name: 'Note', value: reason, inline: true } ] }); await Database.putNote(idToUse, reason, interaction.user.id); interaction.editReply(`Note added to <@${idToUse}>.\n\nReason:\n${reason}`); } else if (subcommand == 'remove') { const noteId = interaction.options.getInteger('note', true); const notes = await Database.getNotes(idToUse); const note = notes.find(n => n.id == noteId); if (!note) return interaction.editReply('Note not found.'); logCustomEvent(interaction.guild!, { title: 'Note Removed', description: null, color: 0xFF0000, timestamp: new Date(), fields: [ { name: 'User', value: `<@${interaction.user.id}>\n${interaction.user.username}`, inline: true }, { name: 'Note', value: `${note.reason}\nBy: <@${note.mod_id}>`, inline: true } ] }); await Database.removeNote(noteId); interaction.editReply('Removed note.'); } else if (subcommand == 'edit') { const noteId = interaction.options.getInteger('note', true); const notes = await Database.getNotes(idToUse); const note = notes.find(n => n.id == noteId); if (!note) return interaction.editReply('Note not found.'); const reason = interaction.options.getString('new-reason', true); logCustomEvent(interaction.guild!, { title: 'Note Edited', description: null, color: 0x00FF00, timestamp: new Date(), fields: [ { name: 'User', value: `<@${interaction.user.id}>\n${interaction.user.username}`, inline: true }, { name: 'Old reason', value: note.reason }, { name: 'New reason', value: reason, inline: true } ] }); await Database.editNote(noteId, note.reason, reason, interaction.user.id); interaction.editReply(`Note on <@${idToUse}> edited.\n\nNew reason:\n${reason}`); } else if (subcommand == 'list') { const noteMessage = await getNoteMessage(idToUse, 1); if (!noteMessage) return interaction.editReply(`No notes found for <@${idToUse}>`); interaction.editReply(noteMessage); } }, autoComplete: async function (client: Client, interaction: AutocompleteInteraction) { const input = interaction.options.getString('user', true); const matches = mentionRegex.exec(input); mentionRegex.lastIndex = 0; const idToUse = matches ? matches.groups!.id : input; if (!idToUse) return interaction.respond([]); const value = interaction.options.getFocused().toLowerCase(); const notes = await Database.getNotes(idToUse); const toRespond = notes.filter(w => !value ? true : w.reason.toLowerCase().includes(value)); if (toRespond.length > 25) toRespond.length = 25; interaction.respond(toRespond.map((w) => { return { name: w.reason.substring(0, 50), value: w.id }; })); } };