Manage guild settings via Discord with subcommands: - show: Display current settings - set: Update individual settings (roles, channels, text, numbers, booleans) - reset: Clear a setting to default - colors: Manage color roles (list/add/remove)
248 lines
11 KiB
TypeScript
248 lines
11 KiB
TypeScript
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;
|
|
}
|
|
}
|
|
}
|