From d242a1f0b2b901d1f6997353b8e12a0d11f9bfca Mon Sep 17 00:00:00 2001 From: lunarash0 Date: Thu, 23 Apr 2026 03:22:54 +0200 Subject: [PATCH 1/5] add note --- .env.template | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.env.template b/.env.template index 11b6889..ea87481 100644 --- a/.env.template +++ b/.env.template @@ -1,5 +1,5 @@ DISCORD_TOKEN=your bot's token APPLICATION_ID=your bot's application id DATABASE_URL="postgresql://databaseurl" -DEV=true/false -DEVIDS=list of userids that can run commands when in dev mode +DEV=true/false (this env var is optional) +DEVIDS=list of userids that can run commands when in dev mode (this env var is optional) From 27e8f45c43217820e87a109b0c8dcd62424ccb41 Mon Sep 17 00:00:00 2001 From: UnbraveChimp <129645851+UnbraveChimp@users.noreply.github.com> Date: Tue, 28 Apr 2026 00:56:33 +0200 Subject: [PATCH 2/5] Improve README --- README.md | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 800a480..0275ebf 100644 --- a/README.md +++ b/README.md @@ -5,13 +5,9 @@ An opensource modern Discord Bot built for moderation, utilities and support! -## Discord Server +## Links -Quest's Official Discord Server can be joined using the following link: - -https://discord.gg/ksuqZ77R88 - -Thanks! +[Status Page](https://status.questfoundation.dev), [Bot Documentation](https://questfoundation.dev/bot/docs) & [Official Discord](https://discord.gg/ksuqZ77R88) ## Running locally From 698d3394a04b3e5be1b30981c0bcb56aeda8a428 Mon Sep 17 00:00:00 2001 From: UnbraveChimp <129645851+UnbraveChimp@users.noreply.github.com> Date: Fri, 1 May 2026 13:27:25 +0200 Subject: [PATCH 3/5] README: Docs Link Direct to Quest-Bot --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0275ebf..8b93886 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ An opensource modern Discord Bot built for moderation, utilities and support! ## Links -[Status Page](https://status.questfoundation.dev), [Bot Documentation](https://questfoundation.dev/bot/docs) & [Official Discord](https://discord.gg/ksuqZ77R88) +[Status Page](https://status.questfoundation.dev), [Bot Documentation](https://docs.questfoundation.dev/quest-bot) & [Official Discord](https://discord.gg/ksuqZ77R88) ## Running locally From efc6a76c33e5d35bbeff5ea86f82c13672e70814 Mon Sep 17 00:00:00 2001 From: lunarash0 Date: Fri, 1 May 2026 13:35:05 +0200 Subject: [PATCH 4/5] rename target to member --- README.md | 16 +- src/commands/moderation/ban.ts | 218 ++++++++++++++----------- src/commands/moderation/kick.ts | 193 +++++++++++++---------- src/commands/moderation/mute.ts | 248 ++++++++++++++++------------- src/commands/moderation/unban.ts | 183 +++++++++++---------- src/commands/moderation/unmute.ts | 192 +++++++++++++---------- src/commands/moderation/warn.ts | 253 +++++++++++++++++------------- 7 files changed, 741 insertions(+), 562 deletions(-) diff --git a/README.md b/README.md index 800a480..9c07b12 100644 --- a/README.md +++ b/README.md @@ -5,13 +5,9 @@ An opensource modern Discord Bot built for moderation, utilities and support! -## Discord Server +## Links -Quest's Official Discord Server can be joined using the following link: - -https://discord.gg/ksuqZ77R88 - -Thanks! +[Status Page](https://status.questfoundation.dev), [Bot Documentation](https://docs.questfoundation.dev/quest-bot) & [Official Discord](https://discord.gg/ksuqZ77R88) ## Running locally @@ -19,9 +15,11 @@ Thanks! 2. Create a PostgreSQL database for this bot. 3. Create a `.env` file based on `.env.example` and fill in the required values. 4. Install dependencies with `pnpm install` -5. Deploy the commands with `pnpm run deploy` -6. Run the development server with `pnpm dev` -7. The bot is now live on Discord with registered commands! +5. Generate prisma using `pnpm prisma generate` +6. Update your local databse using `pnpm prisma db push` +7. Deploy the commands with `pnpm run deploy` +8. Run the development server with `pnpm dev` +9. The bot is now live on Discord with registered commands! ## AI declaration diff --git a/src/commands/moderation/ban.ts b/src/commands/moderation/ban.ts index d3fd158..703d3ae 100644 --- a/src/commands/moderation/ban.ts +++ b/src/commands/moderation/ban.ts @@ -1,111 +1,145 @@ import type { Command } from '../index.js'; -import { ButtonBuilder, ButtonStyle, ActionRowBuilder, ApplicationCommandOptionType, PermissionsBitField, GuildMember, MessageFlags } from 'discord.js'; +import { + ButtonBuilder, + ButtonStyle, + ActionRowBuilder, + ApplicationCommandOptionType, + PermissionsBitField, + GuildMember, + MessageFlags, +} from 'discord.js'; import { emojis } from '#utils/emoji.js'; -import ms, { type StringValue } from 'ms' +import ms, { type StringValue } from 'ms'; import { createBan, applyBan } from '#lib/bans.js'; export default { data: { name: 'ban', description: 'Ban someone from the discord server.', - options: [ - { - type: ApplicationCommandOptionType.User, - name: "target", - description: "Select a user to ban", - required: true - }, - { - type: ApplicationCommandOptionType.String, - name: "reason", - description: "Provide a reason for their ban" - }, - { - type: ApplicationCommandOptionType.String, - name: "duration", - description: "Provide a duration for their ban (if needed)" - } - ] + options: [ + { + type: ApplicationCommandOptionType.User, + name: 'member', + description: 'Select a member to ban', + required: true, + }, + { + type: ApplicationCommandOptionType.String, + name: 'reason', + description: 'Provide a reason for their ban', + }, + { + type: ApplicationCommandOptionType.String, + name: 'duration', + description: 'Provide a duration for their ban (if needed)', + }, + ], }, async execute(interaction) { - if (!interaction.inCachedGuild()) { - await interaction.reply({ content: `${emojis.rightArrow2} This command can only be used in a server.`, flags: MessageFlags.Ephemeral }); - return; - } - - const member = interaction.member as GuildMember; - - if (!member.permissions.has(PermissionsBitField.Flags.BanMembers)) { - await interaction.reply({ content: `${emojis.rightArrow2} You do not have permission to ban members.`, flags: MessageFlags.Ephemeral }); - return - } + if (!interaction.inCachedGuild()) { + await interaction.reply({ + content: `${emojis.rightArrow2} This command can only be used in a server.`, + flags: MessageFlags.Ephemeral, + }); + return; + } - const targetMember = interaction.options.getMember('target') as GuildMember + const member = interaction.member as GuildMember; + + if (!member.permissions.has(PermissionsBitField.Flags.BanMembers)) { + await interaction.reply({ + content: `${emojis.rightArrow2} You do not have permission to ban members.`, + flags: MessageFlags.Ephemeral, + }); + return; + } + + const targetMember = interaction.options.getMember('member') as GuildMember; const reason = interaction.options.getString('reason') ?? 'No reason provided'; - - const durationStr = interaction.options.getString('duration') as StringValue - const duration = durationStr ? ms(durationStr as StringValue) : null; - const expiresAt = duration ? new Date(Date.now() + duration) : null; - - if (!targetMember) { - await interaction.reply({ content: `${emojis.rightArrow2} That user is not in this server.`, flags: MessageFlags.Ephemeral }); - return; - } - if (targetMember.id === interaction.user.id) { - await interaction.reply({ content: `${emojis.rightArrow2} You cannot ban yourself.`, flags: MessageFlags.Ephemeral }); - return; - } - - if (targetMember.id === interaction.guild.ownerId) { - await interaction.reply({ content: `${emojis.rightArrow2} You cannot ban the server owner.`, flags: MessageFlags.Ephemeral }); - return; - } - - if (!targetMember.bannable){ - await interaction.reply({ content: `${emojis.rightArrow2} I cannot ban this user.`, flags: MessageFlags.Ephemeral }); - return; - } + const durationStr = interaction.options.getString('duration') as StringValue; + const duration = durationStr ? ms(durationStr as StringValue) : null; + const expiresAt = duration ? new Date(Date.now() + duration) : null; + + if (!targetMember) { + await interaction.reply({ + content: `${emojis.rightArrow2} That user is not in this server.`, + flags: MessageFlags.Ephemeral, + }); + return; + } + + if (targetMember.id === interaction.user.id) { + await interaction.reply({ + content: `${emojis.rightArrow2} You cannot ban yourself.`, + flags: MessageFlags.Ephemeral, + }); + return; + } + + if (targetMember.id === interaction.guild.ownerId) { + await interaction.reply({ + content: `${emojis.rightArrow2} You cannot ban the server owner.`, + flags: MessageFlags.Ephemeral, + }); + return; + } - const confirm = new ButtonBuilder() - .setCustomId('confirm') - .setLabel('Confirm Ban') - .setStyle(ButtonStyle.Danger) - - const cancel = new ButtonBuilder() - .setCustomId('cancel') - .setLabel('Cancel') - .setStyle(ButtonStyle.Secondary) - - const row = new ActionRowBuilder().addComponents(cancel, confirm); - const response = await interaction.reply({ + if (!targetMember.bannable) { + await interaction.reply({ + content: `${emojis.rightArrow2} I cannot ban this user.`, + flags: MessageFlags.Ephemeral, + }); + return; + } + + const confirm = new ButtonBuilder().setCustomId('confirm').setLabel('Confirm Ban').setStyle(ButtonStyle.Danger); + + const cancel = new ButtonBuilder().setCustomId('cancel').setLabel('Cancel').setStyle(ButtonStyle.Secondary); + + const row = new ActionRowBuilder().addComponents(cancel, confirm); + const response = await interaction.reply({ content: `${emojis.rightArrow1} Are you sure you want to ban <@${targetMember.id}> for reason: ${reason}?`, components: [row], - withResponse: true, + withResponse: true, }); - - const collectorFilter = (i: { user: { id: string; }; }) => i.user.id === interaction.user.id; - try { - const confirmation = await response.resource!.message!.awaitMessageComponent({ filter: collectorFilter, time: 60_000 }); - - if (confirmation.customId === 'confirm') { - try { - await createBan(interaction.guild.id, interaction.guild.name, targetMember.id, expiresAt, reason); - await applyBan(interaction.guild, targetMember.id, reason); - await targetMember.send( - `You have been banned from **${interaction.guild.name}**.\nReason: ${reason}${expiresAt ? `\nExpires: ` : ''}` - ).catch(() => {}); - await confirmation.update({ content: `${emojis.rightArrow2} <@${targetMember.user.id}> has been banned with reason: ${reason}`, components: [] }); - } catch (err) { - console.error(err) - await confirmation.update({ content: `${emojis.rightArrow2} Failed to ban <@${targetMember.user.id}> with reason: ${reason}`, components: [] }); - } - } else if (confirmation.customId === 'cancel') { - await confirmation.update({ content: `${emojis.rightArrow2} Cancelled.`, components: [] }); - } - } catch (err) { - console.error(err) - await interaction.editReply({ content: `${emojis.rightArrow2} No response within a minute or errored.`, components: [] }); - } + + const collectorFilter = (i: { user: { id: string } }) => i.user.id === interaction.user.id; + try { + const confirmation = await response.resource!.message!.awaitMessageComponent({ + filter: collectorFilter, + time: 60_000, + }); + + if (confirmation.customId === 'confirm') { + try { + await createBan(interaction.guild.id, interaction.guild.name, targetMember.id, expiresAt, reason); + await applyBan(interaction.guild, targetMember.id, reason); + await targetMember + .send( + `You have been banned from **${interaction.guild.name}**.\nReason: ${reason}${expiresAt ? `\nExpires: ` : ''}`, + ) + .catch(() => {}); + await confirmation.update({ + content: `${emojis.rightArrow2} <@${targetMember.user.id}> has been banned with reason: ${reason}`, + components: [], + }); + } catch (err) { + console.error(err); + await confirmation.update({ + content: `${emojis.rightArrow2} Failed to ban <@${targetMember.user.id}> with reason: ${reason}`, + components: [], + }); + } + } else if (confirmation.customId === 'cancel') { + await confirmation.update({ content: `${emojis.rightArrow2} Cancelled.`, components: [] }); + } + } catch (err) { + console.error(err); + await interaction.editReply({ + content: `${emojis.rightArrow2} No response within a minute or errored.`, + components: [], + }); + } }, } satisfies Command; diff --git a/src/commands/moderation/kick.ts b/src/commands/moderation/kick.ts index 1901228..63f646e 100644 --- a/src/commands/moderation/kick.ts +++ b/src/commands/moderation/kick.ts @@ -1,100 +1,131 @@ import type { Command } from '../index.js'; -import { ButtonBuilder, ButtonStyle, ActionRowBuilder, ApplicationCommandOptionType, PermissionsBitField, GuildMember, MessageFlags } from 'discord.js'; +import { + ButtonBuilder, + ButtonStyle, + ActionRowBuilder, + ApplicationCommandOptionType, + PermissionsBitField, + GuildMember, + MessageFlags, +} from 'discord.js'; import { emojis } from '#utils/emoji.js'; export default { data: { name: 'kick', description: 'Kick someone from the discord server.', - options: [ - { - type: ApplicationCommandOptionType.User, - name: "target", - description: "Select a user to kick", - required: true - }, - { - type: ApplicationCommandOptionType.String, - name: "reason", - description: "Provide a reason for their kick" - } - ] + options: [ + { + type: ApplicationCommandOptionType.User, + name: 'member', + description: 'Select a member to kick', + required: true, + }, + { + type: ApplicationCommandOptionType.String, + name: 'reason', + description: 'Provide a reason for their kick', + }, + ], }, async execute(interaction) { - if (!interaction.inCachedGuild()) { - await interaction.reply({ content: `${emojis.rightArrow2} This command can only be used in a server.`, flags: MessageFlags.Ephemeral }); - return; - } - - const member = interaction.member as GuildMember; - - if (!member.permissions.has(PermissionsBitField.Flags.KickMembers)) { - await interaction.reply({ content: `${emojis.rightArrow2} You do not have permission to kick members.`, flags: MessageFlags.Ephemeral }); - return; - } + if (!interaction.inCachedGuild()) { + await interaction.reply({ + content: `${emojis.rightArrow2} This command can only be used in a server.`, + flags: MessageFlags.Ephemeral, + }); + return; + } - const targetMember = interaction.options.getMember('target'); + const member = interaction.member as GuildMember; + + if (!member.permissions.has(PermissionsBitField.Flags.KickMembers)) { + await interaction.reply({ + content: `${emojis.rightArrow2} You do not have permission to kick members.`, + flags: MessageFlags.Ephemeral, + }); + return; + } + + const targetMember = interaction.options.getMember('member'); const reason = interaction.options.getString('reason') ?? 'No reason provided'; - - if (!targetMember) { - await interaction.reply({ content: `${emojis.rightArrow2} That user is not in this server.`, flags: MessageFlags.Ephemeral }); - return; - } - - if (targetMember.id === interaction.user.id) { - await interaction.reply({ content: `${emojis.rightArrow2} You cannot kick yourself.`, flags: MessageFlags.Ephemeral }); - return; - } - if (targetMember.id === interaction.guild.ownerId) { - await interaction.reply({ content: `${emojis.rightArrow2} You cannot kick the server owner.`, flags: MessageFlags.Ephemeral }); - return; - } - - if (!targetMember.kickable){ - await interaction.reply({ content: `${emojis.rightArrow2} I cannot kick this user.`, flags: MessageFlags.Ephemeral }); - return; - } + if (!targetMember) { + await interaction.reply({ + content: `${emojis.rightArrow2} That user is not in this server.`, + flags: MessageFlags.Ephemeral, + }); + return; + } + + if (targetMember.id === interaction.user.id) { + await interaction.reply({ + content: `${emojis.rightArrow2} You cannot kick yourself.`, + flags: MessageFlags.Ephemeral, + }); + return; + } + + if (targetMember.id === interaction.guild.ownerId) { + await interaction.reply({ + content: `${emojis.rightArrow2} You cannot kick the server owner.`, + flags: MessageFlags.Ephemeral, + }); + return; + } + if (!targetMember.kickable) { + await interaction.reply({ + content: `${emojis.rightArrow2} I cannot kick this user.`, + flags: MessageFlags.Ephemeral, + }); + return; + } - const confirm = new ButtonBuilder() - .setCustomId('confirm') - .setLabel('Confirm Kick') - .setStyle(ButtonStyle.Danger) - - const cancel = new ButtonBuilder() - .setCustomId('cancel') - .setLabel('Cancel') - .setStyle(ButtonStyle.Secondary) - - const row = new ActionRowBuilder().addComponents(cancel, confirm); - const response = await interaction.reply({ + const confirm = new ButtonBuilder().setCustomId('confirm').setLabel('Confirm Kick').setStyle(ButtonStyle.Danger); + + const cancel = new ButtonBuilder().setCustomId('cancel').setLabel('Cancel').setStyle(ButtonStyle.Secondary); + + const row = new ActionRowBuilder().addComponents(cancel, confirm); + const response = await interaction.reply({ content: `${emojis.rightArrow1} Are you sure you want to kick <@${targetMember.user.id}> with reason: ${reason}?`, components: [row], - withResponse: true, + withResponse: true, }); - - const collectorFilter = (i: { user: { id: string; }; }) => i.user.id === interaction.user.id; - try { - const confirmation = await response.resource!.message!.awaitMessageComponent({ filter: collectorFilter, time: 60_000 }); - - if (confirmation.customId === 'confirm') { - try { - await interaction.guild!.members.kick(targetMember); - await targetMember.send( - `You have been kicked from **${interaction.guild.name}**.\nReason: ${reason}}` - ).catch(() => {}); - await confirmation.update({ content: `${emojis.rightArrow2} <@${targetMember.user.id}> has been kicked with reason: ${reason}\nYou must have had a real ick towards that person.`, components: [] }); - } catch (err) { - console.error(err) - await confirmation.update({ content: `${emojis.rightArrow2} Failed to kick <@${targetMember.user.id}> with reason: ${reason}\nYou must have had a real ick towards that person.`, components: [] }); - } - } else if (confirmation.customId === 'cancel') { - await confirmation.update({ content: `${emojis.rightArrow2} Cancelled.`, components: [] }); - } - } catch (err) { - console.error(err) - await interaction.editReply({ content: `${emojis.rightArrow2} No response within a minute or errored.`, components: [] }); - } + + const collectorFilter = (i: { user: { id: string } }) => i.user.id === interaction.user.id; + try { + const confirmation = await response.resource!.message!.awaitMessageComponent({ + filter: collectorFilter, + time: 60_000, + }); + + if (confirmation.customId === 'confirm') { + try { + await interaction.guild!.members.kick(targetMember); + await targetMember + .send(`You have been kicked from **${interaction.guild.name}**.\nReason: ${reason}}`) + .catch(() => {}); + await confirmation.update({ + content: `${emojis.rightArrow2} <@${targetMember.user.id}> has been kicked with reason: ${reason}\nYou must have had a real ick towards that person.`, + components: [], + }); + } catch (err) { + console.error(err); + await confirmation.update({ + content: `${emojis.rightArrow2} Failed to kick <@${targetMember.user.id}> with reason: ${reason}\nYou must have had a real ick towards that person.`, + components: [], + }); + } + } else if (confirmation.customId === 'cancel') { + await confirmation.update({ content: `${emojis.rightArrow2} Cancelled.`, components: [] }); + } + } catch (err) { + console.error(err); + await interaction.editReply({ + content: `${emojis.rightArrow2} No response within a minute or errored.`, + components: [], + }); + } }, } satisfies Command; diff --git a/src/commands/moderation/mute.ts b/src/commands/moderation/mute.ts index 774ab00..bb0a4d1 100644 --- a/src/commands/moderation/mute.ts +++ b/src/commands/moderation/mute.ts @@ -1,114 +1,146 @@ import type { Command } from '../index.js'; -import { ButtonBuilder, ButtonStyle, ActionRowBuilder, ApplicationCommandOptionType, PermissionsBitField, GuildMember, MessageFlags } from 'discord.js'; +import { + ButtonBuilder, + ButtonStyle, + ActionRowBuilder, + ApplicationCommandOptionType, + PermissionsBitField, + GuildMember, + MessageFlags, +} from 'discord.js'; import { emojis } from '#utils/emoji.js'; -import ms, { type StringValue } from 'ms' +import ms, { type StringValue } from 'ms'; import { createMute, enforceMute } from '#lib/mutes.js'; export default { - data: { - name: 'mute', - description: 'Mute someone in the discord server.', - options: [ - { - type: ApplicationCommandOptionType.User, - name: "target", - description: "Select a user to mute", - required: true - }, - { - type: ApplicationCommandOptionType.String, - name: "duration", - description: "Specify a duration for the mute" - }, - { - type: ApplicationCommandOptionType.String, - name: "reason", - description: "Provide a reason for their mute" - } - - ] - }, - async execute(interaction) { - if (!interaction.inCachedGuild()) { - await interaction.reply({ content: `${emojis.rightArrow2} This command can only be used in a server.`, flags: MessageFlags.Ephemeral }); - return; - } - - const member = interaction.member as GuildMember; - - if (!member.permissions.has(PermissionsBitField.Flags.ModerateMembers)) { - await interaction.reply({ content: `${emojis.rightArrow2} You do not have permission to mute members.`, flags: MessageFlags.Ephemeral }); - return - } - - const targetMember = interaction.options.getMember('target') as GuildMember - const reason = interaction.options.getString('reason') ?? 'No reason provided'; - const durationStr = interaction.options.getString('duration') as StringValue - const duration = durationStr ? ms(durationStr as StringValue) : null; - const expiresAt = duration ? new Date(Date.now() + duration) : null; - - if (!targetMember) { - await interaction.reply({ content: `${emojis.rightArrow2} That user is not in this server.`, flags: MessageFlags.Ephemeral }); - return; - } - - if (durationStr && (typeof duration !== 'number' || isNaN(duration))) { - await interaction.reply({ content: `${emojis.rightArrow2} Invalid duration format.`, flags: MessageFlags.Ephemeral }); - return; - } - - if (targetMember.id === interaction.user.id) { - await interaction.reply({ content: `${emojis.rightArrow2} You cannot mute yourself.`, flags: MessageFlags.Ephemeral }); - return; - } - - if (targetMember.id === interaction.guild.ownerId) { - await interaction.reply({ content: `${emojis.rightArrow2} You cannot mute the server owner.`, flags: MessageFlags.Ephemeral }); - return; - } - - if (!targetMember.moderatable){ - await interaction.reply({ content: `${emojis.rightArrow2} I cannot mute this user.`, flags: MessageFlags.Ephemeral }); - return; - } - - const confirm = new ButtonBuilder() - .setCustomId('confirm') - .setLabel('Confirm Mute') - .setStyle(ButtonStyle.Danger) - - const cancel = new ButtonBuilder() - .setCustomId('cancel') - .setLabel('Cancel') - .setStyle(ButtonStyle.Secondary) - - const row = new ActionRowBuilder().addComponents(cancel, confirm); - const response = await interaction.reply({ - content: `${emojis.rightArrow1} Are you sure you want to mute <@${targetMember.user.id}> with reason: ${reason}?`, - components: [row], - withResponse: true, - }); - - const collectorFilter = (i: { user: { id: string; }; }) => i.user.id === interaction.user.id; - try { - const confirmation = await response.resource!.message!.awaitMessageComponent({ filter: collectorFilter, time: 60_000 }); - - if (confirmation.customId === 'confirm') { - await createMute(interaction.guild.id, interaction.guild.name, targetMember.id, expiresAt, reason); - - await targetMember.send( - `You have been muted in **${interaction.guild.name}**.\nReason: ${reason}${expiresAt ? `\nExpires: ` : ''}` - ).catch(() => {}); - - await enforceMute(interaction.guild, targetMember.id); - await confirmation.update({ content: `${emojis.rightArrow2} <@${targetMember.user.id}> has been muted with reason: ${reason}${expiresAt ? `\nExpires: ` : ''}`, components: [] }); - - } else if (confirmation.customId === 'cancel') { - await confirmation.update({ content: `${emojis.rightArrow2} Cancelled.`, components: [] }); - } - } catch (err) { - console.error(err) - await interaction.editReply({ content: `${emojis.rightArrow2} No response within a minute or errored.`, components: [] }); - } - }, + data: { + name: 'mute', + description: 'Mute someone in the discord server.', + options: [ + { + type: ApplicationCommandOptionType.User, + name: 'member', + description: 'Select a member to mute', + required: true, + }, + { + type: ApplicationCommandOptionType.String, + name: 'duration', + description: 'Specify a duration for the mute', + }, + { + type: ApplicationCommandOptionType.String, + name: 'reason', + description: 'Provide a reason for their mute', + }, + ], + }, + async execute(interaction) { + if (!interaction.inCachedGuild()) { + await interaction.reply({ + content: `${emojis.rightArrow2} This command can only be used in a server.`, + flags: MessageFlags.Ephemeral, + }); + return; + } + + const member = interaction.member as GuildMember; + + if (!member.permissions.has(PermissionsBitField.Flags.ModerateMembers)) { + await interaction.reply({ + content: `${emojis.rightArrow2} You do not have permission to mute members.`, + flags: MessageFlags.Ephemeral, + }); + return; + } + + const targetMember = interaction.options.getMember('member') as GuildMember; + const reason = interaction.options.getString('reason') ?? 'No reason provided'; + const durationStr = interaction.options.getString('duration') as StringValue; + const duration = durationStr ? ms(durationStr as StringValue) : null; + const expiresAt = duration ? new Date(Date.now() + duration) : null; + + if (!targetMember) { + await interaction.reply({ + content: `${emojis.rightArrow2} That user is not in this server.`, + flags: MessageFlags.Ephemeral, + }); + return; + } + + if (durationStr && (typeof duration !== 'number' || isNaN(duration))) { + await interaction.reply({ + content: `${emojis.rightArrow2} Invalid duration format.`, + flags: MessageFlags.Ephemeral, + }); + return; + } + + if (targetMember.id === interaction.user.id) { + await interaction.reply({ + content: `${emojis.rightArrow2} You cannot mute yourself.`, + flags: MessageFlags.Ephemeral, + }); + return; + } + + if (targetMember.id === interaction.guild.ownerId) { + await interaction.reply({ + content: `${emojis.rightArrow2} You cannot mute the server owner.`, + flags: MessageFlags.Ephemeral, + }); + return; + } + + if (!targetMember.moderatable) { + await interaction.reply({ + content: `${emojis.rightArrow2} I cannot mute this user.`, + flags: MessageFlags.Ephemeral, + }); + return; + } + + const confirm = new ButtonBuilder().setCustomId('confirm').setLabel('Confirm Mute').setStyle(ButtonStyle.Danger); + + const cancel = new ButtonBuilder().setCustomId('cancel').setLabel('Cancel').setStyle(ButtonStyle.Secondary); + + const row = new ActionRowBuilder().addComponents(cancel, confirm); + const response = await interaction.reply({ + content: `${emojis.rightArrow1} Are you sure you want to mute <@${targetMember.user.id}> with reason: ${reason}?`, + components: [row], + withResponse: true, + }); + + const collectorFilter = (i: { user: { id: string } }) => i.user.id === interaction.user.id; + try { + const confirmation = await response.resource!.message!.awaitMessageComponent({ + filter: collectorFilter, + time: 60_000, + }); + + if (confirmation.customId === 'confirm') { + await createMute(interaction.guild.id, interaction.guild.name, targetMember.id, expiresAt, reason); + + await targetMember + .send( + `You have been muted in **${interaction.guild.name}**.\nReason: ${reason}${expiresAt ? `\nExpires: ` : ''}`, + ) + .catch(() => {}); + + await enforceMute(interaction.guild, targetMember.id); + await confirmation.update({ + content: `${emojis.rightArrow2} <@${targetMember.user.id}> has been muted with reason: ${reason}${expiresAt ? `\nExpires: ` : ''}`, + components: [], + }); + } else if (confirmation.customId === 'cancel') { + await confirmation.update({ content: `${emojis.rightArrow2} Cancelled.`, components: [] }); + } + } catch (err) { + console.error(err); + await interaction.editReply({ + content: `${emojis.rightArrow2} No response within a minute or errored.`, + components: [], + }); + } + }, } satisfies Command; diff --git a/src/commands/moderation/unban.ts b/src/commands/moderation/unban.ts index 490bea6..4639a7f 100644 --- a/src/commands/moderation/unban.ts +++ b/src/commands/moderation/unban.ts @@ -1,92 +1,111 @@ import type { Command } from '../index.js'; -import { ButtonBuilder, ButtonStyle, ActionRowBuilder, ApplicationCommandOptionType, PermissionsBitField, GuildMember, MessageFlags } from 'discord.js'; +import { + ButtonBuilder, + ButtonStyle, + ActionRowBuilder, + ApplicationCommandOptionType, + PermissionsBitField, + GuildMember, + MessageFlags, +} from 'discord.js'; import { emojis } from '#utils/emoji.js'; import { getBan, removeBan } from '#lib/bans.js'; export default { - data: { - name: 'unban', - description: 'Unban someone from the discord server.', - options: [ - { - type: ApplicationCommandOptionType.User, - name: "target", - description: "The user to unban", - required: true - }, - { - type: ApplicationCommandOptionType.String, - name: "reason", - description: "Provide a reason for their unban" - } - ] - }, - async execute(interaction) { - if (!interaction.inCachedGuild()) { - await interaction.reply({ content: `${emojis.rightArrow2} This command can only be used in a server.`, flags: MessageFlags.Ephemeral }); - return; - } - - const member = interaction.member as GuildMember; - - if (!member.permissions.has(PermissionsBitField.Flags.BanMembers)) { - await interaction.reply({ content: `${emojis.rightArrow2} You do not have permission to unban members.`, flags: MessageFlags.Ephemeral }); - return - } - - const targetMember = interaction.options.getUser('target', true); + data: { + name: 'unban', + description: 'Unban someone from the discord server.', + options: [ + { + type: ApplicationCommandOptionType.User, + name: 'member', + description: 'The member to unban', + required: true, + }, + { + type: ApplicationCommandOptionType.String, + name: 'reason', + description: 'Provide a reason for their unban', + }, + ], + }, + async execute(interaction) { + if (!interaction.inCachedGuild()) { + await interaction.reply({ + content: `${emojis.rightArrow2} This command can only be used in a server.`, + flags: MessageFlags.Ephemeral, + }); + return; + } + + const member = interaction.member as GuildMember; + + if (!member.permissions.has(PermissionsBitField.Flags.BanMembers)) { + await interaction.reply({ + content: `${emojis.rightArrow2} You do not have permission to unban members.`, + flags: MessageFlags.Ephemeral, + }); + return; + } + + const targetMember = interaction.options.getUser('member', true); const reason = interaction.options.getString('reason') ?? 'No reason provided'; - - const discordBan = await interaction.guild.bans.fetch(targetMember.id).catch(() => null); - const dbBan = await getBan(interaction.guild.id, targetMember.id); - if (!discordBan && !dbBan) { - await interaction.reply({ - content: `${emojis.rightArrow2} That user isn't banned.`, - flags: MessageFlags.Ephemeral, - }); - return; - } + const discordBan = await interaction.guild.bans.fetch(targetMember.id).catch(() => null); + const dbBan = await getBan(interaction.guild.id, targetMember.id); + + if (!discordBan && !dbBan) { + await interaction.reply({ + content: `${emojis.rightArrow2} That user isn't banned.`, + flags: MessageFlags.Ephemeral, + }); + return; + } + + const confirm = new ButtonBuilder().setCustomId('confirm').setLabel('Confirm Ban').setStyle(ButtonStyle.Danger); + + const cancel = new ButtonBuilder().setCustomId('cancel').setLabel('Cancel').setStyle(ButtonStyle.Secondary); + + const row = new ActionRowBuilder().addComponents(cancel, confirm); + const response = await interaction.reply({ + content: `${emojis.rightArrow1} Are you sure you want to unban <@${targetMember.id}> for reason: ${reason}?`, + components: [row], + withResponse: true, + }); + const collectorFilter = (i: { user: { id: string } }) => i.user.id === interaction.user.id; + try { + const confirmation = await response.resource!.message!.awaitMessageComponent({ + filter: collectorFilter, + time: 60_000, + }); - const confirm = new ButtonBuilder() - .setCustomId('confirm') - .setLabel('Confirm Ban') - .setStyle(ButtonStyle.Danger) - - const cancel = new ButtonBuilder() - .setCustomId('cancel') - .setLabel('Cancel') - .setStyle(ButtonStyle.Secondary) - - const row = new ActionRowBuilder().addComponents(cancel, confirm); - const response = await interaction.reply({ - content: `${emojis.rightArrow1} Are you sure you want to unban <@${targetMember.id}> for reason: ${reason}?`, - components: [row], - withResponse: true, - }); - - const collectorFilter = (i: { user: { id: string; }; }) => i.user.id === interaction.user.id; - try { - const confirmation = await response.resource!.message!.awaitMessageComponent({ filter: collectorFilter, time: 60_000 }); - - if (confirmation.customId === 'confirm') { - try { - await removeBan(interaction.guild, targetMember.id); - await targetMember.send( - `You have been unbanned in **${interaction.guild.name}**.\nReason: ${reason}` - ).catch(() => {}); - await confirmation.update({ content: `${emojis.rightArrow2} <@${targetMember.id}> has been unbanned with reason: ${reason}`, components: [] }); - } catch (err) { - console.error(err) - await confirmation.update({ content: `${emojis.rightArrow2} Failed to unban <@${targetMember.id}> with reason: ${reason}`, components: [] }); - } - } else if (confirmation.customId === 'cancel') { - await confirmation.update({ content: `${emojis.rightArrow2} Cancelled.`, components: [] }); - } - } catch (err) { - console.error(err) - await interaction.editReply({ content: `${emojis.rightArrow2} No response within a minute or errored.`, components: [] }); - } - }, + if (confirmation.customId === 'confirm') { + try { + await removeBan(interaction.guild, targetMember.id); + await targetMember + .send(`You have been unbanned in **${interaction.guild.name}**.\nReason: ${reason}`) + .catch(() => {}); + await confirmation.update({ + content: `${emojis.rightArrow2} <@${targetMember.id}> has been unbanned with reason: ${reason}`, + components: [], + }); + } catch (err) { + console.error(err); + await confirmation.update({ + content: `${emojis.rightArrow2} Failed to unban <@${targetMember.id}> with reason: ${reason}`, + components: [], + }); + } + } else if (confirmation.customId === 'cancel') { + await confirmation.update({ content: `${emojis.rightArrow2} Cancelled.`, components: [] }); + } + } catch (err) { + console.error(err); + await interaction.editReply({ + content: `${emojis.rightArrow2} No response within a minute or errored.`, + components: [], + }); + } + }, } satisfies Command; diff --git a/src/commands/moderation/unmute.ts b/src/commands/moderation/unmute.ts index 4834135..300a716 100644 --- a/src/commands/moderation/unmute.ts +++ b/src/commands/moderation/unmute.ts @@ -1,94 +1,116 @@ import type { Command } from '../index.js'; -import { ButtonBuilder, ButtonStyle, ActionRowBuilder, ApplicationCommandOptionType, PermissionsBitField, GuildMember, MessageFlags } from 'discord.js'; +import { + ButtonBuilder, + ButtonStyle, + ActionRowBuilder, + ApplicationCommandOptionType, + PermissionsBitField, + GuildMember, + MessageFlags, +} from 'discord.js'; import { emojis } from '#utils/emoji.js'; import { removeMute } from '#lib/mutes.js'; export default { - data: { - name: 'unmute', - description: 'Unmute someone in the discord server.', - options: [ - { - type: ApplicationCommandOptionType.User, - name: "target", - description: "Select a user to unmute", - required: true - }, - { - type: ApplicationCommandOptionType.String, - name: "reason", - description: "Provide a reason for their unmute" - } - - ] - }, - async execute(interaction) { - if (!interaction.inCachedGuild()) { - await interaction.reply({ content: `${emojis.rightArrow2} This command can only be used in a server.`, flags: MessageFlags.Ephemeral }); - return; - } - - const member = interaction.member as GuildMember; - - if (!member.permissions.has(PermissionsBitField.Flags.ModerateMembers)) { - await interaction.reply({ content: `${emojis.rightArrow2} You do not have permission to unmute members.`, flags: MessageFlags.Ephemeral }); - return - } + data: { + name: 'unmute', + description: 'Unmute someone in the discord server.', + options: [ + { + type: ApplicationCommandOptionType.User, + name: 'member', + description: 'Select a member to unmute', + required: true, + }, + { + type: ApplicationCommandOptionType.String, + name: 'reason', + description: 'Provide a reason for their unmute', + }, + ], + }, + async execute(interaction) { + if (!interaction.inCachedGuild()) { + await interaction.reply({ + content: `${emojis.rightArrow2} This command can only be used in a server.`, + flags: MessageFlags.Ephemeral, + }); + return; + } - const targetMember = interaction.options.getMember('target') as GuildMember - const reason = interaction.options.getString('reason') ?? 'No reason provided'; + const member = interaction.member as GuildMember; - if (!targetMember) { - await interaction.reply({ content: `${emojis.rightArrow2} That user is not in this server.`, flags: MessageFlags.Ephemeral }); - return; - } - - if (!targetMember.moderatable){ - await interaction.reply({ content: `${emojis.rightArrow2} I cannot unmute this user.`, flags: MessageFlags.Ephemeral }); - return; - } + if (!member.permissions.has(PermissionsBitField.Flags.ModerateMembers)) { + await interaction.reply({ + content: `${emojis.rightArrow2} You do not have permission to unmute members.`, + flags: MessageFlags.Ephemeral, + }); + return; + } - const confirm = new ButtonBuilder() - .setCustomId('confirm') - .setLabel('Confirm Unmute') - .setStyle(ButtonStyle.Danger) - - const cancel = new ButtonBuilder() - .setCustomId('cancel') - .setLabel('Cancel') - .setStyle(ButtonStyle.Secondary) - - const row = new ActionRowBuilder().addComponents(cancel, confirm); - const response = await interaction.reply({ - content: `${emojis.rightArrow1} Are you sure you want to unmute <@${targetMember.user.id}> with reason: ${reason}?`, - components: [row], - withResponse: true, - }); - - const collectorFilter = (i: { user: { id: string; }; }) => i.user.id === interaction.user.id; - try { - const confirmation = await response.resource!.message!.awaitMessageComponent({ filter: collectorFilter, time: 60_000 }); - - if (confirmation.customId === 'confirm') { - try { - await Promise.all([ - removeMute(interaction.guild.id, targetMember.id), - targetMember.timeout(null, reason), - ]); - - await targetMember.send( - `You have been unmuted in **${interaction.guild.name}**.\nReason: ${reason}}` - ).catch(() => {}); - await confirmation.update({ content: `${emojis.rightArrow2} <@${targetMember.id}> has been unmuted. Reason: ${reason}`, components: [] }); - } catch (err) { - console.error(err); - await confirmation.update({ content: `${emojis.rightArrow2} Failed to unmute <@${targetMember.id}> with reason: ${reason}`, components: [] }); - } - } else if (confirmation.customId === 'cancel') { - await confirmation.update({ content: `${emojis.rightArrow2} Cancelled.`, components: [] }); - } - } catch { - await interaction.editReply({ content: `${emojis.rightArrow2} No response within a minute or errored.`, components: [] }); - } - }, + const targetMember = interaction.options.getMember('member') as GuildMember; + const reason = interaction.options.getString('reason') ?? 'No reason provided'; + + if (!targetMember) { + await interaction.reply({ + content: `${emojis.rightArrow2} That user is not in this server.`, + flags: MessageFlags.Ephemeral, + }); + return; + } + + if (!targetMember.moderatable) { + await interaction.reply({ + content: `${emojis.rightArrow2} I cannot unmute this user.`, + flags: MessageFlags.Ephemeral, + }); + return; + } + + const confirm = new ButtonBuilder().setCustomId('confirm').setLabel('Confirm Unmute').setStyle(ButtonStyle.Danger); + + const cancel = new ButtonBuilder().setCustomId('cancel').setLabel('Cancel').setStyle(ButtonStyle.Secondary); + + const row = new ActionRowBuilder().addComponents(cancel, confirm); + const response = await interaction.reply({ + content: `${emojis.rightArrow1} Are you sure you want to unmute <@${targetMember.user.id}> with reason: ${reason}?`, + components: [row], + withResponse: true, + }); + + const collectorFilter = (i: { user: { id: string } }) => i.user.id === interaction.user.id; + try { + const confirmation = await response.resource!.message!.awaitMessageComponent({ + filter: collectorFilter, + time: 60_000, + }); + + if (confirmation.customId === 'confirm') { + try { + await Promise.all([removeMute(interaction.guild.id, targetMember.id), targetMember.timeout(null, reason)]); + + await targetMember + .send(`You have been unmuted in **${interaction.guild.name}**.\nReason: ${reason}}`) + .catch(() => {}); + await confirmation.update({ + content: `${emojis.rightArrow2} <@${targetMember.id}> has been unmuted. Reason: ${reason}`, + components: [], + }); + } catch (err) { + console.error(err); + await confirmation.update({ + content: `${emojis.rightArrow2} Failed to unmute <@${targetMember.id}> with reason: ${reason}`, + components: [], + }); + } + } else if (confirmation.customId === 'cancel') { + await confirmation.update({ content: `${emojis.rightArrow2} Cancelled.`, components: [] }); + } + } catch { + await interaction.editReply({ + content: `${emojis.rightArrow2} No response within a minute or errored.`, + components: [], + }); + } + }, } satisfies Command; diff --git a/src/commands/moderation/warn.ts b/src/commands/moderation/warn.ts index b6455f8..4a5a115 100644 --- a/src/commands/moderation/warn.ts +++ b/src/commands/moderation/warn.ts @@ -1,115 +1,158 @@ import type { Command } from '../index.js'; -import { ButtonBuilder, ButtonStyle, ActionRowBuilder, ApplicationCommandOptionType, PermissionsBitField, GuildMember, MessageFlags } from 'discord.js'; +import { + ButtonBuilder, + ButtonStyle, + ActionRowBuilder, + ApplicationCommandOptionType, + PermissionsBitField, + GuildMember, + MessageFlags, +} from 'discord.js'; import { emojis } from '#utils/emoji.js'; -import ms, { type StringValue } from 'ms' +import ms, { type StringValue } from 'ms'; import { createWarn } from '#lib/warns.js'; export default { - data: { - name: 'warn', - description: 'Warn someone in the discord server.', - options: [ - { - type: ApplicationCommandOptionType.User, - name: "target", - description: "Select a user to warn", - required: true - }, - { - type: ApplicationCommandOptionType.String, - name: "reason", - description: "Provide a reason for their warn" - }, - { - type: ApplicationCommandOptionType.String, - name: "duration", - description: "Specify a duration for the warn" - } - - ] - }, - async execute(interaction) { - if (!interaction.inCachedGuild()) { - await interaction.reply({ content: `${emojis.rightArrow2} This command can only be used in a server.`, flags: MessageFlags.Ephemeral }); - return; - } - - const member = interaction.member as GuildMember; - - if (!member.permissions.has(PermissionsBitField.Flags.ModerateMembers)) { - await interaction.reply({ content: `${emojis.rightArrow2} You do not have permission to warn members.`, flags: MessageFlags.Ephemeral }); - return - } + data: { + name: 'warn', + description: 'Warn someone in the discord server.', + options: [ + { + type: ApplicationCommandOptionType.User, + name: 'member', + description: 'Select a member to warn', + required: true, + }, + { + type: ApplicationCommandOptionType.String, + name: 'reason', + description: 'Provide a reason for their warn', + }, + { + type: ApplicationCommandOptionType.String, + name: 'duration', + description: 'Specify a duration for the warn', + }, + ], + }, + async execute(interaction) { + if (!interaction.inCachedGuild()) { + await interaction.reply({ + content: `${emojis.rightArrow2} This command can only be used in a server.`, + flags: MessageFlags.Ephemeral, + }); + return; + } - const targetMember = interaction.options.getMember('target') as GuildMember - const reason = interaction.options.getString('reason') ?? 'No reason provided'; - const durationStr = interaction.options.getString('duration') as StringValue - const duration = durationStr ? ms(durationStr as StringValue) : null; - const expiresAt = duration ? new Date(Date.now() + duration) : null; - - if (durationStr && (typeof duration !== 'number' || isNaN(duration))) { - await interaction.reply({ content: `${emojis.rightArrow2} Invalid duration format.`, flags: MessageFlags.Ephemeral }); - return; - } + const member = interaction.member as GuildMember; - if (!targetMember) { - await interaction.reply({ content: `${emojis.rightArrow2} That user is not in this server.`, flags: MessageFlags.Ephemeral }); - return; - } + if (!member.permissions.has(PermissionsBitField.Flags.ModerateMembers)) { + await interaction.reply({ + content: `${emojis.rightArrow2} You do not have permission to warn members.`, + flags: MessageFlags.Ephemeral, + }); + return; + } - if (targetMember.id === interaction.user.id) { - await interaction.reply({ content: `${emojis.rightArrow2} You cannot warn yourself.`, flags: MessageFlags.Ephemeral }); - return; - } - - if (targetMember.id === interaction.guild.ownerId) { - await interaction.reply({ content: `${emojis.rightArrow2} You cannot warn the server owner.`, flags: MessageFlags.Ephemeral }); - return; - } - - if (!targetMember.moderatable){ - await interaction.reply({ content: `${emojis.rightArrow2} I cannot warn this user.`, flags: MessageFlags.Ephemeral }); - return; - } + const targetMember = interaction.options.getMember('member') as GuildMember; + const reason = interaction.options.getString('reason') ?? 'No reason provided'; + const durationStr = interaction.options.getString('duration') as StringValue; + const duration = durationStr ? ms(durationStr as StringValue) : null; + const expiresAt = duration ? new Date(Date.now() + duration) : null; - const confirm = new ButtonBuilder() - .setCustomId('confirm') - .setLabel('Confirm Warn') - .setStyle(ButtonStyle.Danger) - - const cancel = new ButtonBuilder() - .setCustomId('cancel') - .setLabel('Cancel') - .setStyle(ButtonStyle.Secondary) - - const row = new ActionRowBuilder().addComponents(cancel, confirm); - const response = await interaction.reply({ - content: `${emojis.rightArrow1} Are you sure you want to warn <@${targetMember.user.id}> with reason: ${reason}?`, - components: [row], - withResponse: true, - }); - - const collectorFilter = (i: { user: { id: string; }; }) => i.user.id === interaction.user.id; - try { - const confirmation = await response.resource!.message!.awaitMessageComponent({ filter: collectorFilter, time: 60_000 }); - - if (confirmation.customId === 'confirm') { - try { - await createWarn(interaction.guild.id, interaction.guild.name, targetMember.id, interaction.user.id, reason, expiresAt); - await targetMember.send( - `You have been warned in **${interaction.guild.name}**.\nReason: ${reason}${expiresAt ? `\nExpires: ` : ''}` - ).catch(() => {}); - await confirmation.update({ content: `${emojis.rightArrow2} <@${targetMember.id}> has been warned with reason: ${reason}`, components: [] }) - } catch (err) { - console.error(err) - await confirmation.update({ content: `${emojis.rightArrow2} Failed to warn <@${targetMember.id}>`, components: [] }) - } - } else if (confirmation.customId === 'cancel') { - await confirmation.update({ content: `${emojis.rightArrow2} Cancelled.`, components: [] }); - } - } catch(err) { - console.error(err) - await interaction.editReply({ content: `${emojis.rightArrow2} No response within a minute or errored.`, components: [] }); - } - }, + if (durationStr && (typeof duration !== 'number' || isNaN(duration))) { + await interaction.reply({ + content: `${emojis.rightArrow2} Invalid duration format.`, + flags: MessageFlags.Ephemeral, + }); + return; + } + + if (!targetMember) { + await interaction.reply({ + content: `${emojis.rightArrow2} That user is not in this server.`, + flags: MessageFlags.Ephemeral, + }); + return; + } + + if (targetMember.id === interaction.user.id) { + await interaction.reply({ + content: `${emojis.rightArrow2} You cannot warn yourself.`, + flags: MessageFlags.Ephemeral, + }); + return; + } + + if (targetMember.id === interaction.guild.ownerId) { + await interaction.reply({ + content: `${emojis.rightArrow2} You cannot warn the server owner.`, + flags: MessageFlags.Ephemeral, + }); + return; + } + + if (!targetMember.moderatable) { + await interaction.reply({ + content: `${emojis.rightArrow2} I cannot warn this user.`, + flags: MessageFlags.Ephemeral, + }); + return; + } + + const confirm = new ButtonBuilder().setCustomId('confirm').setLabel('Confirm Warn').setStyle(ButtonStyle.Danger); + + const cancel = new ButtonBuilder().setCustomId('cancel').setLabel('Cancel').setStyle(ButtonStyle.Secondary); + + const row = new ActionRowBuilder().addComponents(cancel, confirm); + const response = await interaction.reply({ + content: `${emojis.rightArrow1} Are you sure you want to warn <@${targetMember.user.id}> with reason: ${reason}?`, + components: [row], + withResponse: true, + }); + + const collectorFilter = (i: { user: { id: string } }) => i.user.id === interaction.user.id; + try { + const confirmation = await response.resource!.message!.awaitMessageComponent({ + filter: collectorFilter, + time: 60_000, + }); + + if (confirmation.customId === 'confirm') { + try { + await createWarn( + interaction.guild.id, + interaction.guild.name, + targetMember.id, + interaction.user.id, + reason, + expiresAt, + ); + await targetMember + .send( + `You have been warned in **${interaction.guild.name}**.\nReason: ${reason}${expiresAt ? `\nExpires: ` : ''}`, + ) + .catch(() => {}); + await confirmation.update({ + content: `${emojis.rightArrow2} <@${targetMember.id}> has been warned with reason: ${reason}`, + components: [], + }); + } catch (err) { + console.error(err); + await confirmation.update({ + content: `${emojis.rightArrow2} Failed to warn <@${targetMember.id}>`, + components: [], + }); + } + } else if (confirmation.customId === 'cancel') { + await confirmation.update({ content: `${emojis.rightArrow2} Cancelled.`, components: [] }); + } + } catch (err) { + console.error(err); + await interaction.editReply({ + content: `${emojis.rightArrow2} No response within a minute or errored.`, + components: [], + }); + } + }, } satisfies Command; From 2205b299ac56a07c3a65f6ab14bb946a0ccb4192 Mon Sep 17 00:00:00 2001 From: lunarash0 Date: Fri, 1 May 2026 13:43:43 +0200 Subject: [PATCH 5/5] upd: rename warns target to member --- src/commands/moderation/warns.ts | 100 ++++++++++++++++--------------- 1 file changed, 53 insertions(+), 47 deletions(-) diff --git a/src/commands/moderation/warns.ts b/src/commands/moderation/warns.ts index ffabec8..73074cb 100644 --- a/src/commands/moderation/warns.ts +++ b/src/commands/moderation/warns.ts @@ -4,51 +4,57 @@ import { emojis } from '#utils/emoji.js'; import { getWarns } from '#lib/warns.js'; export default { - data: { - name: 'warns', - description: 'View (someones) warns.', - options: [ - { - type: ApplicationCommandOptionType.User, - name: "target", - description: "User to view warns of", - } - - ] - }, - async execute(interaction) { - if (!interaction.inCachedGuild()) { - await interaction.reply({ content: `${emojis.rightArrow2} This command can only be used in a server.`, flags: MessageFlags.Ephemeral }); - return; - } - - const member = interaction.member as GuildMember; - const targetOption = interaction.options.getMember('target') as GuildMember - - if (targetOption && targetOption.id !== interaction.user.id) { - if (!member.permissions.has(PermissionsBitField.Flags.ModerateMembers)) { - await interaction.reply({ content: `${emojis.rightArrow2} You do not have permission to view other members' warns.`, flags: MessageFlags.Ephemeral }); - return; - } - } - - const targetMember = targetOption ?? member; - const active = await getWarns(interaction.guild.id, targetMember.id); - - if (active.length === 0) { - await interaction.reply({ content: `${emojis.rightArrow2} <@${targetMember.user.id}> has no active warns.`, flags: MessageFlags.Ephemeral }); - return; - } - - const lines = active.slice(0, 10).map(warn => { - const expires = warn.expiresAt - ? `` - : 'Never'; - return `${emojis.rightArrow2} **${warn.reason ?? 'No reason provided'}** by <@${warn.moderatorId}>\n Expires: ${expires} \`${warn.id}\``; - }); - - let content = `${emojis.rightArrow1} ${active.length} active warn(s) for <@${targetMember.user.id}>:\n${lines.join('\n')}`; - - await interaction.reply({ content }); - }, + data: { + name: 'warns', + description: 'View (someones) warns.', + options: [ + { + type: ApplicationCommandOptionType.User, + name: 'member', + description: 'Member to view warns of', + }, + ], + }, + async execute(interaction) { + if (!interaction.inCachedGuild()) { + await interaction.reply({ + content: `${emojis.rightArrow2} This command can only be used in a server.`, + flags: MessageFlags.Ephemeral, + }); + return; + } + + const member = interaction.member as GuildMember; + const targetOption = interaction.options.getMember('member') as GuildMember; + + if (targetOption && targetOption.id !== interaction.user.id) { + if (!member.permissions.has(PermissionsBitField.Flags.ModerateMembers)) { + await interaction.reply({ + content: `${emojis.rightArrow2} You do not have permission to view other members' warns.`, + flags: MessageFlags.Ephemeral, + }); + return; + } + } + + const targetMember = targetOption ?? member; + const active = await getWarns(interaction.guild.id, targetMember.id); + + if (active.length === 0) { + await interaction.reply({ + content: `${emojis.rightArrow2} <@${targetMember.user.id}> has no active warns.`, + flags: MessageFlags.Ephemeral, + }); + return; + } + + const lines = active.slice(0, 10).map((warn) => { + const expires = warn.expiresAt ? `` : 'Never'; + return `${emojis.rightArrow2} **${warn.reason ?? 'No reason provided'}** by <@${warn.moderatorId}>\n Expires: ${expires} \`${warn.id}\``; + }); + + let content = `${emojis.rightArrow1} ${active.length} active warn(s) for <@${targetMember.user.id}>:\n${lines.join('\n')}`; + + await interaction.reply({ content }); + }, } satisfies Command;