From 7bf19608e9e622788fdb61ccbc388aee5519b37c Mon Sep 17 00:00:00 2001 From: onnoyt Date: Sun, 31 May 2026 13:16:06 +0200 Subject: [PATCH 1/9] Update docker-compose.yml From 6cb1e7eb5863842cdc242b8ee3087d9e8cec8c56 Mon Sep 17 00:00:00 2001 From: onnoyt Date: Sun, 31 May 2026 20:15:27 +0200 Subject: [PATCH 2/9] Update bot.js --- src/config/bot.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/config/bot.js b/src/config/bot.js index 36e588cd4..ca33fa6a8 100644 --- a/src/config/bot.js +++ b/src/config/bot.js @@ -25,7 +25,7 @@ export const botConfig = { activities: [ { // Text users will see (example: "Playing /help | Titan Bot"). - name: "Made with ❤️", + name: "MOfficiële Pixel Lounge bot", // Activity type number (0 = Playing). type: 0, }, From 990f8d5d886750a0acddd9214d0604d0af0a3648 Mon Sep 17 00:00:00 2001 From: onnoyt Date: Sun, 31 May 2026 20:18:08 +0200 Subject: [PATCH 3/9] Update bot.js --- src/config/bot.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/config/bot.js b/src/config/bot.js index ca33fa6a8..2786541c2 100644 --- a/src/config/bot.js +++ b/src/config/bot.js @@ -25,7 +25,7 @@ export const botConfig = { activities: [ { // Text users will see (example: "Playing /help | Titan Bot"). - name: "MOfficiële Pixel Lounge bot", + name: "Officiële Pixel Lounge bot","Moderation, Minigames, Tickets & meer", // Activity type number (0 = Playing). type: 0, }, From c48fbef85f4f6f2984b1ea271c7735210e3fda47 Mon Sep 17 00:00:00 2001 From: onnoyt Date: Sun, 31 May 2026 20:27:56 +0200 Subject: [PATCH 4/9] Update bot.js --- src/config/bot.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/config/bot.js b/src/config/bot.js index 2786541c2..26f0f98bb 100644 --- a/src/config/bot.js +++ b/src/config/bot.js @@ -25,9 +25,11 @@ export const botConfig = { activities: [ { // Text users will see (example: "Playing /help | Titan Bot"). - name: "Officiële Pixel Lounge bot","Moderation, Minigames, Tickets & meer", + name: "Officiële Pixel Lounge bot", // Activity type number (0 = Playing). type: 0, + "Moderation, Minigames, Tickets & meer", + type: 1 }, ], }, From 72269eba648fe2d9399fa55a9d74fd8b13774abb Mon Sep 17 00:00:00 2001 From: onnoyt Date: Sun, 31 May 2026 20:30:47 +0200 Subject: [PATCH 5/9] Update bot.js --- src/config/bot.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/config/bot.js b/src/config/bot.js index 26f0f98bb..7847ff488 100644 --- a/src/config/bot.js +++ b/src/config/bot.js @@ -28,8 +28,6 @@ export const botConfig = { name: "Officiële Pixel Lounge bot", // Activity type number (0 = Playing). type: 0, - "Moderation, Minigames, Tickets & meer", - type: 1 }, ], }, From ab41cd930cdc5e9d9f637ddd2c5cedcd35f5ff53 Mon Sep 17 00:00:00 2001 From: onnoyt Date: Sun, 31 May 2026 20:36:00 +0200 Subject: [PATCH 6/9] Update bot.js --- src/config/bot.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/config/bot.js b/src/config/bot.js index 7847ff488..f2ff63282 100644 --- a/src/config/bot.js +++ b/src/config/bot.js @@ -25,9 +25,9 @@ export const botConfig = { activities: [ { // Text users will see (example: "Playing /help | Titan Bot"). - name: "Officiële Pixel Lounge bot", + name: "Officiële Pixel Lounge bot | /help | Moderatie, Tickets, Minigames en meer |", // Activity type number (0 = Playing). - type: 0, + type: 4, }, ], }, From a9fec38d82d1db7f9cd5807d18d4ab74ae1fb62b Mon Sep 17 00:00:00 2001 From: onnoyt Date: Sun, 31 May 2026 20:55:19 +0200 Subject: [PATCH 7/9] Update ticket.js --- src/commands/Ticket/ticket.js | 360 +++------------------------------- 1 file changed, 31 insertions(+), 329 deletions(-) diff --git a/src/commands/Ticket/ticket.js b/src/commands/Ticket/ticket.js index e3a1a5015..8d8053873 100644 --- a/src/commands/Ticket/ticket.js +++ b/src/commands/Ticket/ticket.js @@ -1,336 +1,38 @@ -import { getColor } from '../../config/bot.js'; -import { SlashCommandBuilder, PermissionFlagsBits, PermissionsBitField, ChannelType, ActionRowBuilder, ButtonBuilder, ButtonStyle, MessageFlags } from 'discord.js'; -import { createEmbed, errorEmbed, successEmbed, infoEmbed, warningEmbed } from '../../utils/embeds.js'; -import { getGuildConfig } from '../../services/guildConfig.js'; -import { InteractionHelper } from '../../utils/interactionHelper.js'; -import { logger } from '../../utils/logger.js'; -import { handleInteractionError } from '../../utils/errorHandler.js'; - -import ticketConfig from './modules/ticket_dashboard.js'; - -export default { - data: new SlashCommandBuilder() - .setName("ticket") - .setDescription("Manages the server's ticket system.") - .setDefaultMemberPermissions(PermissionFlagsBits.ManageChannels) - .addSubcommand((subcommand) => - subcommand - .setName("setup") - .setDescription( - "Sets up the ticket creation panel in a specified channel.", - ) - .addChannelOption((option) => - option -.setName("panel_channel") - .setDescription( - "The channel where the ticket panel will be sent.", - ) - .addChannelTypes(ChannelType.GuildText) - .setRequired(true), - ) - - .addStringOption((option) => - option - .setName("panel_message") - .setDescription( - "The main message/description for the ticket panel.", - ) - .setRequired(true), - ) - .addStringOption((option) => - option - .setName("button_label") - .setDescription( - "The label for the ticket creation button (default: Create Ticket)", - ) - .setRequired(false), - ) - .addChannelOption((option) => - option - .setName("category") - .setDescription( - "The category where new tickets will be created (optional).", - ) - .addChannelTypes(ChannelType.GuildCategory) - .setRequired(false), - ) - .addChannelOption((option) => - option - .setName("closed_category") - .setDescription( - "The category where closed tickets will be moved (optional).", - ) - .addChannelTypes(ChannelType.GuildCategory) - .setRequired(false), - ) - .addRoleOption((option) => - option - .setName("staff_role") - .setDescription( - "The role that can access tickets (optional).", - ) - .setRequired(false), - ) - .addIntegerOption((option) => - option - .setName("max_tickets_per_user") - .setDescription("Maximum number of tickets a user can create (default: 3)") - .setMinValue(1) - .setMaxValue(10) - .setRequired(false), - ) - .addBooleanOption((option) => - option - .setName("dm_on_close") - .setDescription("Send DM to user when their ticket is closed (default: true)") - .setRequired(false), - ), +i.addSubcommand((subcommand) => + subcommand + .setName("setup") + .setDescription("Sets up the ticket creation panel.") + + .addChannelOption((option) => + option.setName("panel_channel") + .setDescription("Channel for the panel") + .addChannelTypes(ChannelType.GuildText) + .setRequired(true) ) - .addSubcommand((subcommand) => - subcommand - .setName("dashboard") - .setDescription("Open the interactive ticket system dashboard"), - ), - category: "ticket", - - async execute(interaction, config, client) { - try { - - const deferred = await InteractionHelper.safeDefer(interaction, { flags: MessageFlags.Ephemeral }); - if (!deferred) { - return; - } - - if ( - !interaction.member.permissions.has( - PermissionFlagsBits.ManageChannels, - ) - ) { - logger.warn('Ticket command permission denied', { - userId: interaction.user.id, - guildId: interaction.guildId, - commandName: 'ticket' - }); - return await InteractionHelper.safeEditReply(interaction, { - embeds: [ - errorEmbed( - "Permission Denied", - "You need the `Manage Channels` permission for this action.", - ), - ], - }); - } - - const subcommand = interaction.options.getSubcommand(); - - if (subcommand === "dashboard") { - return ticketConfig.execute(interaction, config, client); - } - - if (subcommand === "setup") { - const existingConfig = await getGuildConfig(client, interaction.guildId); - if (existingConfig?.ticketPanelChannelId) { - return await InteractionHelper.safeEditReply(interaction, { - embeds: [ - errorEmbed( - 'Ticket System Already Active', - `This server already has a ticket system set up (panel in <#${existingConfig.ticketPanelChannelId}>).\n\nOnly one ticket system is supported per server. Use \`/ticket dashboard\` to edit or update the existing setup, or select **Delete System** from the dashboard to remove it and start fresh.`, - ), - ], - }); - } - - const panelChannel = - interaction.options.getChannel("panel_channel"); - const categoryChannel = interaction.options.getChannel("category"); - const closedCategoryChannel = interaction.options.getChannel("closed_category"); - const staffRole = interaction.options.getRole("staff_role"); -const panelMessage = interaction.options.getString("panel_message") || "Click the button below to create a support ticket."; - const buttonLabel = - interaction.options.getString("button_label") || -"Create Ticket"; - const maxTicketsPerUser = interaction.options.getInteger("max_tickets_per_user") || 3; -const dmOnClose = interaction.options.getBoolean("dm_on_close") !== false; - const setupEmbed = createEmbed({ - title: "🎫 Support Tickets", -description: panelMessage, - color: getColor('info') - }); - - const ticketButton = new ActionRowBuilder().addComponents( - new ButtonBuilder() - .setCustomId("create_ticket") -.setLabel(buttonLabel) - .setStyle(ButtonStyle.Primary) - .setEmoji("📩"), - ); - - try { - await panelChannel.send({ - embeds: [setupEmbed], - components: [ticketButton], - }); - - if (client.db && interaction.guildId) { - const currentConfig = existingConfig; - currentConfig.ticketCategoryId = categoryChannel ? categoryChannel.id : null; - currentConfig.ticketClosedCategoryId = closedCategoryChannel ? closedCategoryChannel.id : null; - currentConfig.ticketStaffRoleId = staffRole ? staffRole.id : null; - currentConfig.ticketPanelChannelId = panelChannel.id; - currentConfig.ticketPanelMessage = panelMessage; - currentConfig.ticketButtonLabel = buttonLabel; - currentConfig.maxTicketsPerUser = maxTicketsPerUser; - currentConfig.dmOnClose = dmOnClose; - - const { getGuildConfigKey } = await import('../../utils/database.js'); - const configKey = getGuildConfigKey(interaction.guildId); - await client.db.set(configKey, currentConfig); - logger.info('Ticket configuration saved', { - guildId: interaction.guildId, - categoryId: categoryChannel?.id, - closedCategoryId: closedCategoryChannel?.id, - staffRoleId: staffRole?.id, - maxTickets: maxTicketsPerUser, - dmOnClose: dmOnClose - }); - } - - let successMessage = `The ticket creation panel has been sent to ${panelChannel}. `; - - if (categoryChannel) { - successMessage += `New tickets will be created in the **${categoryChannel.name}** category. `; - } else { - successMessage += 'New tickets will be created in a new "Tickets" category. '; - } - - if (closedCategoryChannel) { - successMessage += `Closed tickets will be moved to **${closedCategoryChannel.name}**. `; - } - - if (staffRole) { - successMessage += `**${staffRole.name}** role will have access to tickets. `; - } - - successMessage += `\n\n**Max Tickets Per User:** ${maxTicketsPerUser}\n**DM on Close:** ${dmOnClose ? 'Enabled' : 'Disabled'}`; - - await InteractionHelper.safeEditReply(interaction, { - embeds: [ - successEmbed( - "Ticket Panel Set Up", - successMessage, - ), - ], - }); - - logger.info('Ticket panel setup completed', { - userId: interaction.user.id, - userTag: interaction.user.tag, - guildId: interaction.guildId, - panelChannelId: panelChannel.id, - categoryId: categoryChannel?.id, - closedCategoryId: closedCategoryChannel?.id, - staffRoleId: staffRole?.id, - maxTickets: maxTicketsPerUser, - dmOnClose: dmOnClose, - commandName: 'ticket_setup' - }); - - const logEmbed = createEmbed({ - title: "🔧 Ticket System Setup (Configuration Log)", - description: `The ticket panel was set up in ${panelChannel} by ${interaction.user}.`, - color: getColor('warning') - }) - .addFields( - { - name: "Panel Channel", - value: panelChannel.toString(), - inline: true, - }, - { - name: "Ticket Category", - value: categoryChannel - ? categoryChannel.toString() - : "None specified.", - inline: true, - }, - { - name: "Closed Category", - value: closedCategoryChannel - ? closedCategoryChannel.toString() - : "None specified.", - inline: true, - }, - { - name: "Staff Role", - value: staffRole - ? staffRole.toString() - : "None specified.", - inline: true, - }, - { - name: "Max Tickets Per User", - value: maxTicketsPerUser.toString(), - inline: true, - }, - { - name: "DM on Close", - value: dmOnClose ? 'Enabled' : 'Disabled', - inline: true, - }, - { - name: "Moderator", - value: `${interaction.user.tag} (${interaction.user.id})`, - inline: false, - }, - ); + .addStringOption((option) => + option.setName("panel_message") + .setDescription("Panel message") + .setRequired(true) + ) + // BUTTON 1 (REQUIRED) + .addStringOption(o => o.setName("button_1_label").setDescription("Button 1 label").setRequired(true)) + .addChannelOption(o => o.setName("button_1_category").setDescription("Button 1 category").addChannelTypes(ChannelType.GuildCategory).setRequired(true)) - } catch (error) { - logger.error('Ticket setup error', { - error: error.message, - stack: error.stack, - userId: interaction.user.id, - guildId: interaction.guildId, - commandName: 'ticket_setup' - }); - if (interaction.deferred || interaction.replied) { - await InteractionHelper.safeEditReply(interaction, { - embeds: [ - errorEmbed( - "Setup Failed", - "Could not send the ticket panel or save configuration. Check the bot's permissions (especially the ability to send messages in the target channel) and database connection.", - ), - ], - }).catch(err => { - logger.error('Failed to send error reply', { - error: err.message, - guildId: interaction.guildId - }); - }); - } else { - await handleInteractionError(interaction, error, { - commandName: 'ticket_setup', - source: 'ticket_setup_command' - }); - } - } - } - } catch (error) { - logger.error('Error executing ticket command', { - error: error.message, - stack: error.stack, - userId: interaction.user.id, - guildId: interaction.guildId, - commandName: 'ticket' - }); - await handleInteractionError(interaction, error, { - commandName: 'ticket', - source: 'ticket_command_main' - }); - } - } -}; + // BUTTON 2 + .addStringOption(o => o.setName("button_2_label").setDescription("Button 2 label")) + .addChannelOption(o => o.setName("button_2_category").setDescription("Button 2 category").addChannelTypes(ChannelType.GuildCategory)) + // BUTTON 3 + .addStringOption(o => o.setName("button_3_label").setDescription("Button 3 label")) + .addChannelOption(o => o.setName("button_3_category").setDescription("Button 3 category").addChannelTypes(ChannelType.GuildCategory)) + // BUTTON 4 + .addStringOption(o => o.setName("button_4_label").setDescription("Button 4 label")) + .addChannelOption(o => o.setName("button_4_category").setDescription("Button 4 category").addChannelTypes(ChannelType.GuildCategory)) + // BUTTON 5 + .addStringOption(o => o.setName("button_5_label").setDescription("Button 5 label")) + .addChannelOption(o => o.setName("button_5_category").setDescription("Button 5 category").addChannelTypes(ChannelType.GuildCategory)) +) From 1465767e384f3cca6dd3a2ccc1a4ea540362d183 Mon Sep 17 00:00:00 2001 From: onnoyt Date: Sun, 31 May 2026 21:02:54 +0200 Subject: [PATCH 8/9] Update ticket.js --- src/commands/Ticket/ticket.js | 187 ++++++++++++++++++++++++++++------ 1 file changed, 156 insertions(+), 31 deletions(-) diff --git a/src/commands/Ticket/ticket.js b/src/commands/Ticket/ticket.js index 8d8053873..9fcb0ee92 100644 --- a/src/commands/Ticket/ticket.js +++ b/src/commands/Ticket/ticket.js @@ -1,38 +1,163 @@ -i.addSubcommand((subcommand) => - subcommand - .setName("setup") - .setDescription("Sets up the ticket creation panel.") - - .addChannelOption((option) => - option.setName("panel_channel") - .setDescription("Channel for the panel") - .addChannelTypes(ChannelType.GuildText) - .setRequired(true) - ) +```js +import { getColor } from '../../config/bot.js'; +import { + SlashCommandBuilder, + PermissionFlagsBits, + ChannelType, + ActionRowBuilder, + ButtonBuilder, + ButtonStyle, + MessageFlags +} from 'discord.js'; + +import { createEmbed, errorEmbed, successEmbed } from '../../utils/embeds.js'; +import { getGuildConfig } from '../../services/guildConfig.js'; +import { InteractionHelper } from '../../utils/interactionHelper.js'; +import { logger } from '../../utils/logger.js'; +import { handleInteractionError } from '../../utils/errorHandler.js'; + +import ticketConfig from './modules/ticket_dashboard.js'; + +export default { + data: new SlashCommandBuilder() + .setName("ticket") + .setDescription("Manages the server's ticket system.") + .setDefaultMemberPermissions(PermissionFlagsBits.ManageChannels) + + .addSubcommand((subcommand) => + subcommand + .setName("setup") + .setDescription("Sets up the ticket panel") + + .addChannelOption(option => + option.setName("panel_channel") + .setDescription("Channel for the panel") + .addChannelTypes(ChannelType.GuildText) + .setRequired(true) + ) + + .addStringOption(option => + option.setName("panel_message") + .setDescription("Message for the panel") + .setRequired(true) + ) - .addStringOption((option) => - option.setName("panel_message") - .setDescription("Panel message") - .setRequired(true) + .addStringOption(option => + option.setName("button_label") + .setDescription("Button label") + .setRequired(false) + ) + + .addChannelOption(option => + option.setName("category") + .setDescription("Ticket category") + .addChannelTypes(ChannelType.GuildCategory) + .setRequired(false) + ) ) - // BUTTON 1 (REQUIRED) - .addStringOption(o => o.setName("button_1_label").setDescription("Button 1 label").setRequired(true)) - .addChannelOption(o => o.setName("button_1_category").setDescription("Button 1 category").addChannelTypes(ChannelType.GuildCategory).setRequired(true)) + .addSubcommand((subcommand) => + subcommand + .setName("dashboard") + .setDescription("Open dashboard") + ), + + category: "ticket", + + async execute(interaction, config, client) { + try { + const deferred = await InteractionHelper.safeDefer(interaction, { flags: MessageFlags.Ephemeral }); + if (!deferred) return; + + if (!interaction.member.permissions.has(PermissionFlagsBits.ManageChannels)) { + return await InteractionHelper.safeEditReply(interaction, { + embeds: [ + errorEmbed( + "Permission Denied", + "You need Manage Channels permission." + ) + ] + }); + } + + const subcommand = interaction.options.getSubcommand(); + + if (subcommand === "dashboard") { + return ticketConfig.execute(interaction, config, client); + } + + if (subcommand === "setup") { + + const existingConfig = await getGuildConfig(client, interaction.guildId); + + if (existingConfig?.ticketPanelChannelId) { + return await InteractionHelper.safeEditReply(interaction, { + embeds: [ + errorEmbed( + "Already Setup", + `Panel already exists in <#${existingConfig.ticketPanelChannelId}>` + ) + ] + }); + } + + const panelChannel = interaction.options.getChannel("panel_channel"); + const categoryChannel = interaction.options.getChannel("category"); + + const panelMessage = interaction.options.getString("panel_message"); + const buttonLabel = interaction.options.getString("button_label") || "Create Ticket"; + + const embed = createEmbed({ + title: "🎫 Support Tickets", + description: panelMessage, + color: getColor('info') + }); + + const row = new ActionRowBuilder().addComponents( + new ButtonBuilder() + .setCustomId("create_ticket") + .setLabel(buttonLabel) + .setStyle(ButtonStyle.Primary) + .setEmoji("📩") + ); + + await panelChannel.send({ + embeds: [embed], + components: [row] + }); + + if (client.db) { + const currentConfig = existingConfig || {}; + + currentConfig.ticketCategoryId = categoryChannel ? categoryChannel.id : null; + currentConfig.ticketPanelChannelId = panelChannel.id; + currentConfig.ticketPanelMessage = panelMessage; + + const { getGuildConfigKey } = await import('../../utils/database.js'); + const key = getGuildConfigKey(interaction.guildId); + + await client.db.set(key, currentConfig); + } - // BUTTON 2 - .addStringOption(o => o.setName("button_2_label").setDescription("Button 2 label")) - .addChannelOption(o => o.setName("button_2_category").setDescription("Button 2 category").addChannelTypes(ChannelType.GuildCategory)) + return await InteractionHelper.safeEditReply(interaction, { + embeds: [ + successEmbed( + "Setup Complete", + `Panel sent to ${panelChannel}` + ) + ] + }); + } - // BUTTON 3 - .addStringOption(o => o.setName("button_3_label").setDescription("Button 3 label")) - .addChannelOption(o => o.setName("button_3_category").setDescription("Button 3 category").addChannelTypes(ChannelType.GuildCategory)) + } catch (error) { + logger.error('Ticket command error', { + error: error.message, + stack: error.stack + }); - // BUTTON 4 - .addStringOption(o => o.setName("button_4_label").setDescription("Button 4 label")) - .addChannelOption(o => o.setName("button_4_category").setDescription("Button 4 category").addChannelTypes(ChannelType.GuildCategory)) + await handleInteractionError(interaction, error); + } + } +}; +``` - // BUTTON 5 - .addStringOption(o => o.setName("button_5_label").setDescription("Button 5 label")) - .addChannelOption(o => o.setName("button_5_category").setDescription("Button 5 category").addChannelTypes(ChannelType.GuildCategory)) -) From f40e9655bb1ec8bd6b6d53384c59284d59248545 Mon Sep 17 00:00:00 2001 From: Railway Agent Date: Tue, 2 Jun 2026 10:30:52 +0000 Subject: [PATCH 9/9] Fix moderation commands: warn, timeout, kick, ban - proper error handling and defer --- src/commands/Moderation/ban.js | 78 ++++++++--- src/commands/Moderation/kick.js | 213 +++++++++++++++-------------- src/commands/Moderation/timeout.js | 38 +++-- src/commands/Moderation/warn.js | 123 ++++++++++------- 4 files changed, 261 insertions(+), 191 deletions(-) diff --git a/src/commands/Moderation/ban.js b/src/commands/Moderation/ban.js index 657359233..884081b22 100644 --- a/src/commands/Moderation/ban.js +++ b/src/commands/Moderation/ban.js @@ -1,10 +1,10 @@ -import { SlashCommandBuilder, PermissionFlagsBits, PermissionsBitField, ChannelType } from 'discord.js'; -import { createEmbed, errorEmbed, successEmbed, infoEmbed, warningEmbed } from '../../utils/embeds.js'; +import { SlashCommandBuilder, PermissionFlagsBits } from 'discord.js'; +import { errorEmbed, successEmbed } from '../../utils/embeds.js'; import { logModerationAction } from '../../utils/moderation.js'; import { logger } from '../../utils/logger.js'; import { InteractionHelper } from '../../utils/interactionHelper.js'; -import { ModerationService } from '../../services/moderationService.js'; import { handleInteractionError } from '../../utils/errorHandler.js'; + export default { data: new SlashCommandBuilder() .setName("ban") @@ -18,43 +18,85 @@ export default { .addStringOption((option) => option.setName("reason").setDescription("Reason for the ban"), ) -.setDefaultMemberPermissions(PermissionFlagsBits.BanMembers), + .setDefaultMemberPermissions(PermissionFlagsBits.BanMembers), category: "moderation", async execute(interaction, config, client) { + const deferSuccess = await InteractionHelper.safeDefer(interaction); + if (!deferSuccess) { + logger.warn(`Ban interaction defer failed`, { + userId: interaction.user.id, + guildId: interaction.guildId, + commandName: 'ban' + }); + return; + } + try { - const user = interaction.options.getUser("target"); + if (!interaction.member.permissions.has(PermissionFlagsBits.BanMembers)) { + throw new Error("You do not have permission to ban members."); + } + + const targetUser = interaction.options.getUser("target"); const reason = interaction.options.getString("reason") || "No reason provided"; - if (user.id === interaction.user.id) { + if (!targetUser) { + throw new Error("Could not find the target user."); + } + + if (targetUser.id === interaction.user.id) { throw new Error("You cannot ban yourself."); } - if (user.id === client.user.id) { + + if (targetUser.id === client.user.id) { throw new Error("You cannot ban the bot."); } - - const result = await ModerationService.banUser({ + // Ban the user + await interaction.guild.bans.create(targetUser.id, { reason }); + + const caseId = await logModerationAction({ + client, guild: interaction.guild, - user, - moderator: interaction.member, - reason + event: { + action: "Member Banned", + target: `${targetUser.tag} (${targetUser.id})`, + executor: `${interaction.user.tag} (${interaction.user.id})`, + reason: reason, + metadata: { + userId: targetUser.id, + moderatorId: interaction.user.id, + } + } }); - await InteractionHelper.universalReply(interaction, { + await InteractionHelper.safeEditReply(interaction, { embeds: [ successEmbed( - `🚫 **Banned** ${user.tag}`, - `**Reason:** ${reason}\n**Case ID:** #${result.caseId}`, + `🚫 **Banned** ${targetUser.tag}`, + `**Reason:** ${reason}\n**Case ID:** #${caseId}`, ), ], }); + + logger.info('User banned', { + guildId: interaction.guildId, + userId: targetUser.id, + moderatorId: interaction.user.id, + reason: reason + }); + } catch (error) { logger.error('Ban command error:', error); - await handleInteractionError(interaction, error, { subtype: 'ban_failed' }); + await InteractionHelper.safeEditReply(interaction, { + embeds: [ + errorEmbed( + "Ban Failed", + error.message || "An unexpected error occurred during the ban action. Please check my role permissions." + ), + ], + }); } }, }; - - diff --git a/src/commands/Moderation/kick.js b/src/commands/Moderation/kick.js index d1902f906..6d7926516 100644 --- a/src/commands/Moderation/kick.js +++ b/src/commands/Moderation/kick.js @@ -1,125 +1,126 @@ -import { SlashCommandBuilder, PermissionFlagsBits, PermissionsBitField, ChannelType } from 'discord.js'; -import { createEmbed, errorEmbed, successEmbed, infoEmbed, warningEmbed } from '../../utils/embeds.js'; +import { SlashCommandBuilder, PermissionFlagsBits } from 'discord.js'; +import { errorEmbed, successEmbed } from '../../utils/embeds.js'; import { logModerationAction } from '../../utils/moderation.js'; import { logger } from '../../utils/logger.js'; -import { InteractionHelper } from '../../utils/interactionHelper.js'; import { TitanBotError, ErrorTypes } from '../../utils/errorHandler.js'; +import { InteractionHelper } from '../../utils/interactionHelper.js'; export default { data: new SlashCommandBuilder() - .setName("kick") - .setDescription("Kick a user from the server") - .addUserOption((option) => - option - .setName("target") - .setDescription("The user to kick") - .setRequired(true), - ) - .addStringOption((option) => - option.setName("reason").setDescription("Reason for the kick"), - ) -.setDefaultMemberPermissions(PermissionFlagsBits.KickMembers), - category: "moderation", + .setName("kick") + .setDescription("Kick a user from the server") + .addUserOption((option) => + option + .setName("target") + .setDescription("The user to kick") + .setRequired(true), + ) + .addStringOption((option) => + option.setName("reason").setDescription("Reason for the kick"), + ) + .setDefaultMemberPermissions(PermissionFlagsBits.KickMembers), + category: "moderation", - async execute(interaction, config, client) { - try { - - if (!interaction.member.permissions.has(PermissionFlagsBits.KickMembers)) { - throw new TitanBotError( - "User lacks permission", - ErrorTypes.PERMISSION, - "You do not have permission to kick members." - ); - } + async execute(interaction, config, client) { + const deferSuccess = await InteractionHelper.safeDefer(interaction); + if (!deferSuccess) { + logger.warn(`Kick interaction defer failed`, { + userId: interaction.user.id, + guildId: interaction.guildId, + commandName: 'kick' + }); + return; + } - const targetUser = interaction.options.getUser("target"); - const member = interaction.options.getMember("target"); - const reason = interaction.options.getString("reason") || "No reason provided"; + try { + if (!interaction.member.permissions.has(PermissionFlagsBits.KickMembers)) { + throw new TitanBotError( + "User lacks permission", + ErrorTypes.PERMISSION, + "You do not have permission to kick members." + ); + } - - if (targetUser.id === interaction.user.id) { - throw new TitanBotError( - "Cannot kick self", - ErrorTypes.VALIDATION, - "You cannot kick yourself." - ); - } + const targetUser = interaction.options.getUser("target"); + const member = interaction.options.getMember("target"); + const reason = interaction.options.getString("reason") || "No reason provided"; - - if (targetUser.id === client.user.id) { - throw new TitanBotError( - "Cannot kick bot", - ErrorTypes.VALIDATION, - "You cannot kick the bot." - ); - } + if (!targetUser) { + throw new TitanBotError( + "Target not found", + ErrorTypes.USER_INPUT, + "Could not find the target user." + ); + } - - if (!member) { - throw new TitanBotError( - "Target not found", - ErrorTypes.USER_INPUT, - "The target user is not currently in this server.", - { subtype: 'user_not_found' } - ); - } + if (targetUser.id === interaction.user.id) { + throw new TitanBotError( + "Cannot kick self", + ErrorTypes.VALIDATION, + "You cannot kick yourself." + ); + } - - if (interaction.member.roles.highest.position <= member.roles.highest.position) { - throw new TitanBotError( - "Cannot kick user", - ErrorTypes.PERMISSION, - "You cannot kick a user with an equal or higher role than you." - ); - } + if (targetUser.id === client.user.id) { + throw new TitanBotError( + "Cannot kick bot", + ErrorTypes.VALIDATION, + "You cannot kick the bot." + ); + } - - if (!member.kickable) { - throw new TitanBotError( - "Bot cannot kick", - ErrorTypes.PERMISSION, - "I cannot kick this user. Please check my role position relative to the target user." - ); - } + if (!member) { + throw new TitanBotError( + "Target not found", + ErrorTypes.USER_INPUT, + "The target user is not currently in this server." + ); + } - - await member.kick(reason); + if (!member.kickable) { + throw new TitanBotError( + "Cannot kick member", + ErrorTypes.PERMISSION, + "I cannot kick this user. They might have a higher role than me or you." + ); + } - - const caseId = await logModerationAction({ - client, - guild: interaction.guild, - event: { - action: "Member Kicked", - target: `${targetUser.tag} (${targetUser.id})`, - executor: `${interaction.user.tag} (${interaction.user.id})`, - reason, - metadata: { - userId: targetUser.id, - moderatorId: interaction.user.id - } - } - }); + await member.kick(reason); - - await InteractionHelper.universalReply(interaction, { - embeds: [ - successEmbed( - `👢 **Kicked** ${targetUser.tag}`, - `**Reason:** ${reason}\n**Case ID:** #${caseId}`, - ), - ], - }); - } catch (error) { - logger.error('Kick command error:', error); - const errorEmbed_default = errorEmbed( - "An unexpected error occurred while trying to kick the user.", - error.message || "Could not kick the user" - ); - await InteractionHelper.universalReply(interaction, { embeds: [errorEmbed_default] }); + const caseId = await logModerationAction({ + client, + guild: interaction.guild, + event: { + action: "Member Kicked", + target: `${targetUser.tag} (${targetUser.id})`, + executor: `${interaction.user.tag} (${interaction.user.id})`, + reason: reason, + metadata: { + userId: targetUser.id, + moderatorId: interaction.user.id, + } + } + }); + + await InteractionHelper.safeEditReply(interaction, { + embeds: [ + successEmbed( + `👢 **Kicked** ${targetUser.tag}`, + `**Reason:** ${reason}\n**Case ID:** #${caseId}`, + ), + ], + }); + } catch (error) { + logger.error('Kick command error:', error); + await InteractionHelper.safeEditReply(interaction, { + embeds: [ + errorEmbed( + "Kick Failed", + error.userMessage || "An unexpected error occurred during the kick action. Please check my role permissions." + ), + ], + }); + } } - } }; - - diff --git a/src/commands/Moderation/timeout.js b/src/commands/Moderation/timeout.js index 04f205a3f..f18160445 100644 --- a/src/commands/Moderation/timeout.js +++ b/src/commands/Moderation/timeout.js @@ -1,11 +1,10 @@ -import { SlashCommandBuilder, PermissionFlagsBits, PermissionsBitField, ChannelType } from 'discord.js'; -import { createEmbed, errorEmbed, successEmbed, infoEmbed, warningEmbed } from '../../utils/embeds.js'; +import { SlashCommandBuilder, PermissionFlagsBits } from 'discord.js'; +import { errorEmbed, successEmbed } from '../../utils/embeds.js'; import { logModerationAction } from '../../utils/moderation.js'; import { logger } from '../../utils/logger.js'; import { TitanBotError, ErrorTypes } from '../../utils/errorHandler.js'; - - import { InteractionHelper } from '../../utils/interactionHelper.js'; + const durationChoices = [ { name: "5 minutes", value: 5 }, { name: "10 minutes", value: 10 }, @@ -15,6 +14,7 @@ const durationChoices = [ { name: "1 day", value: 1440 }, { name: "1 week", value: 10080 }, ]; + export default { data: new SlashCommandBuilder() .setName("timeout") @@ -25,18 +25,17 @@ export default { .setDescription("User to timeout") .setRequired(true), ) - .addIntegerOption( - (option) => - option - .setName("duration") - .setDescription("Duration of the timeout") - .setRequired(true) -.addChoices(...durationChoices), + .addIntegerOption((option) => + option + .setName("duration") + .setDescription("Duration of the timeout") + .setRequired(true) + .addChoices(...durationChoices), ) .addStringOption((option) => option.setName("reason").setDescription("Reason for the timeout"), ) -.setDefaultMemberPermissions(PermissionFlagsBits.ModerateMembers), + .setDefaultMemberPermissions(PermissionFlagsBits.ModerateMembers), category: "moderation", async execute(interaction, config, client) { @@ -64,6 +63,14 @@ export default { const durationMinutes = interaction.options.getInteger("duration"); const reason = interaction.options.getString("reason") || "No reason provided"; + if (!targetUser) { + throw new TitanBotError( + "Target not found", + ErrorTypes.USER_INPUT, + "Could not find the target user." + ); + } + if (targetUser.id === interaction.user.id) { throw new TitanBotError( "Cannot timeout self", @@ -71,6 +78,7 @@ export default { "You cannot timeout yourself." ); } + if (targetUser.id === client.user.id) { throw new TitanBotError( "Cannot timeout bot", @@ -78,6 +86,7 @@ export default { "You cannot timeout the bot." ); } + if (!member) { throw new TitanBotError( "Target not found", @@ -132,7 +141,8 @@ export default { await InteractionHelper.safeEditReply(interaction, { embeds: [ errorEmbed( - error.userMessage || "An unexpected error occurred during the timeout action. Please check my role permissions.", + "Timeout Failed", + error.userMessage || "An unexpected error occurred during the timeout action. Please check my role permissions." ), ], }); @@ -140,5 +150,3 @@ export default { } }; - - diff --git a/src/commands/Moderation/warn.js b/src/commands/Moderation/warn.js index 571214ece..d797ec150 100644 --- a/src/commands/Moderation/warn.js +++ b/src/commands/Moderation/warn.js @@ -1,10 +1,11 @@ -import { SlashCommandBuilder, PermissionFlagsBits, PermissionsBitField, ChannelType, MessageFlags } from 'discord.js'; -import { createEmbed, errorEmbed, successEmbed, infoEmbed, warningEmbed } from '../../utils/embeds.js'; +import { SlashCommandBuilder, PermissionFlagsBits } from 'discord.js'; +import { errorEmbed, successEmbed } from '../../utils/embeds.js'; import { logModerationAction } from '../../utils/moderation.js'; import { logger } from '../../utils/logger.js'; import { WarningService } from '../../services/warningService.js'; import { handleInteractionError } from '../../utils/errorHandler.js'; import { InteractionHelper } from '../../utils/interactionHelper.js'; + export default { data: new SlashCommandBuilder() .setName("warn") @@ -36,67 +37,85 @@ export default { } try { - if (!interaction.member.permissions.has(PermissionFlagsBits.ModerateMembers)) { - throw new Error("You need the `Moderate Members` permission to issue warnings."); - } + if (!interaction.member.permissions.has(PermissionFlagsBits.ModerateMembers)) { + throw new Error("You need the `Moderate Members` permission to issue warnings."); + } - const target = interaction.options.getUser("target"); - const member = interaction.options.getMember("target"); - const reason = interaction.options.getString("reason"); - const moderator = interaction.user; - const guildId = interaction.guildId; + const target = interaction.options.getUser("target"); + const member = interaction.options.getMember("target"); + const reason = interaction.options.getString("reason"); + const moderator = interaction.user; + const guildId = interaction.guildId; - if (!member) { - throw new Error("The target user is not currently in this server."); - } + if (!target) { + throw new Error("Could not find the target user."); + } - - const result = await WarningService.addWarning({ - guildId, - userId: target.id, - moderatorId: moderator.id, - reason, - timestamp: Date.now() - }); + if (!member) { + throw new Error("The target user is not currently in this server."); + } - if (!result.success) { - throw new Error("Failed to store warning in database"); - } + // Add warning + const result = await WarningService.addWarning({ + guildId, + userId: target.id, + moderatorId: moderator.id, + reason, + timestamp: Date.now() + }); + + if (!result.success) { + throw new Error("Failed to store warning in database"); + } - const totalWarns = result.totalCount; + const totalWarns = result.totalCount; - await logModerationAction({ - client, - guild: interaction.guild, - event: { - action: "User Warned", - target: `${target.tag} (${target.id})`, - executor: `${moderator.tag} (${moderator.id})`, - reason, - metadata: { - userId: target.id, - moderatorId: moderator.id, - totalWarns, - warningNumber: totalWarns, - warningId: result.id - } + await logModerationAction({ + client, + guild: interaction.guild, + event: { + action: "User Warned", + target: `${target.tag} (${target.id})`, + executor: `${moderator.tag} (${moderator.id})`, + reason, + metadata: { + userId: target.id, + moderatorId: moderator.id, + totalWarns, + warningNumber: totalWarns, + warningId: result.id } - }); + } + }); + + await InteractionHelper.safeEditReply(interaction, { + embeds: [ + successEmbed( + `⚠️ **Warned** ${target.tag}`, + `**Reason:** ${reason}\n**Total Warns:** ${totalWarns}`, + ), + ], + }); + + logger.info('User warned', { + guildId: interaction.guildId, + userId: target.id, + moderatorId: moderator.id, + reason: reason, + totalWarns: totalWarns + }); - await InteractionHelper.safeEditReply(interaction, { - embeds: [ - successEmbed( - `⚠️ **Warned** ${target.tag}`, - `**Reason:** ${reason}\n**Total Warns:** ${totalWarns}`, - ), - ], - }); } catch (error) { logger.error('Warn command error:', error); - await handleInteractionError(interaction, error, { subtype: 'warn_failed' }); + await InteractionHelper.safeEditReply(interaction, { + embeds: [ + errorEmbed( + "Warning Failed", + error.message || "An unexpected error occurred while issuing the warning." + ), + ], + }); } } }; - -