diff --git a/bots/discord/src/commands/fun/reply.ts b/bots/discord/src/commands/fun/reply.ts index a68c849..73fe802 100644 --- a/bots/discord/src/commands/fun/reply.ts +++ b/bots/discord/src/commands/fun/reply.ts @@ -1,5 +1,6 @@ import { SlashCommandBuilder, type TextBasedChannel } from 'discord.js' +import { config } from '$/context' import type { Command } from '..' export default { @@ -16,7 +17,7 @@ export default { .toJSON(), memberRequirements: { - roles: ['955220417969262612', '973886585294704640'], + roles: config.moderation?.roles ?? [], }, global: false, diff --git a/bots/discord/src/commands/moderation/ban.ts b/bots/discord/src/commands/moderation/ban.ts index ee5ec14..42d8f57 100644 --- a/bots/discord/src/commands/moderation/ban.ts +++ b/bots/discord/src/commands/moderation/ban.ts @@ -2,8 +2,10 @@ import { SlashCommandBuilder } from 'discord.js' import type { Command } from '..' +import CommandError, { CommandErrorType } from '$/classes/CommandError' import { config } from '$/context' -import { applyReferenceToModerationActionEmbed, createModerationActionEmbed } from '$/utils/discord/embeds' +import { createModerationActionEmbed } from '$/utils/discord/embeds' +import { sendModerationReplyAndLogs } from '$/utils/discord/moderation' import { parseDuration } from '$/utils/duration' export default { @@ -23,27 +25,34 @@ export default { global: false, - async execute({ config, logger }, interaction) { - const user = interaction.options.getUser('member', true) - const reason = interaction.options.getString('reason') ?? undefined + async execute({ logger }, interaction) { + const user = interaction.options.getUser('user', true) + const reason = interaction.options.getString('reason') ?? 'No reason provided' const dmd = interaction.options.getString('dmd') + const member = await interaction.guild!.members.fetch(user.id) + const moderator = await interaction.guild!.members.fetch(interaction.user.id) + + if (member.bannable) throw new CommandError(CommandErrorType.Generic, 'This user cannot be banned by the bot.') + + if (moderator.roles.highest.comparePositionTo(member.roles.highest) <= 0) + throw new CommandError( + CommandErrorType.InvalidUser, + 'You cannot ban a user with a role equal to or higher than yours.', + ) + const dms = Math.floor(dmd ? parseDuration(dmd) : 0 / 1000) await interaction.guild!.members.ban(user, { reason: `Banned by moderator ${interaction.user.tag} (${interaction.user.id}): ${reason}`, deleteMessageSeconds: dms, }) - const embed = createModerationActionEmbed('Banned', user, interaction.user, reason ?? 'No reason provided') - const reply = await interaction.reply({ embeds: [embed] }).then(it => it.fetch()) - - const logConfig = config.moderation?.log - if (logConfig) { - const channel = await interaction.guild!.channels.fetch(logConfig.thread ?? logConfig.channel) - if (!channel || !channel.isTextBased()) - return void logger.warn('The moderation log channel does not exist, skipping logging') - - await channel.send({ embeds: [applyReferenceToModerationActionEmbed(embed, reply.url)] }) - } + await sendModerationReplyAndLogs( + interaction, + createModerationActionEmbed('Banned', user, interaction.user, reason), + ) + logger.info( + `${interaction.user.tag} (${interaction.user.id}) banned ${user.tag} (${user.id}) because ${reason}, deleting their messages sent in the previous ${dms}s`, + ) }, } satisfies Command diff --git a/bots/discord/src/commands/moderation/mute.ts b/bots/discord/src/commands/moderation/mute.ts index f4ebd28..8328c6f 100644 --- a/bots/discord/src/commands/moderation/mute.ts +++ b/bots/discord/src/commands/moderation/mute.ts @@ -5,7 +5,8 @@ import { applyRolePreset } from '$/utils/discord/rolePresets' import type { Command } from '..' import { config } from '$/context' -import { applyReferenceToModerationActionEmbed, createModerationActionEmbed } from '$/utils/discord/embeds' +import { createModerationActionEmbed } from '$/utils/discord/embeds' +import { sendModerationReplyAndLogs } from '$/utils/discord/moderation' import { parseDuration } from '$/utils/duration' export default { @@ -23,9 +24,9 @@ export default { global: false, - async execute({ config, logger }, interaction) { + async execute({ logger }, interaction) { const user = interaction.options.getUser('member', true) - const reason = interaction.options.getString('reason') + const reason = interaction.options.getString('reason') ?? 'No reason provided' const duration = interaction.options.getString('duration') const durationMs = duration ? parseDuration(duration) : null @@ -35,6 +36,8 @@ export default { 'The duration must be at least 1 millisecond long.', ) + const expires = durationMs ? Date.now() + durationMs : null + const moderator = await interaction.guild!.members.fetch(interaction.user.id) const member = await interaction.guild!.members.fetch(user.id) if (!member) throw new CommandError( @@ -42,25 +45,23 @@ export default { 'The provided member is not in the server or does not exist.', ) - await applyRolePreset(member, 'mute', durationMs ? Date.now() + durationMs : null) + if (member.manageable) + throw new CommandError(CommandErrorType.Generic, 'This user cannot be managed by the bot.') - const embed = createModerationActionEmbed( - 'Muted', - user, - interaction.user, - reason ?? 'No reason provided', - durationMs, + if (moderator.roles.highest.comparePositionTo(member.roles.highest) <= 0) + throw new CommandError( + CommandErrorType.InvalidUser, + 'You cannot mute a user with a role equal to or higher than yours.', + ) + + await applyRolePreset(member, 'mute', durationMs ? Date.now() + durationMs : null) + await sendModerationReplyAndLogs( + interaction, + createModerationActionEmbed('Muted', user, interaction.user, reason, durationMs), ) - const reply = await interaction.reply({ embeds: [embed] }).then(it => it.fetch()) - - const logConfig = config.moderation?.log - if (logConfig) { - const channel = await interaction.guild!.channels.fetch(logConfig.thread ?? logConfig.channel) - if (!channel || !channel.isTextBased()) - return void logger.warn('The moderation log channel does not exist, skipping logging') - - await channel.send({ embeds: [applyReferenceToModerationActionEmbed(embed, reply.url)] }) - } + logger.info( + `Moderator ${interaction.user.tag} (${interaction.user.id}) muted ${user.tag} (${user.id}) until ${expires} because ${reason}`, + ) }, } satisfies Command diff --git a/bots/discord/src/commands/moderation/slowmode.ts b/bots/discord/src/commands/moderation/slowmode.ts index eb5534d..61b49da 100644 --- a/bots/discord/src/commands/moderation/slowmode.ts +++ b/bots/discord/src/commands/moderation/slowmode.ts @@ -48,12 +48,18 @@ export default { logger.info(`Setting slowmode to ${duration}ms on ${channel.id}`) - await channel.setRateLimitPerUser( - duration / 1000, - `Slowmode set by @${interaction.user.username} (${interaction.user.id})`, - ) + await channel.setRateLimitPerUser(duration / 1000, `Set by ${interaction.user.tag} (${interaction.user.id})`) + await interaction.reply({ - embeds: [createSuccessEmbed(`Slowmode set to ${durationToString(duration)} on ${channel.toString()}`)], + embeds: [ + createSuccessEmbed( + `Slowmode ${duration ? `set to ${durationToString(duration)}` : 'removed'} on ${channel.toString()}`, + ), + ], }) + + logger.info( + `${interaction.user.tag} (${interaction.user.id}) set the slowmode on ${channel.name} (${channel.id}) to ${duration}ms`, + ) }, } satisfies Command diff --git a/bots/discord/src/commands/moderation/unban.ts b/bots/discord/src/commands/moderation/unban.ts index 0e9946f..9adbe89 100644 --- a/bots/discord/src/commands/moderation/unban.ts +++ b/bots/discord/src/commands/moderation/unban.ts @@ -3,7 +3,8 @@ import { SlashCommandBuilder } from 'discord.js' import type { Command } from '..' import { config } from '$/context' -import { applyReferenceToModerationActionEmbed, createModerationActionEmbed } from '$/utils/discord/embeds' +import { createModerationActionEmbed } from '$/utils/discord/embeds' +import { sendModerationReplyAndLogs } from '$/utils/discord/moderation' export default { data: new SlashCommandBuilder() @@ -18,24 +19,15 @@ export default { global: false, - async execute({ config, logger }, interaction) { - const user = interaction.options.getUser('member', true) + async execute({ logger }, interaction) { + const user = interaction.options.getUser('user', true) await interaction.guild!.members.unban( user, `Unbanned by moderator ${interaction.user.tag} (${interaction.user.id})`, ) - const embed = createModerationActionEmbed('Unbanned', user, interaction.user) - const reply = await interaction.reply({ embeds: [embed] }).then(it => it.fetch()) - - const logConfig = config.moderation?.log - if (logConfig) { - const channel = await interaction.guild!.channels.fetch(logConfig.thread ?? logConfig.channel) - if (!channel || !channel.isTextBased()) - return void logger.warn('The moderation log channel does not exist, skipping logging') - - await channel.send({ embeds: [applyReferenceToModerationActionEmbed(embed, reply.url)] }) - } + await sendModerationReplyAndLogs(interaction, createModerationActionEmbed('Unbanned', user, interaction.user)) + logger.info(`${interaction.user.tag} (${interaction.user.id}) unbanned ${user.tag} (${user.id})`) }, } satisfies Command diff --git a/bots/discord/src/commands/moderation/unmute.ts b/bots/discord/src/commands/moderation/unmute.ts index 41c37df..29860ea 100644 --- a/bots/discord/src/commands/moderation/unmute.ts +++ b/bots/discord/src/commands/moderation/unmute.ts @@ -2,8 +2,11 @@ import { SlashCommandBuilder } from 'discord.js' import CommandError, { CommandErrorType } from '$/classes/CommandError' import { config } from '$/context' -import { applyReferenceToModerationActionEmbed, createModerationActionEmbed } from '$/utils/discord/embeds' +import { appliedPresets } from '$/database/schemas' +import { createModerationActionEmbed } from '$/utils/discord/embeds' +import { sendModerationReplyAndLogs } from '$/utils/discord/moderation' import { removeRolePreset } from '$/utils/discord/rolePresets' +import { and, eq } from 'drizzle-orm' import type { Command } from '..' export default { @@ -19,7 +22,7 @@ export default { global: false, - async execute({ config, logger }, interaction) { + async execute({ logger, database }, interaction) { const user = interaction.options.getUser('member', true) const member = await interaction.guild!.members.fetch(user.id) if (!member) @@ -28,18 +31,16 @@ export default { 'The provided member is not in the server or does not exist.', ) + if ( + !(await database.query.appliedPresets.findFirst({ + where: and(eq(appliedPresets.memberId, member.id), eq(appliedPresets.preset, 'mute')), + })) + ) + throw new CommandError(CommandErrorType.Generic, 'This user is not muted.') + await removeRolePreset(member, 'mute') - const embed = createModerationActionEmbed('Unmuted', user, interaction.user) + await sendModerationReplyAndLogs(interaction, createModerationActionEmbed('Unmuted', user, interaction.user)) - const reply = await interaction.reply({ embeds: [embed] }).then(it => it.fetch()) - - const logConfig = config.moderation?.log - if (logConfig) { - const channel = await interaction.guild!.channels.fetch(logConfig.thread ?? logConfig.channel) - if (!channel || !channel.isTextBased()) - return void logger.warn('The moderation log channel does not exist, skipping logging') - - await channel.send({ embeds: [applyReferenceToModerationActionEmbed(embed, reply.url)] }) - } + logger.info(`Moderator ${interaction.user.tag} (${interaction.user.id}) unmuted ${user.tag} (${user.id})`) }, } satisfies Command diff --git a/bots/discord/src/utils/discord/modLogs.ts b/bots/discord/src/utils/discord/modLogs.ts deleted file mode 100644 index e69de29..0000000