From 9cf864f56eabfa70a83778f7b82f966f8d7051c9 Mon Sep 17 00:00:00 2001 From: Raphael Date: Tue, 25 Nov 2025 15:17:05 +0100 Subject: [PATCH 01/10] feat(event/guild): adding the timestamp on the log --- src/events/guild/guildUpdate.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/events/guild/guildUpdate.ts b/src/events/guild/guildUpdate.ts index 5036320..bbac58a 100644 --- a/src/events/guild/guildUpdate.ts +++ b/src/events/guild/guildUpdate.ts @@ -65,6 +65,7 @@ export default { } const toRep = new EmbedBuilder() .setColor(guildData.color) + .setTimestamp() .setFooter({ text: guildData.footer, }) From 031055e09c17b80214f36e8084901806b6090e26 Mon Sep 17 00:00:00 2001 From: Raphael Date: Tue, 25 Nov 2025 15:20:27 +0100 Subject: [PATCH 02/10] feat(event/guild): creation of the event guildMemberAdd This event will: - Send the welcome message (setup on the welcome command) - Send the logs message (setup on log in logMember) --- src/events/guild/guildMemberAdd.ts | 84 ++++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 src/events/guild/guildMemberAdd.ts diff --git a/src/events/guild/guildMemberAdd.ts b/src/events/guild/guildMemberAdd.ts new file mode 100644 index 0000000..153a0c8 --- /dev/null +++ b/src/events/guild/guildMemberAdd.ts @@ -0,0 +1,84 @@ +import { Channel, EmbedBuilder, Events, GuildMember } from 'discord.js'; +import { Guild as GuildPrisma } from '@prisma/client'; +import { prisma } from '@lib/prisma'; +import { placeholder } from '@lib/placeholder'; + +export default { + name: Events.GuildMemberAdd, + async execute(member: GuildMember) { + const memberId: string = member.id; + const guildId: string = member.guild.id; + const guildData: GuildPrisma = await prisma.guild.findUnique({ + where: { + id: guildId, + }, + }); + if (guildData.joinEnabled) { + const fetchedChannel: Channel | null = await member.client.channels.fetch(guildData.joinChannel as string); + if (fetchedChannel) { + fetchedChannel.send({ + content: placeholder(guildData.joinMessage, member), + }); + } + } + if (guildData.logMember) { + const toSend: EmbedBuilder = new EmbedBuilder() + .setTitle('🛬 | Member just joined') + .setAuthor({ + name: `${member.user.username} (${member.user.id})`, + iconURL: member.displayAvatarURL({ + size: 2048, + extension: 'png', + }), + }) + .setColor(guildData.color) + .setFooter({ + text: guildData.footer, + }) + .setTimestamp() + .setDescription(` + **Acount Creation:** + () + **JoinDate:** + () + `); + const fetchedChannel: Channel | null = await member.client.channels.fetch(guildData.logMember as string); + if (fetchedChannel) { + fetchedChannel.send({ + embeds: [toSend], + }); + } + } + await prisma.user.upsert({ + where: { + id: memberId, + }, + update: {}, + create: { + id: memberId, + }, + }); + await prisma.guildUser.upsert({ + where: { + userId_guildId: { + userId: memberId, + guildId: guildId, + }, + }, + update: {}, + create: { + user: { + connect: + { + id: memberId, + }, + }, + guild: { + connect: { + id: guildId, + }, + }, + }, + }); + }, +}; From 0afccf3735f36433e2e04c8ba5e486313a897656 Mon Sep 17 00:00:00 2001 From: Raphael Date: Tue, 25 Nov 2025 15:20:38 +0100 Subject: [PATCH 03/10] feat(event/guild): creation of the event guildMemberRemove This event will: - Send the welcome message (setup on the welcome command) - Send the logs message (setup on log in logMember) --- src/events/guild/guildMemberRemove.ts | 92 +++++++++++++++++++++++++++ 1 file changed, 92 insertions(+) create mode 100644 src/events/guild/guildMemberRemove.ts diff --git a/src/events/guild/guildMemberRemove.ts b/src/events/guild/guildMemberRemove.ts new file mode 100644 index 0000000..cc61d08 --- /dev/null +++ b/src/events/guild/guildMemberRemove.ts @@ -0,0 +1,92 @@ +import { Channel, EmbedBuilder, Events, GuildMember } from 'discord.js'; +import { Guild as GuildPrisma } from '@prisma/client'; +import { prisma } from '@lib/prisma'; +import { placeholder } from '@lib/placeholder'; +import { getUserRoles } from '@lib/roles.js'; +import { client } from '@lib/client.js'; + +export default { + name: Events.GuildMemberRemove, + async execute(member: GuildMember) { + if (member.id === client.user.id) { + return ; + } + const memberId: string = member.id; + const guildId: string = member.guild.id; + const guildData: GuildPrisma = await prisma.guild.findUnique({ + where: { + id: guildId, + }, + }); + if (guildData.leaveEnabled) { + const fetchedChannel: Channel | null = await member.client.channels.fetch(guildData.leaveChannel as string); + if (fetchedChannel) { + fetchedChannel.send({ + content: placeholder(guildData.leaveMessage, member), + }); + } + } + if (guildData.logMember) { + const toSend: EmbedBuilder = new EmbedBuilder() + .setTitle('🛫 | Member just left') + .setAuthor({ + name: `${member.user.username} (${member.user.id})`, + iconURL: member.displayAvatarURL({ + size: 2048, + extension: 'png', + }), + }) + .setColor(guildData.color) + .setFooter({ + text: guildData.footer, + }) + .setTimestamp() + .setDescription(` + **Roles:** + ${getUserRoles(member)} + + **Acount Creation:** + + **JoinDate:** + + `); + const fetchedChannel: Channel | null = await member.client.channels.fetch(guildData.logMember as string); + if (fetchedChannel) { + fetchedChannel.send({ + embeds: [toSend], + }); + } + } + await prisma.user.upsert({ + where: { + id: memberId, + }, + update: {}, + create: { + id: memberId, + }, + }); + await prisma.guildUser.upsert({ + where: { + userId_guildId: { + userId: memberId, + guildId: guildId, + }, + }, + update: {}, + create: { + user: { + connect: + { + id: memberId, + }, + }, + guild: { + connect: { + id: guildId, + }, + }, + }, + }); + }, +}; From 6b2876e514fd576f5ea9e37ffaaa1a8eae794bfc Mon Sep 17 00:00:00 2001 From: Raphael Date: Fri, 5 Dec 2025 12:16:09 +0100 Subject: [PATCH 04/10] fix(commands/administration): now the interaction will not reply if the channel is deleted The interaction will send a message at the start of the command's execution and a confirmation message at the end (only if the channel is still here) --- src/commands/administration/deleteCategories.ts | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/commands/administration/deleteCategories.ts b/src/commands/administration/deleteCategories.ts index 07c3854..94a04f8 100644 --- a/src/commands/administration/deleteCategories.ts +++ b/src/commands/administration/deleteCategories.ts @@ -42,6 +42,11 @@ export default { const category = categoryOption as CategoryChannel; + await interaction.reply({ + content: `${emoji.answer.loading} | Starting the deletion of **${category.name}**.`, + flags: MessageFlags.Ephemeral, + }); + try { for (const channel of category.children.cache.values()) { await channel.delete( @@ -53,10 +58,11 @@ export default { `Deleted ${category.name} (requested by ${interaction.user.username})`, ); - await interaction.reply({ - content: `${emoji.answer.yes} | Deleted category **${category.name}** and its channels.`, - flags: MessageFlags.Ephemeral, - }); + if (interaction.channel) { + await interaction.editReply({ + content: `${emoji.answer.yes} | Deleted category **${category.name}** and its channels.`, + }); + } } catch (err: unknown) { log.error(err, 'Cannot delete category or its channels'); From 2c09ecf9c3c5371cd3666f50d73e1794f710dc93 Mon Sep 17 00:00:00 2001 From: Raphael Date: Fri, 5 Dec 2025 12:17:34 +0100 Subject: [PATCH 05/10] refactor(event/client): guildCreate now removing the unused err param in catch --- src/events/client/guildCreate.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/events/client/guildCreate.ts b/src/events/client/guildCreate.ts index 596f88f..db5461f 100644 --- a/src/events/client/guildCreate.ts +++ b/src/events/client/guildCreate.ts @@ -108,7 +108,7 @@ export default { }); await new Promise((res) => setTimeout(res, 1000)); } - catch (err) { + catch { log.info(`Not able to fetch user ${buyer.id}`); return; } From cabef748b02e08ef322251c7f054f6966f5f1d36 Mon Sep 17 00:00:00 2001 From: Raphael Date: Fri, 5 Dec 2025 12:19:32 +0100 Subject: [PATCH 06/10] refactor(commands/administration): fixing the log extansion in import --- src/commands/administration/logs.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/commands/administration/logs.ts b/src/commands/administration/logs.ts index 96a4b5b..c711300 100644 --- a/src/commands/administration/logs.ts +++ b/src/commands/administration/logs.ts @@ -16,7 +16,7 @@ import { import emoji from '../../../assets/emoji.json' assert { type: 'json' }; import { Guild as GuildPrisma, User as UserPrisma } from '@prisma/client'; import { log } from '@lib/log'; -import { isOwner } from '@lib/perm.js'; +import { isOwner } from '@lib/perm'; export default { data: new SlashCommandBuilder() From d90746b07cf9b904f1f3cd46a3d38bb78a2fc9c3 Mon Sep 17 00:00:00 2001 From: Raphael Date: Fri, 5 Dec 2025 12:20:49 +0100 Subject: [PATCH 07/10] style(commands/administration): logs now have only one configuration and not two --- src/commands/administration/logs.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/commands/administration/logs.ts b/src/commands/administration/logs.ts index c711300..34f1cf2 100644 --- a/src/commands/administration/logs.ts +++ b/src/commands/administration/logs.ts @@ -32,10 +32,6 @@ export default { name: 'Show', value: 'logs_show', }, - { - name: 'Auto-configuration', - value: 'logs_auto', - }, { name: 'Configuration', value: 'logs_config', @@ -143,7 +139,7 @@ export default { } return; } - case 'logs_auto': { + case 'logs_config': { if (!await isOwner(interaction.user.id)) { await interaction.reply({ content: `${emoji.answer.no} | This command is only for owner`, From d121765e88cb8a424c56d372c9cdd042297154a0 Mon Sep 17 00:00:00 2001 From: Raphael Date: Fri, 5 Dec 2025 12:21:18 +0100 Subject: [PATCH 08/10] feat(lib/client): now adding all the Gateway needed for the bot --- src/lib/client.ts | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/lib/client.ts b/src/lib/client.ts index 3fa9cbb..76d6e7f 100644 --- a/src/lib/client.ts +++ b/src/lib/client.ts @@ -1,13 +1,18 @@ -import { - Client, - GatewayIntentBits, -} from 'discord.js'; +import { Client, GatewayIntentBits } from 'discord.js'; export const client: Client = new Client({ intents: [ GatewayIntentBits.Guilds, + GatewayIntentBits.GuildModeration, + GatewayIntentBits.GuildMembers, GatewayIntentBits.GuildMessages, GatewayIntentBits.MessageContent, - GatewayIntentBits.GuildMembers, + GatewayIntentBits.GuildMessageReactions, + GatewayIntentBits.GuildMessageTyping, + GatewayIntentBits.GuildVoiceStates, + GatewayIntentBits.DirectMessages, + GatewayIntentBits.DirectMessageReactions, + GatewayIntentBits.AutoModerationConfiguration, + GatewayIntentBits.AutoModerationExecution, ], }); From 5c22f2dd256f7a428ec2b05e25e2bf6a09120bf9 Mon Sep 17 00:00:00 2001 From: Raphael Date: Fri, 5 Dec 2025 12:22:04 +0100 Subject: [PATCH 09/10] feat(events/guild): Adding the handling of guildBanAdd event --- src/events/guild/guildBanAdd.ts | 64 +++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 src/events/guild/guildBanAdd.ts diff --git a/src/events/guild/guildBanAdd.ts b/src/events/guild/guildBanAdd.ts new file mode 100644 index 0000000..f01de32 --- /dev/null +++ b/src/events/guild/guildBanAdd.ts @@ -0,0 +1,64 @@ +import { + AuditLogEvent, + Channel, + EmbedBuilder, + Events, + Guild, + GuildAuditLogs, + GuildBan, + User, +} from 'discord.js'; +import { Guild as GuildPrisma } from '@prisma/client'; +import { prisma } from '@lib/prisma'; + +export default { + name: Events.GuildBanAdd, + async execute(ban: GuildBan) { + const guild: Guild = ban.guild; + const banned_user: User = ban.user; + const fetchedLogs: GuildAuditLogs = await guild.fetchAuditLogs({ + limit: 5, + type: AuditLogEvent.MemberBanAdd, + }); + const banLog = fetchedLogs.entries.find( + (entry) => entry.target?.id === banned_user.id, + ); + const executor: string = banLog.executor.username ?? 'Unknown executor'; + const reason: string = ban.reason ?? banLog.reason ?? 'No reason provided'; + const guildData: GuildPrisma = await prisma.guild.findUnique({ + where: { + id: guild.id, + }, + }); + + const toSend: EmbedBuilder = new EmbedBuilder() + .setTitle('🔨 | Member banned') + .setAuthor({ + name: `${banned_user.username} (${banned_user.id})`, + iconURL: banned_user.displayAvatarURL({ + size: 2048, + extension: 'png', + }), + }) + .setColor(guildData.color) + .setFooter({ + text: guildData.footer, + }) + .setTimestamp().setDescription(` + **Banned by:** + ${executor} + **Reason:** + ${reason} + `); + if (guildData.logMod) { + const logChannel: Channel | null = await guild.client.channels.fetch( + guildData.logMod, + ); + if (logChannel) { + logChannel.send({ + embeds: [toSend], + }); + } + } + }, +}; From dde334bdfa27b661f4bacd09d0b68f3426d2eea6 Mon Sep 17 00:00:00 2001 From: Raphael Date: Fri, 5 Dec 2025 12:22:17 +0100 Subject: [PATCH 10/10] feat(events/guild): Adding the handling of guildBanRemove event --- src/events/guild/guildBanRemove.ts | 98 ++++++++++++++++++++++++++++++ 1 file changed, 98 insertions(+) create mode 100644 src/events/guild/guildBanRemove.ts diff --git a/src/events/guild/guildBanRemove.ts b/src/events/guild/guildBanRemove.ts new file mode 100644 index 0000000..f61afdd --- /dev/null +++ b/src/events/guild/guildBanRemove.ts @@ -0,0 +1,98 @@ +import { + AuditLogEvent, + Channel, + EmbedBuilder, + Events, + Guild, + GuildAuditLogs, + GuildBan, + User, +} from 'discord.js'; +import { Guild as GuildPrisma } from '@prisma/client'; +import { prisma } from '@lib/prisma'; + +export default { + name: Events.GuildBanRemove, + async execute(ban: GuildBan) { + const guild: Guild = ban.guild; + const banned_user: User = ban.user; + const banFetchLog: GuildAuditLogs = + await guild.fetchAuditLogs({ + limit: 99, + type: AuditLogEvent.MemberBanAdd, + }); + const banLog = banFetchLog.entries.find( + (entry) => entry.target?.id === banned_user.id, + ); + const unbanFetchLog: GuildAuditLogs = + await guild.fetchAuditLogs({ + limit: 99, + type: AuditLogEvent.MemberBanRemove, + }); + const unbanLog = unbanFetchLog.entries.find( + (entry) => entry.target?.id === banned_user.id, + ); + const executor: string = banLog.executor.username ?? 'Unknown executor'; + const reason: string = ban.reason ?? banLog.reason ?? 'No reason provided'; + const bannedAt: number | undefined = banLog?.createdTimestamp; + const unbannedAt: number | undefined = unbanLog?.createdTimestamp; + let durationFormatted: string | null = null; + if (unbannedAt && bannedAt) { + const durationMs: number = unbannedAt - bannedAt; + const durationTotalSeconds: number = Math.max(0, Math.floor(durationMs / 1000)); + const durationHours: number = Math.floor(durationTotalSeconds / 3600); + const durationMinutes: number = Math.floor((durationTotalSeconds % 3600) / 60); + const durationSeconds: number = durationTotalSeconds % 60; + const durationParts: string[] = []; + if (durationHours > 0) { + durationParts.push(`${durationHours}h`); + } + if (durationMinutes > 0) { + durationParts.push(`${durationMinutes}m`); + } + if (durationSeconds > 0 || durationParts.length === 0) { + durationParts.push(`${durationSeconds}s`); + } + durationFormatted = durationParts.join(' '); + } + const guildData: GuildPrisma = await prisma.guild.findUnique({ + where: { + id: guild.id, + }, + }); + + const toSend: EmbedBuilder = new EmbedBuilder() + .setTitle('🔨 | Member Unbanned') + .setAuthor({ + name: `${banned_user.username} (${banned_user.id})`, + iconURL: banned_user.displayAvatarURL({ + size: 2048, + extension: 'png', + }), + }) + .setColor(guildData.color) + .setFooter({ + text: guildData.footer, + }) + .setTimestamp().setDescription(` + **Banned by:** + ${executor} + **Banned the:** + ${bannedAt ? ` ()` : 'No date found'} + **Ban Duration:** + ${durationFormatted ?? 'Cannot calculate the ban duration'} + **Reason:** + ${reason} + `); + if (guildData.logMod) { + const logChannel: Channel | null = await guild.client.channels.fetch( + guildData.logMod, + ); + if (logChannel) { + logChannel.send({ + embeds: [toSend], + }); + } + } + }, +};