feat: impl. config command for bug channel & manager roles
willow hai@wlo.moe
Sat, 24 May 2025 02:45:36 +0100
3 files changed,
219 insertions(+),
4 deletions(-)
M
src/database/schemas.ts
→
src/database/schemas.ts
@@ -34,10 +34,10 @@ bug_id: z.string().optional(),
}); // ts types -export type Guild = z.infer<typeof guildSchema> & Document; -export type User = z.infer<typeof userSchema> & Document; -export type Bug = z.infer<typeof bugSchema> & Document; -export type Media = z.infer<typeof mediaSchema> & Document; +export type GuildType = z.infer<typeof guildSchema> & Document; +export type UserType = z.infer<typeof userSchema> & Document; +export type BugType = z.infer<typeof bugSchema> & Document; +export type MediaType = z.infer<typeof mediaSchema> & Document; // mongoose schemas const GuildSchema = new Schema(
A
src/interactions/commands/config.ts
@@ -0,0 +1,190 @@
+import { + ChannelType, + type ChatInputCommandInteraction, + type Client, + PermissionFlagsBits, + SlashCommandBuilder, +} from "discord.js"; +import { GuildModel, type GuildType } from "../../database/schemas.ts"; +import { Logger } from "../../utils/logging.ts"; + +const logger = new Logger("config-command"); + +const commandData = new SlashCommandBuilder() + .setName("config") + .setDescription("configure bot settings for this server") + .setDefaultMemberPermissions(PermissionFlagsBits.ManageGuild) + .addSubcommand((subcommand) => + subcommand + .setName("manager-role") + .setDescription("manage roles that can manage bug reports") + .addStringOption((option) => + option + .setName("action") + .setDescription("action to perform") + .setRequired(true) + .addChoices( + { name: "add", value: "add" }, + { name: "remove", value: "remove" }, + { name: "list", value: "list" }, + ), + ) + .addRoleOption((option) => + option + .setName("role") + .setDescription("role to add/remove") + .setRequired(false), + ), + ) + .addSubcommand((subcommand) => + subcommand + .setName("bug-channel") + .setDescription("set the channel where bug reports are sent") + .addChannelOption((option) => + option + .setName("channel") + .setDescription("channel for bug reports") + .setRequired(true), + ), + ); + +async function execute( + _client: Client, + interaction: ChatInputCommandInteraction, +) { + if (!interaction.guild) { + await interaction.reply({ + content: "❌ this command can only be used in a server.", + flags: ["Ephemeral"], + }); + return; + } + + const subcommand = interaction.options.getSubcommand(); + + try { + let guild = await GuildModel.findOne({ + guild_id: interaction.guild.id, + }); + + if (!guild) { + guild = new GuildModel({ + guild_id: interaction.guild.id, + manager_roles: [], + }); + await guild.save(); + logger.info(`created new guild record for ${interaction.guild.id}`); + } + + if (subcommand === "manager-role") { + await handleManagerRole(interaction, guild); + } else if (subcommand === "bug-channel") { + await handleBugChannel(interaction, guild); + } + } catch (error) { + logger.error("failed to execute config command:", error); + await interaction.reply({ + content: "❌ failed to update configuration. please try again later.", + flags: ["Ephemeral"], + }); + } +} + +async function handleManagerRole( + interaction: ChatInputCommandInteraction, + guild: GuildType, +) { + const action = interaction.options.getString("action", true); + const role = interaction.options.getRole("role"); + + if (action === "list") { + if (guild.manager_roles.length === 0) { + await interaction.reply({ + content: "📋 no manager roles configured.", + flags: ["Ephemeral"], + }); + return; + } + + const roleList = guild.manager_roles + .map((roleId: string) => `<@&${roleId}>`) + .join("\n"); + + await interaction.reply({ + content: `📋 **manager roles:**\n${roleList}`, + flags: ["Ephemeral"], + }); + return; + } + + if (!role) { + await interaction.reply({ + content: "❌ you must specify a role for this action.", + flags: ["Ephemeral"], + }); + return; + } + + if (action === "add") { + if (guild.manager_roles.includes(role.id)) { + await interaction.reply({ + content: `❌ ${role} is already a manager role.`, + flags: ["Ephemeral"], + }); + return; + } + + guild.manager_roles.push(role.id); + await guild.save(); + + await interaction.reply({ + content: `✅ added ${role} as a manager role.`, + flags: ["Ephemeral"], + }); + } else if (action === "remove") { + const index = guild.manager_roles.indexOf(role.id); + if (index === -1) { + await interaction.reply({ + content: `❌ ${role} is not a manager role.`, + flags: ["Ephemeral"], + }); + return; + } + + guild.manager_roles.splice(index, 1); + await guild.save(); + + await interaction.reply({ + content: `✅ removed ${role} from manager roles.`, + flags: ["Ephemeral"], + }); + } +} + +async function handleBugChannel( + interaction: ChatInputCommandInteraction, + guild: GuildType, +) { + const channel = interaction.options.getChannel("channel", true); + + if (channel.type !== ChannelType.GuildText) { + await interaction.reply({ + content: "❌ bug channel must be a text channel.", + flags: ["Ephemeral"], + }); + return; + } + + guild.bug_channel = channel.id; + await guild.save(); + + await interaction.reply({ + content: `✅ set bug reports channel to ${channel}.`, + flags: ["Ephemeral"], + }); +} + +export default { + data: commandData, + execute, +};
A
src/utils/permissions.ts
@@ -0,0 +1,25 @@
+import type { GuildMember } from "discord.js"; +import { GuildModel } from "../database/schemas.ts"; + +export async function hasManagerPermissions( + member: GuildMember, +): Promise<boolean> { + if ( + member.permissions.has("ManageGuild") || + member.guild.ownerId === member.id + ) { + return true; + } + + const guild = await GuildModel.findOne({ + guild_id: member.guild.id, + }); + + if (!guild || guild.manager_roles.length === 0) { + return false; + } + + return member.roles.cache.some((role) => + guild.manager_roles.includes(role.id), + ); +}