From adbb5e939cebcf4c0479d66162377957f2a845af Mon Sep 17 00:00:00 2001 From: IRONM00N <64110067+IRONM00N@users.noreply.github.com> Date: Wed, 6 Jul 2022 22:10:58 +0200 Subject: feat(automod): unmute button --- src/lib/common/AutoMod.ts | 148 +++++++++++++++++++++++++------------- src/lib/common/util/Moderation.ts | 24 ++++++- 2 files changed, 123 insertions(+), 49 deletions(-) (limited to 'src/lib/common') diff --git a/src/lib/common/AutoMod.ts b/src/lib/common/AutoMod.ts index 970fecd..21bcb00 100644 --- a/src/lib/common/AutoMod.ts +++ b/src/lib/common/AutoMod.ts @@ -1,4 +1,4 @@ -import { banResponse, colors, emojis, format, formatError, Moderation } from '#lib'; +import { colors, emojis, format, formatError, Moderation, unmuteResponse } from '#lib'; import assert from 'assert'; import chalk from 'chalk'; import { @@ -10,8 +10,10 @@ import { PermissionFlagsBits, type ButtonInteraction, type Message, + type Snowflake, type TextChannel } from 'discord.js'; +import UnmuteCommand from '../../commands/moderation/unmute.js'; /** * Handles auto moderation functionality. @@ -165,21 +167,14 @@ export class AutoMod { .setDescription( `**User:** ${this.message.author} (${this.message.author.tag})\n**Sent From:** <#${this.message.channel.id}> [Jump to context](${this.message.url})` ) - .addFields([ - { name: 'Message Content', value: `${await this.message.client.utils.codeblock(this.message.content, 1024)}` } - ]) + .addFields({ + name: 'Message Content', + value: `${await this.message.client.utils.codeblock(this.message.content, 1024)}` + }) .setColor(color) .setTimestamp() ], - components: [ - new ActionRowBuilder().addComponents([ - new ButtonBuilder({ - style: ButtonStyle.Danger, - label: 'Ban User', - customId: `automod;ban;${this.message.author.id};everyone mention and scam phrase` - }) - ]) - ] + components: [this.buttons(this.message.author.id, 'everyone mention and scam phrase')] }); } } @@ -332,28 +327,33 @@ export class AutoMod { this.message.channel.id }> [Jump to context](${this.message.url})\n**Blacklisted Words:** ${offenses.map((o) => `\`${o.match}\``).join(', ')}` ) - .addFields([ - { name: 'Message Content', value: `${await this.message.client.utils.codeblock(this.message.content, 1024)}` } - ]) + .addFields({ + name: 'Message Content', + value: `${await this.message.client.utils.codeblock(this.message.content, 1024)}` + }) .setColor(color) .setTimestamp() .setAuthor({ name: this.message.author.tag, url: this.message.author.displayAvatarURL() }) ], - components: - highestOffence.severity >= 2 - ? [ - new ActionRowBuilder().addComponents([ - new ButtonBuilder({ - style: ButtonStyle.Danger, - label: 'Ban User', - customId: `automod;ban;${this.message.author.id};${highestOffence.reason}` - }) - ]) - ] - : undefined + components: highestOffence.severity >= 2 ? [this.buttons(this.message.author.id, highestOffence.reason)] : undefined }); } + private buttons(userId: Snowflake, reason: string): ActionRowBuilder { + return new ActionRowBuilder().addComponents( + new ButtonBuilder({ + style: ButtonStyle.Danger, + label: 'Ban User', + customId: `automod;ban;${userId};${reason}` + }), + new ButtonBuilder({ + style: ButtonStyle.Success, + label: 'Unmute User', + customId: `automod;unmute;${userId}` + }) + ); + } + /** * Handles the ban button in the automod log. * @param interaction The button interaction. @@ -364,23 +364,31 @@ export class AutoMod { content: `${emojis.error} You are missing the **Ban Members** permission.`, ephemeral: true }); - const [action, userId, reason] = interaction.customId.replace('automod;', '').split(';'); - switch (action) { - case 'ban': { - const victim = await interaction.guild!.members.fetch(userId).catch(() => null); - const moderator = - interaction.member instanceof GuildMember - ? interaction.member - : await interaction.guild!.members.fetch(interaction.user.id); + const [action, userId, reason] = interaction.customId.replace('automod;', '').split(';') as [ + 'ban' | 'unmute', + string, + string + ]; - const check = victim ? await Moderation.permissionCheck(moderator, victim, 'ban', true) : true; + if ((['ban', 'unmute'] as const).includes(action)) throw new TypeError(`Invalid automod button action: ${action}`); + + const victim = await interaction.guild!.members.fetch(userId).catch(() => null); + const moderator = + interaction.member instanceof GuildMember + ? interaction.member + : await interaction.guild!.members.fetch(interaction.user.id); - if (check !== true) + switch (action) { + case 'ban': { + if (!interaction.guild?.members.me?.permissions.has('BanMembers')) return interaction.reply({ - content: check, + content: `${emojis.error} I do not have permission to ${action} members.`, ephemeral: true }); + const check = victim ? await Moderation.permissionCheck(moderator, victim, 'ban', true) : true; + if (check !== true) return interaction.reply({ content: check, ephemeral: true }); + const result = await interaction.guild?.bushBan({ user: userId, reason, @@ -389,21 +397,65 @@ export class AutoMod { }); const victimUserFormatted = (await interaction.client.utils.resolveNonCachedUser(userId))?.tag ?? userId; - if (result === banResponse.SUCCESS) - return interaction.reply({ - content: `${emojis.success} Successfully banned **${victimUserFormatted}**.`, - ephemeral: true - }); - else if (result === banResponse.DM_ERROR) + + const content = (() => { + if (result === unmuteResponse.SUCCESS) { + return `${emojis.success} Successfully banned ${format.input(victimUserFormatted)}.`; + } else if (result === unmuteResponse.DM_ERROR) { + return `${emojis.warn} Banned ${format.input(victimUserFormatted)} however I could not send them a dm.`; + } else { + return `${emojis.error} Could not ban ${format.input(victimUserFormatted)}: \`${result}\` .`; + } + })(); + + return interaction.reply({ + content: content, + ephemeral: true + }); + } + + case 'unmute': { + if (!victim) return interaction.reply({ - content: `${emojis.warn} Banned ${victimUserFormatted} however I could not send them a dm.`, + content: `${emojis.error} Cannot find member, they may have left the server.`, ephemeral: true }); - else + + if (!interaction.guild) return interaction.reply({ - content: `${emojis.error} Could not ban **${victimUserFormatted}**: \`${result}\` .`, + content: `${emojis.error} This is weird, I don't seem to be in the server...`, ephemeral: true }); + + const check = await Moderation.permissionCheck(moderator, victim, 'unmute', true); + if (check !== true) return interaction.reply({ content: check, ephemeral: true }); + + const check2 = await Moderation.checkMutePermissions(interaction.guild); + if (check2 !== true) + return interaction.reply({ content: UnmuteCommand.formatCode('/', victim!, check2), ephemeral: true }); + + const result = await victim.bushUnmute({ + reason, + moderator: interaction.member as GuildMember, + evidence: (interaction.message as Message).url ?? undefined + }); + + const victimUserFormatted = victim.user.tag; + + const content = (() => { + if (result === unmuteResponse.SUCCESS) { + return `${emojis.success} Successfully unmuted ${format.input(victimUserFormatted)}.`; + } else if (result === unmuteResponse.DM_ERROR) { + return `${emojis.warn} Unmuted ${format.input(victimUserFormatted)} however I could not send them a dm.`; + } else { + return `${emojis.error} Could not unmute ${format.input(victimUserFormatted)}: \`${result}\` .`; + } + })(); + + return interaction.reply({ + content: content, + ephemeral: true + }); } } } diff --git a/src/lib/common/util/Moderation.ts b/src/lib/common/util/Moderation.ts index cb6b4db..fc01602 100644 --- a/src/lib/common/util/Moderation.ts +++ b/src/lib/common/util/Moderation.ts @@ -1,13 +1,16 @@ import { ActivePunishment, ActivePunishmentType, + baseMuteResponse, colors, emojis, format, Guild as GuildDB, humanizeDuration, ModLog, - type ModLogType + permissionsResponse, + type ModLogType, + type ValueOf } from '#lib'; import assert from 'assert'; import { @@ -118,6 +121,25 @@ export async function permissionCheck( return true; } +/** + * Performs permission checks that are required in order to (un)mute a member. + * @param guild The guild to check the mute permissions in. + * @returns A {@link MuteResponse} or true if nothing failed. + */ +export async function checkMutePermissions( + guild: Guild +): Promise | ValueOf | true> { + if (!guild.members.me!.permissions.has('ManageRoles')) return permissionsResponse.MISSING_PERMISSIONS; + const muteRoleID = await guild.getSetting('muteRole'); + if (!muteRoleID) return baseMuteResponse.NO_MUTE_ROLE; + const muteRole = guild.roles.cache.get(muteRoleID); + if (!muteRole) return baseMuteResponse.MUTE_ROLE_INVALID; + if (muteRole.position >= guild.members.me!.roles.highest.position || muteRole.managed) + return baseMuteResponse.MUTE_ROLE_NOT_MANAGEABLE; + + return true; +} + /** * Creates a modlog entry for a punishment. * @param options Options for creating a modlog entry. -- cgit