aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorIRONM00N <64110067+IRONM00N@users.noreply.github.com>2022-07-06 22:10:58 +0200
committerIRONM00N <64110067+IRONM00N@users.noreply.github.com>2022-07-06 22:10:58 +0200
commitadbb5e939cebcf4c0479d66162377957f2a845af (patch)
treee69b237c79fac2701f162fce6076ec1f85f617a9 /src
parent22b3a8af49dc16bf5d8f9c3ce48b4770505ea33b (diff)
downloadtanzanite-adbb5e939cebcf4c0479d66162377957f2a845af.tar.gz
tanzanite-adbb5e939cebcf4c0479d66162377957f2a845af.tar.bz2
tanzanite-adbb5e939cebcf4c0479d66162377957f2a845af.zip
feat(automod): unmute button
Diffstat (limited to 'src')
-rw-r--r--src/commands/moderation/ban.ts51
-rw-r--r--src/commands/moderation/block.ts51
-rw-r--r--src/commands/moderation/kick.ts43
-rw-r--r--src/commands/moderation/mute.ts60
-rw-r--r--src/commands/moderation/timeout.ts49
-rw-r--r--src/commands/moderation/unban.ts51
-rw-r--r--src/commands/moderation/unblock.ts53
-rw-r--r--src/commands/moderation/unmute.ts64
-rw-r--r--src/commands/moderation/untimeout.ts43
-rw-r--r--src/commands/moderation/warn.ts45
-rw-r--r--src/lib/common/AutoMod.ts148
-rw-r--r--src/lib/common/util/Moderation.ts24
-rw-r--r--src/lib/extensions/discord.js/ExtendedGuildMember.ts32
-rw-r--r--src/lib/utils/BushUtils.ts1
14 files changed, 416 insertions, 299 deletions
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<ButtonBuilder>().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<ButtonBuilder>().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<ButtonBuilder> {
+ return new ActionRowBuilder<ButtonBuilder>().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 {
@@ -119,6 +122,25 @@ export async function permissionCheck(
}
/**
+ * 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<typeof baseMuteResponse> | ValueOf<typeof permissionsResponse> | 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.
* @param getCaseNumber Whether or not to get the case number of the 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<MuteResponse> {
// 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<UnmuteResponse> {
// 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> = 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<T> = { [K in keyof T]: T[K] extends Record<string, any> ? StripPrivate<T[K]> : T[K] };
+export type ValueOf<T> = T[keyof T];
/**
* Capitalizes the first letter of the given text