Files
hexerade/src/commands/notes.ts
T
2025-09-07 20:13:00 -04:00

230 lines
7.5 KiB
TypeScript

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
};
}));
}
};