From 4d17995a68965c4abcc8584450724fb4d427f632 Mon Sep 17 00:00:00 2001 From: Sing4Hope Date: Wed, 3 Jun 2026 14:05:17 +0200 Subject: [PATCH 1/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..136546383 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: "© Thijs's Bot shop", // Activity type number (0 = Playing). type: 0, }, From 038ff8a703dbf309012d413c86f3138ce115c76e Mon Sep 17 00:00:00 2001 From: Sing4Hope Date: Wed, 3 Jun 2026 14:40:07 +0200 Subject: [PATCH 2/9] Update messageCreate.js --- src/events/messageCreate.js | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/src/events/messageCreate.js b/src/events/messageCreate.js index b7f9841dc..f714ca04d 100644 --- a/src/events/messageCreate.js +++ b/src/events/messageCreate.js @@ -16,7 +16,28 @@ export default { name: Events.MessageCreate, async execute(message, client) { try { - + // MODMAIL DM HANDLER +if (message.author.bot) return; + +if (!message.guild) { + + const guild = client.guilds.cache.get(client.config.bot.guildId); + if (!guild) return; + + const existingTicket = guild.channels.cache.find( + c => c.topic === message.author.id + ); + + if (existingTicket) { + await existingTicket.send( + `👤 ${message.author.tag}: ${message.content}` + ); + return; + } + + // Hier stuur je de bevestigingsknoppen + return; +} if (message.author.bot || !message.guild) return; await handleLeveling(message, client); From fd03080c2857b3744e6d6a9b2b0707572863e4c9 Mon Sep 17 00:00:00 2001 From: Sing4Hope Date: Wed, 3 Jun 2026 14:42:04 +0200 Subject: [PATCH 3/9] Implement modmail_yes command to create tickets --- src/interactions/buttons/modmail_yes.js | 62 +++++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 src/interactions/buttons/modmail_yes.js diff --git a/src/interactions/buttons/modmail_yes.js b/src/interactions/buttons/modmail_yes.js new file mode 100644 index 000000000..6ef7886f3 --- /dev/null +++ b/src/interactions/buttons/modmail_yes.js @@ -0,0 +1,62 @@ +import { + ChannelType, + PermissionFlagsBits +} from 'discord.js'; + +const CATEGORY_ID = '1498650880604639272'; +const STAFF_ROLE_ID = '1498650876473249818'; + +export default { + name: 'modmail_yes', + + async execute(interaction, client, args) { + + const userId = args[0]; + + const guild = client.guilds.cache.get( + client.config.bot.guildId + ); + + const existing = guild.channels.cache.find( + c => c.topic === userId + ); + + if (existing) { + return interaction.reply({ + content: 'Je hebt al een open ticket.', + ephemeral: true + }); + } + + const channel = await guild.channels.create({ + name: `ticket-${userId}`, + type: ChannelType.GuildText, + parent: CATEGORY_ID, + topic: userId, + + permissionOverwrites: [ + { + id: guild.id, + deny: [PermissionFlagsBits.ViewChannel] + }, + { + id: STAFF_ROLE_ID, + allow: [ + PermissionFlagsBits.ViewChannel, + PermissionFlagsBits.SendMessages + ] + } + ] + }); + + await channel.send( + `📩 Nieuw ModMail ticket van <@${userId}>` + ); + + await interaction.update({ + content: '✅ Ticket geopend.', + embeds: [], + components: [] + }); + } +}; From 37f692af86905066ae40b66c923ec5e78656dee3 Mon Sep 17 00:00:00 2001 From: Sing4Hope Date: Wed, 3 Jun 2026 14:42:51 +0200 Subject: [PATCH 4/9] Create modmail_no.js --- src/interactions/buttons/modmail_no.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 src/interactions/buttons/modmail_no.js diff --git a/src/interactions/buttons/modmail_no.js b/src/interactions/buttons/modmail_no.js new file mode 100644 index 000000000..9fc69f5c0 --- /dev/null +++ b/src/interactions/buttons/modmail_no.js @@ -0,0 +1,12 @@ +export default { + name: 'modmail_no', + + async execute(interaction) { + + await interaction.update({ + content: '❌ Ticket geannuleerd.', + embeds: [], + components: [] + }); + } +}; From 74673b453775577bca37ef6c9b8d20a52d573e10 Mon Sep 17 00:00:00 2001 From: Sing4Hope Date: Wed, 3 Jun 2026 14:44:35 +0200 Subject: [PATCH 5/9] Update messageCreate.js --- src/events/messageCreate.js | 53 +++++++++++++++++++++---------------- 1 file changed, 30 insertions(+), 23 deletions(-) diff --git a/src/events/messageCreate.js b/src/events/messageCreate.js index f714ca04d..4c4a826a6 100644 --- a/src/events/messageCreate.js +++ b/src/events/messageCreate.js @@ -1,9 +1,32 @@ +async execute(message, client) { + try { + + // ModMail DM's + if (message.author.bot) return; + + if (!message.guild) { + // modmail code hier + return; + } + + // normale serverberichten + await handleLeveling(message, client); + } catch (error) { + logger.error('Error in messageCreate event:', error); + } +} -import { Events } from 'discord.js'; +import { + Events, + ActionRowBuilder, + ButtonBuilder, + ButtonStyle, + EmbedBuilder +} from 'discord.js'; import { logger } from '../utils/logger.js'; import { getLevelingConfig, getUserLevelData } from '../services/leveling.js'; import { addXp } from '../services/xpSystem.js'; @@ -16,28 +39,12 @@ export default { name: Events.MessageCreate, async execute(message, client) { try { - // MODMAIL DM HANDLER -if (message.author.bot) return; - -if (!message.guild) { - - const guild = client.guilds.cache.get(client.config.bot.guildId); - if (!guild) return; - - const existingTicket = guild.channels.cache.find( - c => c.topic === message.author.id - ); - - if (existingTicket) { - await existingTicket.send( - `👤 ${message.author.tag}: ${message.content}` - ); - return; - } - - // Hier stuur je de bevestigingsknoppen - return; -} + import { + ActionRowBuilder, + ButtonBuilder, + ButtonStyle, + EmbedBuilder +} from 'discord.js'; if (message.author.bot || !message.guild) return; await handleLeveling(message, client); From 4f26c52964450edfa502e57504df5f02f3ce8d68 Mon Sep 17 00:00:00 2001 From: Sing4Hope Date: Wed, 3 Jun 2026 14:45:33 +0200 Subject: [PATCH 6/9] Update messageCreate.js --- src/events/messageCreate.js | 73 +++++++++++++++++++++++++++++-------- 1 file changed, 58 insertions(+), 15 deletions(-) diff --git a/src/events/messageCreate.js b/src/events/messageCreate.js index 4c4a826a6..9c3a50c9f 100644 --- a/src/events/messageCreate.js +++ b/src/events/messageCreate.js @@ -1,24 +1,67 @@ -async execute(message, client) { - try { +// MODMAIL +if (message.author.bot) return; - // ModMail DM's - if (message.author.bot) return; +if (!message.guild) { - if (!message.guild) { - // modmail code hier - return; - } + const guild = client.guilds.cache.get( + client.config.bot.guildId + ); - // normale serverberichten - await handleLeveling(message, client); - - } catch (error) { - logger.error('Error in messageCreate event:', error); - } -} + if (!guild) return; + const ticket = guild.channels.cache.find( + c => c.topic === message.author.id + ); + if (ticket) { + await ticket.send( + `👤 ${message.author.tag}: ${message.content}` + ); + return; + } + const embed = new EmbedBuilder() + .setTitle('📩 ModMail') + .setDescription( + 'Weet je zeker dat je een ticket wilt openen?' + ); + + const row = new ActionRowBuilder() + .addComponents( + new ButtonBuilder() + .setCustomId(`modmail_yes:${message.author.id}`) + .setLabel('Ja') + .setStyle(ButtonStyle.Success), + + new ButtonBuilder() + .setCustomId(`modmail_no:${message.author.id}`) + .setLabel('Nee') + .setStyle(ButtonStyle.Danger) + ); + + await message.author.send({ + embeds: [embed], + components: [row] + }); + + return; +} +if ( + message.guild && + message.channel.topic && + message.channel.parentId === 'JOUW_CATEGORIE_ID' +) { + + const user = await client.users.fetch( + message.channel.topic + ).catch(() => null); + + if (user) { + await user.send( + `📨 Staff: ${message.content}` + ).catch(() => {}); + } +} import { Events, From 950daaff0e247006153f265856b440cdf7ae88fd Mon Sep 17 00:00:00 2001 From: Sing4Hope Date: Wed, 3 Jun 2026 14:46:26 +0200 Subject: [PATCH 7/9] Create close.js --- src/commands/Utility/close.js | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 src/commands/Utility/close.js diff --git a/src/commands/Utility/close.js b/src/commands/Utility/close.js new file mode 100644 index 000000000..1c7dcdc15 --- /dev/null +++ b/src/commands/Utility/close.js @@ -0,0 +1,33 @@ +import { SlashCommandBuilder } from 'discord.js'; + +export default { + data: new SlashCommandBuilder() + .setName('close') + .setDescription('Sluit een ModMail ticket'), + + async execute(interaction) { + + if (!interaction.channel.topic) { + return interaction.reply({ + content: 'Dit is geen ticket.', + ephemeral: true + }); + } + + const user = await interaction.client.users.fetch( + interaction.channel.topic + ); + + await user.send( + '🔒 Je ticket is gesloten.' + ).catch(() => {}); + + await interaction.reply( + '✅ Ticket gesloten.' + ); + + setTimeout(() => { + interaction.channel.delete().catch(() => {}); + }, 3000); + } +}; From 24dba623f8eab35bcd42cc67df10cd9360604b8f Mon Sep 17 00:00:00 2001 From: Sing4Hope Date: Wed, 3 Jun 2026 14:55:50 +0200 Subject: [PATCH 8/9] Update messageCreate.js --- src/events/messageCreate.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/events/messageCreate.js b/src/events/messageCreate.js index 9c3a50c9f..2a9237f8a 100644 --- a/src/events/messageCreate.js +++ b/src/events/messageCreate.js @@ -1,4 +1,8 @@ -// MODMAIL +console.log( + "Message ontvangen:", + message.author.tag, + message.guild ? "SERVER" : "DM" +);// MODMAIL if (message.author.bot) return; if (!message.guild) { From eb4830d4bb7c391811bd3d784cc988cb40e929a1 Mon Sep 17 00:00:00 2001 From: Codex Date: Wed, 3 Jun 2026 15:10:51 +0200 Subject: [PATCH 9/9] Fix modmail event handling --- .env.example | 4 + src/events/messageCreate.js | 165 +++++++++++------------- src/interactions/buttons/modmail_no.js | 3 +- src/interactions/buttons/modmail_yes.js | 32 +++-- 4 files changed, 101 insertions(+), 103 deletions(-) diff --git a/.env.example b/.env.example index 4f08ac030..3889acad8 100644 --- a/.env.example +++ b/.env.example @@ -6,6 +6,10 @@ CLIENT_ID=your_discord_client_id_here GUILD_ID=your_discord_guild_id_here OWNER_IDS=your_discord_id_here (optional) +# ModMail Configuration +MODMAIL_CATEGORY_ID=your_modmail_category_channel_id_here +MODMAIL_STAFF_ROLE_ID=your_staff_role_id_here + # Bot Runtime Configuration NODE_ENV=production LOG_LEVEL=warn diff --git a/src/events/messageCreate.js b/src/events/messageCreate.js index 2a9237f8a..3c0bbd951 100644 --- a/src/events/messageCreate.js +++ b/src/events/messageCreate.js @@ -1,34 +1,62 @@ -console.log( - "Message ontvangen:", - message.author.tag, - message.guild ? "SERVER" : "DM" -);// MODMAIL -if (message.author.bot) return; +import { + Events, + ActionRowBuilder, + ButtonBuilder, + ButtonStyle, + EmbedBuilder +} from 'discord.js'; +import { logger } from '../utils/logger.js'; +import { getLevelingConfig, getUserLevelData } from '../services/leveling.js'; +import { addXp } from '../services/xpSystem.js'; +import { checkRateLimit } from '../utils/rateLimiter.js'; -if (!message.guild) { +const MESSAGE_XP_RATE_LIMIT_ATTEMPTS = 12; +const MESSAGE_XP_RATE_LIMIT_WINDOW_MS = 10000; +const MODMAIL_CATEGORY_ID = process.env.MODMAIL_CATEGORY_ID || '1498650880604639272'; - const guild = client.guilds.cache.get( - client.config.bot.guildId - ); +export default { + name: Events.MessageCreate, + async execute(message, client) { + try { + if (message.author.bot) return; + + if (!message.guild) { + await handleModmailDm(message, client); + return; + } + + const handledStaffReply = await handleModmailStaffReply(message, client); + if (handledStaffReply) return; + + await handleLeveling(message, client); + } catch (error) { + logger.error('Error in messageCreate event:', error); + } + } +}; - if (!guild) return; +async function handleModmailDm(message, client) { + const guild = await getConfiguredGuild(client); + if (!guild) { + logger.warn('Modmail DM received, but GUILD_ID is not configured or the guild is unavailable.'); + return; + } const ticket = guild.channels.cache.find( - c => c.topic === message.author.id + channel => channel.parentId === MODMAIL_CATEGORY_ID && channel.topic === message.author.id ); if (ticket) { - await ticket.send( - `👤 ${message.author.tag}: ${message.content}` - ); + await ticket.send({ + content: `User ${message.author.tag}: ${message.content || '(no text)'}`, + files: [...message.attachments.values()].map(attachment => attachment.url) + }); return; } const embed = new EmbedBuilder() - .setTitle('📩 ModMail') - .setDescription( - 'Weet je zeker dat je een ticket wilt openen?' - ); + .setTitle('ModMail') + .setDescription('Weet je zeker dat je een ticket wilt openen?'); const row = new ActionRowBuilder() .addComponents( @@ -36,7 +64,6 @@ if (!message.guild) { .setCustomId(`modmail_yes:${message.author.id}`) .setLabel('Ja') .setStyle(ButtonStyle.Success), - new ButtonBuilder() .setCustomId(`modmail_no:${message.author.id}`) .setLabel('Nee') @@ -47,66 +74,39 @@ if (!message.guild) { embeds: [embed], components: [row] }); - - return; -} -if ( - message.guild && - message.channel.topic && - message.channel.parentId === 'JOUW_CATEGORIE_ID' -) { - - const user = await client.users.fetch( - message.channel.topic - ).catch(() => null); - - if (user) { - await user.send( - `📨 Staff: ${message.content}` - ).catch(() => {}); - } } -import { - Events, - ActionRowBuilder, - ButtonBuilder, - ButtonStyle, - EmbedBuilder -} from 'discord.js'; -import { logger } from '../utils/logger.js'; -import { getLevelingConfig, getUserLevelData } from '../services/leveling.js'; -import { addXp } from '../services/xpSystem.js'; -import { checkRateLimit } from '../utils/rateLimiter.js'; - -const MESSAGE_XP_RATE_LIMIT_ATTEMPTS = 12; -const MESSAGE_XP_RATE_LIMIT_WINDOW_MS = 10000; - -export default { - name: Events.MessageCreate, - async execute(message, client) { - try { - import { - ActionRowBuilder, - ButtonBuilder, - ButtonStyle, - EmbedBuilder -} from 'discord.js'; - if (message.author.bot || !message.guild) return; - - await handleLeveling(message, client); - } catch (error) { - logger.error('Error in messageCreate event:', error); - } +async function handleModmailStaffReply(message, client) { + if ( + message.channel?.parentId !== MODMAIL_CATEGORY_ID || + !message.channel?.topic + ) { + return false; } -}; - - + const user = await client.users.fetch(message.channel.topic).catch(() => null); + if (!user) { + await message.reply('I could not find the user for this modmail ticket.'); + return true; + } + await user.send({ + content: `Staff: ${message.content || '(no text)'}`, + files: [...message.attachments.values()].map(attachment => attachment.url) + }).catch(async () => { + await message.reply('I could not DM this user. Their DMs may be closed.'); + }); + return true; +} +async function getConfiguredGuild(client) { + const guildId = client.config?.bot?.guildId; + if (!guildId) return null; + return client.guilds.cache.get(guildId) + || await client.guilds.fetch(guildId).catch(() => null); +} async function handleLeveling(message, client) { try { @@ -117,17 +117,15 @@ async function handleLeveling(message, client) { } const levelingConfig = await getLevelingConfig(client, message.guild.id); - + if (!levelingConfig?.enabled) { return; } - if (levelingConfig.ignoredChannels?.includes(message.channel.id)) { return; } - if (levelingConfig.ignoredRoles?.length > 0) { const member = await message.guild.members.fetch(message.author.id).catch(() => { return null; @@ -137,48 +135,39 @@ async function handleLeveling(message, client) { } } - if (levelingConfig.blacklistedUsers?.includes(message.author.id)) { return; } - if (!message.content || message.content.trim().length === 0) { return; } const userData = await getUserLevelData(client, message.guild.id, message.author.id); - - + const cooldownTime = levelingConfig.xpCooldown || 60; const now = Date.now(); const timeSinceLastMessage = now - (userData.lastMessage || 0); - - + if (timeSinceLastMessage < cooldownTime * 1000) { return; } - const minXP = levelingConfig.xpRange?.min || levelingConfig.xpPerMessage?.min || 15; const maxXP = levelingConfig.xpRange?.max || levelingConfig.xpPerMessage?.max || 25; - const safeMinXP = Math.max(1, minXP); const safeMaxXP = Math.max(safeMinXP, maxXP); - const xpToGive = Math.floor(Math.random() * (safeMaxXP - safeMinXP + 1)) + safeMinXP; - let finalXP = xpToGive; if (levelingConfig.xpMultiplier && levelingConfig.xpMultiplier > 1) { finalXP = Math.floor(finalXP * levelingConfig.xpMultiplier); } - const result = await addXp(client, message.guild, message.member, finalXP); - + if (result.success && result.leveledUp) { logger.info( `${message.author.tag} leveled up to level ${result.level} in ${message.guild.name}` @@ -188,5 +177,3 @@ async function handleLeveling(message, client) { logger.error('Error handling leveling for message:', error); } } - - diff --git a/src/interactions/buttons/modmail_no.js b/src/interactions/buttons/modmail_no.js index 9fc69f5c0..d0fb2b5db 100644 --- a/src/interactions/buttons/modmail_no.js +++ b/src/interactions/buttons/modmail_no.js @@ -2,9 +2,8 @@ export default { name: 'modmail_no', async execute(interaction) { - await interaction.update({ - content: '❌ Ticket geannuleerd.', + content: 'Ticket geannuleerd.', embeds: [], components: [] }); diff --git a/src/interactions/buttons/modmail_yes.js b/src/interactions/buttons/modmail_yes.js index 6ef7886f3..72133e4bd 100644 --- a/src/interactions/buttons/modmail_yes.js +++ b/src/interactions/buttons/modmail_yes.js @@ -3,22 +3,33 @@ import { PermissionFlagsBits } from 'discord.js'; -const CATEGORY_ID = '1498650880604639272'; -const STAFF_ROLE_ID = '1498650876473249818'; +const CATEGORY_ID = process.env.MODMAIL_CATEGORY_ID || '1498650880604639272'; +const STAFF_ROLE_ID = process.env.MODMAIL_STAFF_ROLE_ID || '1498650876473249818'; export default { name: 'modmail_yes', async execute(interaction, client, args) { - const userId = args[0]; + if (!userId || userId !== interaction.user.id) { + return interaction.reply({ + content: 'Deze modmail knop is niet voor jou.', + ephemeral: true + }); + } - const guild = client.guilds.cache.get( - client.config.bot.guildId - ); + const guild = client.guilds.cache.get(client.config.bot.guildId) + || await client.guilds.fetch(client.config.bot.guildId).catch(() => null); + + if (!guild) { + return interaction.reply({ + content: 'Ik kan de support server niet vinden. Controleer GUILD_ID.', + ephemeral: true + }); + } const existing = guild.channels.cache.find( - c => c.topic === userId + channel => channel.parentId === CATEGORY_ID && channel.topic === userId ); if (existing) { @@ -33,7 +44,6 @@ export default { type: ChannelType.GuildText, parent: CATEGORY_ID, topic: userId, - permissionOverwrites: [ { id: guild.id, @@ -49,12 +59,10 @@ export default { ] }); - await channel.send( - `📩 Nieuw ModMail ticket van <@${userId}>` - ); + await channel.send(`Nieuw ModMail ticket van <@${userId}>`); await interaction.update({ - content: '✅ Ticket geopend.', + content: 'Ticket geopend.', embeds: [], components: [] });