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/commands/moderation/ban.ts | 51 +++---- src/commands/moderation/block.ts | 51 +++---- src/commands/moderation/kick.ts | 43 +++--- src/commands/moderation/mute.ts | 60 +++++---- src/commands/moderation/timeout.ts | 49 ++++--- src/commands/moderation/unban.ts | 51 +++---- src/commands/moderation/unblock.ts | 53 ++++---- src/commands/moderation/unmute.ts | 64 ++++----- src/commands/moderation/untimeout.ts | 43 +++--- src/commands/moderation/warn.ts | 45 ++++--- src/lib/common/AutoMod.ts | 148 ++++++++++++++------- src/lib/common/util/Moderation.ts | 24 +++- .../extensions/discord.js/ExtendedGuildMember.ts | 32 ++--- src/lib/utils/BushUtils.ts | 1 + 14 files changed, 416 insertions(+), 299 deletions(-) (limited to 'src') diff --git a/src/commands/moderation/ban.ts b/src/commands/moderation/ban.ts index 598fcaa..1b045aa 100644 --- a/src/commands/moderation/ban.ts +++ b/src/commands/moderation/ban.ts @@ -8,12 +8,13 @@ import { format, Moderation, type ArgType, + type BanResponse, type CommandMessage, type OptArgType, type SlashMessage } from '#lib'; import assert from 'assert'; -import { ApplicationCommandOptionType, PermissionFlagsBits } from 'discord.js'; +import { ApplicationCommandOptionType, PermissionFlagsBits, type User } from 'discord.js'; export default class BanCommand extends BushCommand { public constructor() { @@ -109,27 +110,31 @@ export default class BanCommand extends BushCommand { const responseCode = member ? await member.bushBan(opts) : await message.guild.bushBan({ user, ...opts }); - const responseMessage = (): string => { - const victim = format.input(user.tag); - switch (responseCode) { - case banResponse.ALREADY_BANNED: - return `${emojis.error} ${victim} is already banned.`; - case banResponse.MISSING_PERMISSIONS: - return `${emojis.error} Could not ban ${victim} because I am missing the **Ban Members** permission.`; - case banResponse.ACTION_ERROR: - return `${emojis.error} An error occurred while trying to ban ${victim}.`; - case banResponse.PUNISHMENT_ENTRY_ADD_ERROR: - return `${emojis.error} While banning ${victim}, there was an error creating a ban entry, please report this to my developers.`; - case banResponse.MODLOG_ERROR: - return `${emojis.error} While banning ${victim}, there was an error creating a modlog entry, please report this to my developers.`; - case banResponse.DM_ERROR: - return `${emojis.warn} Banned ${victim} however I could not send them a dm.`; - case banResponse.SUCCESS: - return `${emojis.success} Successfully banned ${victim}.`; - default: - return `${emojis.error} An error occurred: ${format.input(responseCode)}}`; - } - }; - return await message.util.reply({ content: responseMessage(), allowedMentions: AllowedMentions.none() }); + return await message.util.reply({ + content: BanCommand.formatCode(user, responseCode), + allowedMentions: AllowedMentions.none() + }); + } + + public static formatCode(user: User, code: BanResponse): string { + const victim = format.input(user.tag); + switch (code) { + case banResponse.ALREADY_BANNED: + return `${emojis.error} ${victim} is already banned.`; + case banResponse.MISSING_PERMISSIONS: + return `${emojis.error} Could not ban ${victim} because I am missing the **Ban Members** permission.`; + case banResponse.ACTION_ERROR: + return `${emojis.error} An error occurred while trying to ban ${victim}.`; + case banResponse.PUNISHMENT_ENTRY_ADD_ERROR: + return `${emojis.error} While banning ${victim}, there was an error creating a ban entry, please report this to my developers.`; + case banResponse.MODLOG_ERROR: + return `${emojis.error} While banning ${victim}, there was an error creating a modlog entry, please report this to my developers.`; + case banResponse.DM_ERROR: + return `${emojis.warn} Banned ${victim} however I could not send them a dm.`; + case banResponse.SUCCESS: + return `${emojis.success} Successfully banned ${victim}.`; + default: + return `${emojis.error} An error occurred: ${format.input(code)}}`; + } } } diff --git a/src/commands/moderation/block.ts b/src/commands/moderation/block.ts index fc93fb1..48436eb 100644 --- a/src/commands/moderation/block.ts +++ b/src/commands/moderation/block.ts @@ -9,12 +9,13 @@ import { Moderation, userGuildPermCheck, type ArgType, + type BlockResponse, type CommandMessage, type OptArgType, type SlashMessage } from '#lib'; import assert from 'assert'; -import { ApplicationCommandOptionType, PermissionFlagsBits } from 'discord.js'; +import { ApplicationCommandOptionType, PermissionFlagsBits, type GuildMember } from 'discord.js'; export default class BlockCommand extends BushCommand { public constructor() { @@ -97,27 +98,31 @@ export default class BlockCommand extends BushCommand { channel: message.channel }); - const responseMessage = (): string => { - const victim = format.input(member.user.tag); - switch (responseCode) { - case blockResponse.MISSING_PERMISSIONS: - return `${emojis.error} Could not block ${victim} because I am missing the **Manage Channel** permission.`; - case blockResponse.INVALID_CHANNEL: - return `${emojis.error} Could not block ${victim}, you can only block users in text or thread channels.`; - case blockResponse.ACTION_ERROR: - return `${emojis.error} An unknown error occurred while trying to block ${victim}.`; - case blockResponse.MODLOG_ERROR: - return `${emojis.error} There was an error creating a modlog entry, please report this to my developers.`; - case blockResponse.PUNISHMENT_ENTRY_ADD_ERROR: - return `${emojis.error} There was an error creating a punishment entry, please report this to my developers.`; - case blockResponse.DM_ERROR: - return `${emojis.warn} Blocked ${victim} however I could not send them a dm.`; - case blockResponse.SUCCESS: - return `${emojis.success} Successfully blocked ${victim}.`; - default: - return `${emojis.error} An error occurred: ${format.input(responseCode)}}`; - } - }; - return await message.util.reply({ content: responseMessage(), allowedMentions: AllowedMentions.none() }); + return await message.util.reply({ + content: BlockCommand.formatCode(member, responseCode), + allowedMentions: AllowedMentions.none() + }); + } + + public static formatCode(member: GuildMember, code: BlockResponse): string { + const victim = format.input(member.user.tag); + switch (code) { + case blockResponse.MISSING_PERMISSIONS: + return `${emojis.error} Could not block ${victim} because I am missing the **Manage Channel** permission.`; + case blockResponse.INVALID_CHANNEL: + return `${emojis.error} Could not block ${victim}, you can only block users in text or thread channels.`; + case blockResponse.ACTION_ERROR: + return `${emojis.error} An unknown error occurred while trying to block ${victim}.`; + case blockResponse.MODLOG_ERROR: + return `${emojis.error} There was an error creating a modlog entry, please report this to my developers.`; + case blockResponse.PUNISHMENT_ENTRY_ADD_ERROR: + return `${emojis.error} There was an error creating a punishment entry, please report this to my developers.`; + case blockResponse.DM_ERROR: + return `${emojis.warn} Blocked ${victim} however I could not send them a dm.`; + case blockResponse.SUCCESS: + return `${emojis.success} Successfully blocked ${victim}.`; + default: + return `${emojis.error} An error occurred: ${format.input(code)}}`; + } } } diff --git a/src/commands/moderation/kick.ts b/src/commands/moderation/kick.ts index bf079f3..df14271 100644 --- a/src/commands/moderation/kick.ts +++ b/src/commands/moderation/kick.ts @@ -8,11 +8,12 @@ import { Moderation, type ArgType, type CommandMessage, + type KickResponse, type OptArgType, type SlashMessage } from '#lib'; import assert from 'assert'; -import { ApplicationCommandOptionType, PermissionFlagsBits } from 'discord.js'; +import { ApplicationCommandOptionType, PermissionFlagsBits, type GuildMember } from 'discord.js'; export default class KickCommand extends BushCommand { public constructor() { @@ -81,23 +82,27 @@ export default class KickCommand extends BushCommand { moderator: message.member }); - const responseMessage = (): string => { - const victim = format.input(member.user.tag); - switch (responseCode) { - case kickResponse.MISSING_PERMISSIONS: - return `${emojis.error} Could not kick ${victim} because I am missing the **Kick Members** permission.`; - case kickResponse.ACTION_ERROR: - return `${emojis.error} An error occurred while trying to kick ${victim}.`; - case kickResponse.MODLOG_ERROR: - return `${emojis.error} While muting ${victim}, there was an error creating a modlog entry, please report this to my developers.`; - case kickResponse.DM_ERROR: - return `${emojis.warn} Kicked ${victim} however I could not send them a dm.`; - case kickResponse.SUCCESS: - return `${emojis.success} Successfully kicked ${victim}.`; - default: - return `${emojis.error} An error occurred: ${format.input(responseCode)}}`; - } - }; - return await message.util.reply({ content: responseMessage(), allowedMentions: AllowedMentions.none() }); + return await message.util.reply({ + content: KickCommand.formatCode(member, responseCode), + allowedMentions: AllowedMentions.none() + }); + } + + public static formatCode(member: GuildMember, code: KickResponse): string { + const victim = format.input(member.user.tag); + switch (code) { + case kickResponse.MISSING_PERMISSIONS: + return `${emojis.error} Could not kick ${victim} because I am missing the **Kick Members** permission.`; + case kickResponse.ACTION_ERROR: + return `${emojis.error} An error occurred while trying to kick ${victim}.`; + case kickResponse.MODLOG_ERROR: + return `${emojis.error} While muting ${victim}, there was an error creating a modlog entry, please report this to my developers.`; + case kickResponse.DM_ERROR: + return `${emojis.warn} Kicked ${victim} however I could not send them a dm.`; + case kickResponse.SUCCESS: + return `${emojis.success} Successfully kicked ${victim}.`; + default: + return `${emojis.error} An error occurred: ${format.input(code)}}`; + } } } diff --git a/src/commands/moderation/mute.ts b/src/commands/moderation/mute.ts index 12b94d6..5502a84 100644 --- a/src/commands/moderation/mute.ts +++ b/src/commands/moderation/mute.ts @@ -10,11 +10,12 @@ import { userGuildPermCheck, type ArgType, type CommandMessage, + type MuteResponse, type OptArgType, type SlashMessage } from '#lib'; import assert from 'assert'; -import { ApplicationCommandOptionType, PermissionFlagsBits } from 'discord.js'; +import { ApplicationCommandOptionType, PermissionFlagsBits, type GuildMember } from 'discord.js'; export default class MuteCommand extends BushCommand { public constructor() { @@ -91,32 +92,35 @@ export default class MuteCommand extends BushCommand { duration }); - const responseMessage = (): string => { - const prefix_ = this.client.utils.prefix(message); - const victim = format.input(member.user.tag); - switch (responseCode) { - case muteResponse.MISSING_PERMISSIONS: - return `${emojis.error} Could not mute ${victim} because I am missing the **Manage Roles** permission.`; - case muteResponse.NO_MUTE_ROLE: - return `${emojis.error} Could not mute ${victim}, you must set a mute role with \`${prefix_}config muteRole\`.`; - case muteResponse.MUTE_ROLE_INVALID: - return `${emojis.error} Could not mute ${victim} because the current mute role no longer exists. Please set a new mute role with \`${prefix_}config muteRole\`.`; - case muteResponse.MUTE_ROLE_NOT_MANAGEABLE: - return `${emojis.error} Could not mute ${victim} because I cannot assign the current mute role, either change the role's position or set a new mute role with \`${prefix_}config muteRole\`.`; - case muteResponse.ACTION_ERROR: - return `${emojis.error} Could not mute ${victim}, there was an error assigning them the mute role.`; - case muteResponse.MODLOG_ERROR: - return `${emojis.error} There was an error creating a modlog entry, please report this to my developers.`; - case muteResponse.PUNISHMENT_ENTRY_ADD_ERROR: - return `${emojis.error} There was an error creating a punishment entry, please report this to my developers.`; - case muteResponse.DM_ERROR: - return `${emojis.warn} Muted ${victim} however I could not send them a dm.`; - case muteResponse.SUCCESS: - return `${emojis.success} Successfully muted ${victim}.`; - default: - return `${emojis.error} An error occurred: ${format.input(responseCode)}}`; - } - }; - return await message.util.reply({ content: responseMessage(), allowedMentions: AllowedMentions.none() }); + return await message.util.reply({ + content: MuteCommand.formatCode(this.client.utils.prefix(message), member, responseCode), + allowedMentions: AllowedMentions.none() + }); + } + + public static formatCode(prefix: string, member: GuildMember, code: MuteResponse): string { + const victim = format.input(member.user.tag); + switch (code) { + case muteResponse.MISSING_PERMISSIONS: + return `${emojis.error} Could not mute ${victim} because I am missing the **Manage Roles** permission.`; + case muteResponse.NO_MUTE_ROLE: + return `${emojis.error} Could not mute ${victim}, you must set a mute role with \`${prefix}config muteRole\`.`; + case muteResponse.MUTE_ROLE_INVALID: + return `${emojis.error} Could not mute ${victim} because the current mute role no longer exists. Please set a new mute role with \`${prefix}config muteRole\`.`; + case muteResponse.MUTE_ROLE_NOT_MANAGEABLE: + return `${emojis.error} Could not mute ${victim} because I cannot assign the current mute role, either change the role's position or set a new mute role with \`${prefix}config muteRole\`.`; + case muteResponse.ACTION_ERROR: + return `${emojis.error} Could not mute ${victim}, there was an error assigning them the mute role.`; + case muteResponse.MODLOG_ERROR: + return `${emojis.error} There was an error creating a modlog entry, please report this to my developers.`; + case muteResponse.PUNISHMENT_ENTRY_ADD_ERROR: + return `${emojis.error} There was an error creating a punishment entry, please report this to my developers.`; + case muteResponse.DM_ERROR: + return `${emojis.warn} Muted ${victim} however I could not send them a dm.`; + case muteResponse.SUCCESS: + return `${emojis.success} Successfully muted ${victim}.`; + default: + return `${emojis.error} An error occurred: ${format.input(code)}}`; + } } } diff --git a/src/commands/moderation/timeout.ts b/src/commands/moderation/timeout.ts index 7be8ecb..1ceedf9 100644 --- a/src/commands/moderation/timeout.ts +++ b/src/commands/moderation/timeout.ts @@ -9,10 +9,11 @@ import { timeoutResponse, type ArgType, type CommandMessage, - type SlashMessage + type SlashMessage, + type TimeoutResponse } from '#lib'; import assert from 'assert'; -import { ApplicationCommandOptionType, PermissionFlagsBits } from 'discord.js'; +import { ApplicationCommandOptionType, PermissionFlagsBits, type GuildMember } from 'discord.js'; export default class TimeoutCommand extends BushCommand { public constructor() { @@ -85,25 +86,29 @@ export default class TimeoutCommand extends BushCommand { duration: duration }); - const responseMessage = (): string => { - const victim = format.input(member.user.tag); - switch (responseCode) { - case timeoutResponse.MISSING_PERMISSIONS: - return `${emojis.error} Could not timeout ${victim} because I am missing the **Timeout Members** permission.`; - case timeoutResponse.INVALID_DURATION: - return `${emojis.error} The duration you specified is too long, the longest you can timeout someone for is 28 days.`; - case timeoutResponse.ACTION_ERROR: - return `${emojis.error} An unknown error occurred while trying to timeout ${victim}.`; - case timeoutResponse.MODLOG_ERROR: - return `${emojis.error} There was an error creating a modlog entry, please report this to my developers.`; - case timeoutResponse.DM_ERROR: - return `${emojis.warn} Timed out ${victim} however I could not send them a dm.`; - case timeoutResponse.SUCCESS: - return `${emojis.success} Successfully timed out ${victim}.`; - default: - return `${emojis.error} An error occurred: ${format.input(responseCode)}}`; - } - }; - return await message.util.reply({ content: responseMessage(), allowedMentions: AllowedMentions.none() }); + return await message.util.reply({ + content: TimeoutCommand.formatCode(member, responseCode), + allowedMentions: AllowedMentions.none() + }); + } + + public static formatCode(member: GuildMember, code: TimeoutResponse): string { + const victim = format.input(member.user.tag); + switch (code) { + case timeoutResponse.MISSING_PERMISSIONS: + return `${emojis.error} Could not timeout ${victim} because I am missing the **Timeout Members** permission.`; + case timeoutResponse.INVALID_DURATION: + return `${emojis.error} The duration you specified is too long, the longest you can timeout someone for is 28 days.`; + case timeoutResponse.ACTION_ERROR: + return `${emojis.error} An unknown error occurred while trying to timeout ${victim}.`; + case timeoutResponse.MODLOG_ERROR: + return `${emojis.error} There was an error creating a modlog entry, please report this to my developers.`; + case timeoutResponse.DM_ERROR: + return `${emojis.warn} Timed out ${victim} however I could not send them a dm.`; + case timeoutResponse.SUCCESS: + return `${emojis.success} Successfully timed out ${victim}.`; + default: + return `${emojis.error} An error occurred: ${format.input(code)}}`; + } } } diff --git a/src/commands/moderation/unban.ts b/src/commands/moderation/unban.ts index a4c4992..c102434 100644 --- a/src/commands/moderation/unban.ts +++ b/src/commands/moderation/unban.ts @@ -8,10 +8,11 @@ import { type ArgType, type CommandMessage, type OptArgType, - type SlashMessage + type SlashMessage, + type UnbanResponse } from '#lib'; import assert from 'assert'; -import { ApplicationCommandOptionType, PermissionFlagsBits } from 'discord.js'; +import { ApplicationCommandOptionType, PermissionFlagsBits, type User } from 'discord.js'; export default class UnbanCommand extends BushCommand { public constructor() { @@ -60,26 +61,30 @@ export default class UnbanCommand extends BushCommand { reason }); - const responseMessage = (): string => { - const victim = format.input(user.tag); - switch (responseCode) { - case unbanResponse.MISSING_PERMISSIONS: - return `${emojis.error} Could not unban ${victim} because I am missing the **Ban Members** permission.`; - case unbanResponse.ACTION_ERROR: - return `${emojis.error} An error occurred while trying to unban ${victim}.`; - case unbanResponse.PUNISHMENT_ENTRY_REMOVE_ERROR: - return `${emojis.error} While unbanning ${victim}, there was an error removing their ban entry, please report this to my developers.`; - case unbanResponse.MODLOG_ERROR: - return `${emojis.error} While unbanning ${victim}, there was an error creating a modlog entry, please report this to my developers.`; - case unbanResponse.NOT_BANNED: - return `${emojis.warn} ${victim} is not banned but I tried to unban them anyways.`; - case unbanResponse.DM_ERROR: - case unbanResponse.SUCCESS: - return `${emojis.success} Successfully unbanned ${victim}.`; - default: - return `${emojis.error} An error occurred: ${format.input(responseCode)}}`; - } - }; - return await message.util.reply({ content: responseMessage(), allowedMentions: AllowedMentions.none() }); + return await message.util.reply({ + content: UnbanCommand.formatCode(user, responseCode), + allowedMentions: AllowedMentions.none() + }); + } + + public static formatCode(user: User, code: UnbanResponse): string { + const victim = format.input(user.tag); + switch (code) { + case unbanResponse.MISSING_PERMISSIONS: + return `${emojis.error} Could not unban ${victim} because I am missing the **Ban Members** permission.`; + case unbanResponse.ACTION_ERROR: + return `${emojis.error} An error occurred while trying to unban ${victim}.`; + case unbanResponse.PUNISHMENT_ENTRY_REMOVE_ERROR: + return `${emojis.error} While unbanning ${victim}, there was an error removing their ban entry, please report this to my developers.`; + case unbanResponse.MODLOG_ERROR: + return `${emojis.error} While unbanning ${victim}, there was an error creating a modlog entry, please report this to my developers.`; + case unbanResponse.NOT_BANNED: + return `${emojis.warn} ${victim} is not banned but I tried to unban them anyways.`; + case unbanResponse.DM_ERROR: + case unbanResponse.SUCCESS: + return `${emojis.success} Successfully unbanned ${victim}.`; + default: + return `${emojis.error} An error occurred: ${format.input(code)}}`; + } } } diff --git a/src/commands/moderation/unblock.ts b/src/commands/moderation/unblock.ts index 09ec281..f8a57f8 100644 --- a/src/commands/moderation/unblock.ts +++ b/src/commands/moderation/unblock.ts @@ -10,10 +10,11 @@ import { type ArgType, type CommandMessage, type OptArgType, - type SlashMessage + type SlashMessage, + type UnblockResponse } from '#lib'; import assert from 'assert'; -import { ApplicationCommandOptionType, PermissionFlagsBits } from 'discord.js'; +import { ApplicationCommandOptionType, PermissionFlagsBits, type GuildMember } from 'discord.js'; export default class UnblockCommand extends BushCommand { public constructor() { @@ -88,27 +89,31 @@ export default class UnblockCommand extends BushCommand { channel: message.channel }); - const responseMessage = (): string => { - const victim = format.input(member.user.tag); - switch (responseCode) { - case unblockResponse.MISSING_PERMISSIONS: - return `${emojis.error} Could not unblock ${victim} because I am missing the **Manage Channel** permission.`; - case unblockResponse.INVALID_CHANNEL: - return `${emojis.error} Could not unblock ${victim}, you can only unblock users in text or thread channels.`; - case unblockResponse.ACTION_ERROR: - return `${emojis.error} An unknown error occurred while trying to unblock ${victim}.`; - case unblockResponse.MODLOG_ERROR: - return `${emojis.error} There was an error creating a modlog entry, please report this to my developers.`; - case unblockResponse.PUNISHMENT_ENTRY_REMOVE_ERROR: - return `${emojis.error} There was an error creating a punishment entry, please report this to my developers.`; - case unblockResponse.DM_ERROR: - return `${emojis.warn} Unblocked ${victim} however I could not send them a dm.`; - case unblockResponse.SUCCESS: - return `${emojis.success} Successfully unblocked ${victim}.`; - default: - return `${emojis.error} An error occurred: ${format.input(responseCode)}}`; - } - }; - return await message.util.reply({ content: responseMessage(), allowedMentions: AllowedMentions.none() }); + return await message.util.reply({ + content: UnblockCommand.formatCode(member, responseCode), + allowedMentions: AllowedMentions.none() + }); + } + + public static formatCode(member: GuildMember, code: UnblockResponse): string { + const victim = format.input(member.user.tag); + switch (code) { + case unblockResponse.MISSING_PERMISSIONS: + return `${emojis.error} Could not unblock ${victim} because I am missing the **Manage Channel** permission.`; + case unblockResponse.INVALID_CHANNEL: + return `${emojis.error} Could not unblock ${victim}, you can only unblock users in text or thread channels.`; + case unblockResponse.ACTION_ERROR: + return `${emojis.error} An unknown error occurred while trying to unblock ${victim}.`; + case unblockResponse.MODLOG_ERROR: + return `${emojis.error} There was an error creating a modlog entry, please report this to my developers.`; + case unblockResponse.PUNISHMENT_ENTRY_REMOVE_ERROR: + return `${emojis.error} There was an error creating a punishment entry, please report this to my developers.`; + case unblockResponse.DM_ERROR: + return `${emojis.warn} Unblocked ${victim} however I could not send them a dm.`; + case unblockResponse.SUCCESS: + return `${emojis.success} Successfully unblocked ${victim}.`; + default: + return `${emojis.error} An error occurred: ${format.input(code)}}`; + } } } diff --git a/src/commands/moderation/unmute.ts b/src/commands/moderation/unmute.ts index 08497c7..6452453 100644 --- a/src/commands/moderation/unmute.ts +++ b/src/commands/moderation/unmute.ts @@ -10,10 +10,11 @@ import { type ArgType, type CommandMessage, type OptArgType, - type SlashMessage + type SlashMessage, + type UnmuteResponse } from '#lib'; import assert from 'assert'; -import { ApplicationCommandOptionType, PermissionFlagsBits } from 'discord.js'; +import { ApplicationCommandOptionType, PermissionFlagsBits, type GuildMember } from 'discord.js'; export default class UnmuteCommand extends BushCommand { public constructor() { @@ -67,7 +68,6 @@ export default class UnmuteCommand extends BushCommand { assert(message.inGuild()); assert(message.member); - const error = emojis.error; const member = message.guild.members.cache.get(user.id)!; const useForce = force && message.author.isOwner(); @@ -82,32 +82,36 @@ export default class UnmuteCommand extends BushCommand { moderator: message.member }); - const responseMessage = (): string => { - const prefix_ = this.client.utils.prefix(message); - const victim = format.input(member.user.tag); - switch (responseCode) { - case unmuteResponse.MISSING_PERMISSIONS: - return `${error} Could not unmute ${victim} because I am missing the **Manage Roles** permission.`; - case unmuteResponse.NO_MUTE_ROLE: - return `${error} Could not unmute ${victim}, you must set a mute role with \`${prefix_}config muteRole\`.`; - case unmuteResponse.MUTE_ROLE_INVALID: - return `${error} Could not unmute ${victim} because the current mute role no longer exists. Please set a new mute role with \`${prefix_}config muteRole\`.`; - case unmuteResponse.MUTE_ROLE_NOT_MANAGEABLE: - return `${error} Could not unmute ${victim} because I cannot assign the current mute role, either change the role's position or set a new mute role with \`${prefix_}config muteRole\`.`; - case unmuteResponse.ACTION_ERROR: - return `${error} Could not unmute ${victim}, there was an error removing their mute role.`; - case unmuteResponse.MODLOG_ERROR: - return `${error} While muting ${victim}, there was an error creating a modlog entry, please report this to my developers.`; - case unmuteResponse.PUNISHMENT_ENTRY_REMOVE_ERROR: - return `${error} While muting ${victim}, there was an error removing their mute entry, please report this to my developers.`; - case unmuteResponse.DM_ERROR: - return `${emojis.warn} unmuted ${victim} however I could not send them a dm.`; - case unmuteResponse.SUCCESS: - return `${emojis.success} Successfully unmuted ${victim}.`; - default: - return `${emojis.error} An error occurred: ${format.input(responseCode)}}`; - } - }; - return await message.util.reply({ content: responseMessage(), allowedMentions: AllowedMentions.none() }); + return await message.util.reply({ + content: UnmuteCommand.formatCode(member.client.utils.prefix(message), member, responseCode), + allowedMentions: AllowedMentions.none() + }); + } + + public static formatCode(prefix: string, member: GuildMember, code: UnmuteResponse): string { + const error = emojis.error; + const victim = format.input(member.user.tag); + switch (code) { + case unmuteResponse.MISSING_PERMISSIONS: + return `${error} Could not unmute ${victim} because I am missing the **Manage Roles** permission.`; + case unmuteResponse.NO_MUTE_ROLE: + return `${error} Could not unmute ${victim}, you must set a mute role with \`${prefix}config muteRole\`.`; + case unmuteResponse.MUTE_ROLE_INVALID: + return `${error} Could not unmute ${victim} because the current mute role no longer exists. Please set a new mute role with \`${prefix}config muteRole\`.`; + case unmuteResponse.MUTE_ROLE_NOT_MANAGEABLE: + return `${error} Could not unmute ${victim} because I cannot assign the current mute role, either change the role's position or set a new mute role with \`${prefix}config muteRole\`.`; + case unmuteResponse.ACTION_ERROR: + return `${error} Could not unmute ${victim}, there was an error removing their mute role.`; + case unmuteResponse.MODLOG_ERROR: + return `${error} While muting ${victim}, there was an error creating a modlog entry, please report this to my developers.`; + case unmuteResponse.PUNISHMENT_ENTRY_REMOVE_ERROR: + return `${error} While muting ${victim}, there was an error removing their mute entry, please report this to my developers.`; + case unmuteResponse.DM_ERROR: + return `${emojis.warn} unmuted ${victim} however I could not send them a dm.`; + case unmuteResponse.SUCCESS: + return `${emojis.success} Successfully unmuted ${victim}.`; + default: + return `${emojis.error} An error occurred: ${format.input(code)}}`; + } } } diff --git a/src/commands/moderation/untimeout.ts b/src/commands/moderation/untimeout.ts index c6860c5..aa6665d 100644 --- a/src/commands/moderation/untimeout.ts +++ b/src/commands/moderation/untimeout.ts @@ -9,10 +9,11 @@ import { type ArgType, type CommandMessage, type OptArgType, + type RemoveTimeoutResponse, type SlashMessage } from '#lib'; import assert from 'assert'; -import { ApplicationCommandOptionType, PermissionFlagsBits } from 'discord.js'; +import { ApplicationCommandOptionType, PermissionFlagsBits, type GuildMember } from 'discord.js'; export default class UntimeoutCommand extends BushCommand { public constructor() { @@ -84,23 +85,27 @@ export default class UntimeoutCommand extends BushCommand { moderator: message.member }); - const responseMessage = (): string => { - const victim = format.input(member.user.tag); - switch (responseCode) { - case removeTimeoutResponse.MISSING_PERMISSIONS: - return `${emojis.error} Could not untimeout ${victim} because I am missing the **Timeout Members** permission.`; - case removeTimeoutResponse.ACTION_ERROR: - return `${emojis.error} An unknown error occurred while trying to timeout ${victim}.`; - case removeTimeoutResponse.MODLOG_ERROR: - return `${emojis.error} There was an error creating a modlog entry, please report this to my developers.`; - case removeTimeoutResponse.DM_ERROR: - return `${emojis.warn} Removed ${victim}'s timeout however I could not send them a dm.`; - case removeTimeoutResponse.SUCCESS: - return `${emojis.success} Successfully removed ${victim}'s timeout.`; - default: - return `${emojis.error} An error occurred: ${format.input(responseCode)}}`; - } - }; - return await message.util.reply({ content: responseMessage(), allowedMentions: AllowedMentions.none() }); + return await message.util.reply({ + content: UntimeoutCommand.formatCode(member, responseCode), + allowedMentions: AllowedMentions.none() + }); + } + + public static formatCode(member: GuildMember, code: RemoveTimeoutResponse): string { + const victim = format.input(member.user.tag); + switch (code) { + case removeTimeoutResponse.MISSING_PERMISSIONS: + return `${emojis.error} Could not untimeout ${victim} because I am missing the **Timeout Members** permission.`; + case removeTimeoutResponse.ACTION_ERROR: + return `${emojis.error} An unknown error occurred while trying to timeout ${victim}.`; + case removeTimeoutResponse.MODLOG_ERROR: + return `${emojis.error} There was an error creating a modlog entry, please report this to my developers.`; + case removeTimeoutResponse.DM_ERROR: + return `${emojis.warn} Removed ${victim}'s timeout however I could not send them a dm.`; + case removeTimeoutResponse.SUCCESS: + return `${emojis.success} Successfully removed ${victim}'s timeout.`; + default: + return `${emojis.error} An error occurred: ${format.input(code)}}`; + } } } diff --git a/src/commands/moderation/warn.ts b/src/commands/moderation/warn.ts index 81b2937..442cddc 100644 --- a/src/commands/moderation/warn.ts +++ b/src/commands/moderation/warn.ts @@ -11,10 +11,11 @@ import { type ArgType, type CommandMessage, type OptArgType, - type SlashMessage + type SlashMessage, + type WarnResponse } from '#lib'; import assert from 'assert'; -import { ApplicationCommandOptionType, PermissionFlagsBits } from 'discord.js'; +import { ApplicationCommandOptionType, PermissionFlagsBits, type GuildMember } from 'discord.js'; export default class WarnCommand extends BushCommand { public constructor() { @@ -76,27 +77,31 @@ export default class WarnCommand extends BushCommand { return message.util.reply(canModerateResponse); } - const { result: response, caseNum } = await member.bushWarn({ + const { result: responseCode, caseNum } = await member.bushWarn({ reason, moderator: message.member }); - const responseMessage = (): string => { - const victim = format.input(member.user.tag); - switch (response) { - case warnResponse.MODLOG_ERROR: - return `${emojis.error} While warning ${victim}, there was an error creating a modlog entry, please report this to my developers.`; - case warnResponse.ACTION_ERROR: - case warnResponse.DM_ERROR: - return `${emojis.warn} ${victim} has been warned for the ${ordinal( - caseNum ?? 0 - )} time, however I could not send them a dm.`; - case warnResponse.SUCCESS: - return `${emojis.success} Successfully warned ${victim} for the ${ordinal(caseNum ?? 0)} time.`; - default: - return `${emojis.error} An error occurred: ${format.input(response)}}`; - } - }; - return await message.util.reply({ content: responseMessage(), allowedMentions: AllowedMentions.none() }); + return await message.util.reply({ + content: WarnCommand.formatCode(caseNum, member, responseCode), + allowedMentions: AllowedMentions.none() + }); + } + + public static formatCode(caseNum: number | null, member: GuildMember, code: WarnResponse): string { + const victim = format.input(member.user.tag); + switch (code) { + case warnResponse.MODLOG_ERROR: + return `${emojis.error} While warning ${victim}, there was an error creating a modlog entry, please report this to my developers.`; + case warnResponse.ACTION_ERROR: + case warnResponse.DM_ERROR: + return `${emojis.warn} ${victim} has been warned for the ${ordinal( + caseNum ?? 0 + )} time, however I could not send them a dm.`; + case warnResponse.SUCCESS: + return `${emojis.success} Successfully warned ${victim} for the ${ordinal(caseNum ?? 0)} time.`; + default: + return `${emojis.error} An error occurred: ${format.input(code)}}`; + } } } 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. diff --git a/src/lib/extensions/discord.js/ExtendedGuildMember.ts b/src/lib/extensions/discord.js/ExtendedGuildMember.ts index 947f9cd..f8add83 100644 --- a/src/lib/extensions/discord.js/ExtendedGuildMember.ts +++ b/src/lib/extensions/discord.js/ExtendedGuildMember.ts @@ -1,11 +1,11 @@ /* eslint-disable @typescript-eslint/no-unused-vars */ -import { BushClientEvents, formatError, Moderation, ModLogType, PunishmentTypeDM, Time } from '#lib'; +import { formatError, Moderation, ModLogType, Time, type BushClientEvents, type PunishmentTypeDM, type ValueOf } from '#lib'; import { ChannelType, - GuildChannelResolvable, GuildMember, - GuildTextBasedChannel, PermissionFlagsBits, + type GuildChannelResolvable, + type GuildTextBasedChannel, type Role } from 'discord.js'; /* eslint-enable @typescript-eslint/no-unused-vars */ @@ -358,13 +358,11 @@ export class ExtendedGuildMember extends GuildMember { */ public override async bushMute(options: BushTimedPunishmentOptions): Promise { // checks - if (!this.guild.members.me!.permissions.has(PermissionFlagsBits.ManageRoles)) return muteResponse.MISSING_PERMISSIONS; - const muteRoleID = await this.guild.getSetting('muteRole'); - if (!muteRoleID) return muteResponse.NO_MUTE_ROLE; - const muteRole = this.guild.roles.cache.get(muteRoleID); - if (!muteRole) return muteResponse.MUTE_ROLE_INVALID; - if (muteRole.position >= this.guild.members.me!.roles.highest.position || muteRole.managed) - return muteResponse.MUTE_ROLE_NOT_MANAGEABLE; + const checks = await Moderation.checkMutePermissions(this.guild); + if (checks !== true) return checks; + + const muteRoleID = (await this.guild.getSetting('muteRole'))!; + const muteRole = this.guild.roles.cache.get(muteRoleID)!; let caseID: string | undefined = undefined; let dmSuccessEvent: boolean | undefined = undefined; @@ -446,13 +444,11 @@ export class ExtendedGuildMember extends GuildMember { */ public override async bushUnmute(options: BushPunishmentOptions): Promise { // checks - if (!this.guild.members.me!.permissions.has(PermissionFlagsBits.ManageRoles)) return unmuteResponse.MISSING_PERMISSIONS; - const muteRoleID = await this.guild.getSetting('muteRole'); - if (!muteRoleID) return unmuteResponse.NO_MUTE_ROLE; - const muteRole = this.guild.roles.cache.get(muteRoleID); - if (!muteRole) return unmuteResponse.MUTE_ROLE_INVALID; - if (muteRole.position >= this.guild.members.me!.roles.highest.position || muteRole.managed) - return unmuteResponse.MUTE_ROLE_NOT_MANAGEABLE; + const checks = await Moderation.checkMutePermissions(this.guild); + if (checks !== true) return checks; + + const muteRoleID = (await this.guild.getSetting('muteRole'))!; + const muteRole = this.guild.roles.cache.get(muteRoleID)!; let caseID: string | undefined = undefined; let dmSuccessEvent: boolean | undefined = undefined; @@ -1090,8 +1086,6 @@ export interface BushTimeoutOptions extends BushPunishmentOptions { duration: number; } -type ValueOf = T[keyof T]; - export const basePunishmentResponse = Object.freeze({ SUCCESS: 'success', MODLOG_ERROR: 'error creating modlog entry', diff --git a/src/lib/utils/BushUtils.ts b/src/lib/utils/BushUtils.ts index a6463cf..059d001 100644 --- a/src/lib/utils/BushUtils.ts +++ b/src/lib/utils/BushUtils.ts @@ -32,6 +32,7 @@ import { inspect as inspectUtil, promisify } from 'util'; import * as Format from '../common/util/Format.js'; export type StripPrivate = { [K in keyof T]: T[K] extends Record ? StripPrivate : T[K] }; +export type ValueOf = T[keyof T]; /** * Capitalizes the first letter of the given text -- cgit