import { createCommand } from "@shared/lib/utils"; import { SlashCommandBuilder, PermissionFlagsBits, Colors, ChatInputCommandInteraction } from "discord.js"; import { guildSettingsService } from "@shared/modules/guild-settings/guild-settings.service"; import { createBaseEmbed, createErrorEmbed, createSuccessEmbed } from "@lib/embeds"; import { getGuildConfig, invalidateGuildConfigCache } from "@shared/lib/config"; import { UserError } from "@shared/lib/errors"; export const settings = createCommand({ data: new SlashCommandBuilder() .setName("settings") .setDescription("Manage guild settings") .setDefaultMemberPermissions(PermissionFlagsBits.Administrator) .addSubcommand(sub => sub.setName("show") .setDescription("Show current guild settings")) .addSubcommand(sub => sub.setName("set") .setDescription("Set a guild setting") .addStringOption(opt => opt.setName("key") .setDescription("Setting to change") .setRequired(true) .addChoices( { name: "Student Role", value: "studentRole" }, { name: "Visitor Role", value: "visitorRole" }, { name: "Welcome Channel", value: "welcomeChannel" }, { name: "Welcome Message", value: "welcomeMessage" }, { name: "Feedback Channel", value: "feedbackChannel" }, { name: "Terminal Channel", value: "terminalChannel" }, { name: "Terminal Message", value: "terminalMessage" }, { name: "Moderation Log Channel", value: "moderationLogChannel" }, { name: "DM on Warn", value: "moderationDmOnWarn" }, { name: "Auto Timeout Threshold", value: "moderationAutoTimeoutThreshold" }, )) .addRoleOption(opt => opt.setName("role") .setDescription("Role value")) .addChannelOption(opt => opt.setName("channel") .setDescription("Channel value")) .addStringOption(opt => opt.setName("text") .setDescription("Text value")) .addIntegerOption(opt => opt.setName("number") .setDescription("Number value")) .addBooleanOption(opt => opt.setName("boolean") .setDescription("Boolean value (true/false)"))) .addSubcommand(sub => sub.setName("reset") .setDescription("Reset a setting to default") .addStringOption(opt => opt.setName("key") .setDescription("Setting to reset") .setRequired(true) .addChoices( { name: "Student Role", value: "studentRole" }, { name: "Visitor Role", value: "visitorRole" }, { name: "Welcome Channel", value: "welcomeChannel" }, { name: "Welcome Message", value: "welcomeMessage" }, { name: "Feedback Channel", value: "feedbackChannel" }, { name: "Terminal Channel", value: "terminalChannel" }, { name: "Terminal Message", value: "terminalMessage" }, { name: "Moderation Log Channel", value: "moderationLogChannel" }, { name: "DM on Warn", value: "moderationDmOnWarn" }, { name: "Auto Timeout Threshold", value: "moderationAutoTimeoutThreshold" }, ))) .addSubcommand(sub => sub.setName("colors") .setDescription("Manage color roles") .addStringOption(opt => opt.setName("action") .setDescription("Action to perform") .setRequired(true) .addChoices( { name: "List", value: "list" }, { name: "Add", value: "add" }, { name: "Remove", value: "remove" }, )) .addRoleOption(opt => opt.setName("role") .setDescription("Role to add/remove") .setRequired(false))), execute: async (interaction) => { await interaction.deferReply({ ephemeral: true }); const subcommand = interaction.options.getSubcommand(); const guildId = interaction.guildId!; try { switch (subcommand) { case "show": await handleShow(interaction, guildId); break; case "set": await handleSet(interaction, guildId); break; case "reset": await handleReset(interaction, guildId); break; case "colors": await handleColors(interaction, guildId); break; } } catch (error) { if (error instanceof UserError) { await interaction.editReply({ embeds: [createErrorEmbed(error.message)] }); } else { throw error; } } }, }); async function handleShow(interaction: ChatInputCommandInteraction, guildId: string) { const settings = await getGuildConfig(guildId); const colorRolesDisplay = settings.colorRoles?.length ? settings.colorRoles.map(id => `<@&${id}>`).join(", ") : "None"; const embed = createBaseEmbed("Guild Settings", undefined, Colors.Blue) .addFields( { name: "Student Role", value: settings.studentRole ? `<@&${settings.studentRole}>` : "Not set", inline: true }, { name: "Visitor Role", value: settings.visitorRole ? `<@&${settings.visitorRole}>` : "Not set", inline: true }, { name: "\u200b", value: "\u200b", inline: true }, { name: "Welcome Channel", value: settings.welcomeChannelId ? `<#${settings.welcomeChannelId}>` : "Not set", inline: true }, { name: "Feedback Channel", value: settings.feedbackChannelId ? `<#${settings.feedbackChannelId}>` : "Not set", inline: true }, { name: "Moderation Log", value: settings.moderation?.cases?.logChannelId ? `<#${settings.moderation.cases.logChannelId}>` : "Not set", inline: true }, { name: "Terminal Channel", value: settings.terminal?.channelId ? `<#${settings.terminal.channelId}>` : "Not set", inline: true }, { name: "DM on Warn", value: settings.moderation?.cases?.dmOnWarn !== false ? "Enabled" : "Disabled", inline: true }, { name: "Auto Timeout", value: settings.moderation?.cases?.autoTimeoutThreshold ? `${settings.moderation.cases.autoTimeoutThreshold} warnings` : "Disabled", inline: true }, { name: "Color Roles", value: colorRolesDisplay, inline: false }, ); if (settings.welcomeMessage) { embed.addFields({ name: "Welcome Message", value: settings.welcomeMessage.substring(0, 1024), inline: false }); } await interaction.editReply({ embeds: [embed] }); } async function handleSet(interaction: ChatInputCommandInteraction, guildId: string) { const key = interaction.options.getString("key", true); const role = interaction.options.getRole("role"); const channel = interaction.options.getChannel("channel"); const text = interaction.options.getString("text"); const number = interaction.options.getInteger("number"); const boolean = interaction.options.getBoolean("boolean"); let value: string | number | boolean | null = null; if (role) value = role.id; else if (channel) value = channel.id; else if (text) value = text; else if (number !== null) value = number; else if (boolean !== null) value = boolean; if (value === null) { await interaction.editReply({ embeds: [createErrorEmbed("Please provide a role, channel, text, number, or boolean value")] }); return; } await guildSettingsService.updateSetting(guildId, key, value); invalidateGuildConfigCache(guildId); await interaction.editReply({ embeds: [createSuccessEmbed(`Setting "${key}" updated`)] }); } async function handleReset(interaction: ChatInputCommandInteraction, guildId: string) { const key = interaction.options.getString("key", true); await guildSettingsService.updateSetting(guildId, key, null); invalidateGuildConfigCache(guildId); await interaction.editReply({ embeds: [createSuccessEmbed(`Setting "${key}" reset to default`)] }); } async function handleColors(interaction: ChatInputCommandInteraction, guildId: string) { const action = interaction.options.getString("action", true); const role = interaction.options.getRole("role"); switch (action) { case "list": { const settings = await getGuildConfig(guildId); const colorRoles = settings.colorRoles ?? []; if (colorRoles.length === 0) { await interaction.editReply({ embeds: [createBaseEmbed("Color Roles", "No color roles configured.", Colors.Blue)] }); return; } const embed = createBaseEmbed("Color Roles", undefined, Colors.Blue) .addFields({ name: `Configured Roles (${colorRoles.length})`, value: colorRoles.map(id => `<@&${id}>`).join("\n"), }); await interaction.editReply({ embeds: [embed] }); break; } case "add": { if (!role) { await interaction.editReply({ embeds: [createErrorEmbed("Please specify a role to add.")] }); return; } await guildSettingsService.addColorRole(guildId, role.id); invalidateGuildConfigCache(guildId); await interaction.editReply({ embeds: [createSuccessEmbed(`Added <@&${role.id}> to color roles.`)] }); break; } case "remove": { if (!role) { await interaction.editReply({ embeds: [createErrorEmbed("Please specify a role to remove.")] }); return; } await guildSettingsService.removeColorRole(guildId, role.id); invalidateGuildConfigCache(guildId); await interaction.editReply({ embeds: [createSuccessEmbed(`Removed <@&${role.id}> from color roles.`)] }); break; } } }