aboutsummaryrefslogtreecommitdiff
path: root/src/lib/common
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib/common')
-rw-r--r--src/lib/common/AutoMod.ts51
-rw-r--r--src/lib/common/ButtonPaginator.ts47
-rw-r--r--src/lib/common/ConfirmationPrompt.ts27
-rw-r--r--src/lib/common/DeleteButton.ts27
-rw-r--r--src/lib/common/HighlightManager.ts16
-rw-r--r--src/lib/common/Sentry.ts2
-rw-r--r--src/lib/common/util/Arg.ts269
-rw-r--r--src/lib/common/util/Format.ts187
-rw-r--r--src/lib/common/util/Moderation.ts517
9 files changed, 566 insertions, 577 deletions
diff --git a/src/lib/common/AutoMod.ts b/src/lib/common/AutoMod.ts
index 982e0e8..7f19e63 100644
--- a/src/lib/common/AutoMod.ts
+++ b/src/lib/common/AutoMod.ts
@@ -1,4 +1,4 @@
-import { banResponse, Moderation } from '#lib';
+import { banResponse, codeblock, colors, emojis, format, formatError, getShared, Moderation, resolveNonCachedUser } from '#lib';
import assert from 'assert';
import chalk from 'chalk';
import {
@@ -18,11 +18,6 @@ import {
*/
export class AutoMod {
/**
- * The message to check for blacklisted phrases on
- */
- private message: Message;
-
- /**
* Whether or not a punishment has already been given to the user
*/
private punished = false;
@@ -30,8 +25,12 @@ export class AutoMod {
/**
* @param message The message to check and potentially perform automod actions to
*/
- public constructor(message: Message) {
- this.message = message;
+ public constructor(
+ /**
+ * The message to check for blacklisted phrases on
+ */
+ private message: Message
+ ) {
if (message.author.id === client.user?.id) return;
void this.handle();
}
@@ -57,9 +56,9 @@ export class AutoMod {
traditional: {
if (this.isImmune) break traditional;
- const badLinksArray = util.getShared('badLinks');
- const badLinksSecretArray = util.getShared('badLinksSecret');
- const badWordsRaw = util.getShared('badWords');
+ const badLinksArray = getShared('badLinks');
+ const badLinksSecretArray = getShared('badLinksSecret');
+ const badWordsRaw = getShared('badWords');
const customAutomodPhrases = (await this.message.guild.getSetting('autoModPhases')) ?? [];
const uniqueLinks = [...new Set([...badLinksArray, ...badLinksSecretArray])];
@@ -90,8 +89,8 @@ export class AutoMod {
embeds: [
{
title: 'AutoMod Error',
- description: `Unable to find severity information for ${util.format.inlineCode(highestOffence.match)}`,
- color: util.colors.error
+ description: `Unable to find severity information for ${format.inlineCode(highestOffence.match)}`,
+ color: colors.error
}
]
});
@@ -168,7 +167,7 @@ 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 util.codeblock(this.message.content, 1024)}` }])
+ .addFields([{ name: 'Message Content', value: `${await codeblock(this.message.content, 1024)}` }])
.setColor(color)
.setTimestamp()
],
@@ -252,13 +251,13 @@ export class AutoMod {
let color;
switch (highestOffence.severity) {
case Severity.DELETE: {
- color = util.colors.lightGray;
+ color = colors.lightGray;
void this.message.delete().catch((e) => deleteError.bind(this, e));
this.punished = true;
break;
}
case Severity.WARN: {
- color = util.colors.yellow;
+ color = colors.yellow;
void this.message.delete().catch((e) => deleteError.bind(this, e));
void this.message.member?.bushWarn({
moderator: this.message.guild!.members.me!,
@@ -268,7 +267,7 @@ export class AutoMod {
break;
}
case Severity.TEMP_MUTE: {
- color = util.colors.orange;
+ color = colors.orange;
void this.message.delete().catch((e) => deleteError.bind(this, e));
void this.message.member?.bushMute({
moderator: this.message.guild!.members.me!,
@@ -279,7 +278,7 @@ export class AutoMod {
break;
}
case Severity.PERM_MUTE: {
- color = util.colors.red;
+ color = colors.red;
void this.message.delete().catch((e) => deleteError.bind(this, e));
void this.message.member?.bushMute({
moderator: this.message.guild!.members.me!,
@@ -302,8 +301,8 @@ export class AutoMod {
{
title: 'AutoMod Error',
description: `Unable to delete triggered message.`,
- fields: [{ name: 'Error', value: await util.codeblock(`${util.formatError(e)}`, 1024, 'js', true) }],
- color: util.colors.error
+ fields: [{ name: 'Error', value: await codeblock(`${formatError(e)}`, 1024, 'js', true) }],
+ color: colors.error
}
]
});
@@ -333,7 +332,7 @@ export class AutoMod {
this.message.channel.id
}> [Jump to context](${this.message.url})\n**Blacklisted Words:** ${offences.map((o) => `\`${o.match}\``).join(', ')}`
)
- .addFields([{ name: 'Message Content', value: `${await util.codeblock(this.message.content, 1024)}` }])
+ .addFields([{ name: 'Message Content', value: `${await codeblock(this.message.content, 1024)}` }])
.setColor(color)
.setTimestamp()
.setAuthor({ name: this.message.author.tag, url: this.message.author.displayAvatarURL() })
@@ -360,7 +359,7 @@ export class AutoMod {
public static async handleInteraction(interaction: ButtonInteraction) {
if (!interaction.memberPermissions?.has(PermissionFlagsBits.BanMembers))
return interaction.reply({
- content: `${util.emojis.error} You are missing the **Ban Members** permission.`,
+ content: `${emojis.error} You are missing the **Ban Members** permission.`,
ephemeral: true
});
const [action, userId, reason] = interaction.customId.replace('automod;', '').split(';');
@@ -387,20 +386,20 @@ export class AutoMod {
evidence: (interaction.message as Message).url ?? undefined
});
- const victimUserFormatted = (await util.resolveNonCachedUser(userId))?.tag ?? userId;
+ const victimUserFormatted = (await resolveNonCachedUser(userId))?.tag ?? userId;
if (result === banResponse.SUCCESS)
return interaction.reply({
- content: `${util.emojis.success} Successfully banned **${victimUserFormatted}**.`,
+ content: `${emojis.success} Successfully banned **${victimUserFormatted}**.`,
ephemeral: true
});
else if (result === banResponse.DM_ERROR)
return interaction.reply({
- content: `${util.emojis.warn} Banned ${victimUserFormatted} however I could not send them a dm.`,
+ content: `${emojis.warn} Banned ${victimUserFormatted} however I could not send them a dm.`,
ephemeral: true
});
else
return interaction.reply({
- content: `${util.emojis.error} Could not ban **${victimUserFormatted}**: \`${result}\` .`,
+ content: `${emojis.error} Could not ban **${victimUserFormatted}**: \`${result}\` .`,
ephemeral: true
});
}
diff --git a/src/lib/common/ButtonPaginator.ts b/src/lib/common/ButtonPaginator.ts
index 64870cf..9560247 100644
--- a/src/lib/common/ButtonPaginator.ts
+++ b/src/lib/common/ButtonPaginator.ts
@@ -15,26 +15,6 @@ import {
*/
export class ButtonPaginator {
/**
- * The message that triggered the command
- */
- protected message: CommandMessage | SlashMessage;
-
- /**
- * The embeds to paginate
- */
- protected embeds: EmbedBuilder[] | APIEmbed[];
-
- /**
- * The optional text to send with the paginator
- */
- protected text: string | null;
-
- /**
- * Whether the paginator message gets deleted when the exit button is pressed
- */
- protected deleteOnExit: boolean;
-
- /**
* The current page of the paginator
*/
protected curPage: number;
@@ -52,16 +32,27 @@ export class ButtonPaginator {
* @param startOn The page to start from (**not** the index)
*/
protected constructor(
- message: CommandMessage | SlashMessage,
- embeds: EmbedBuilder[] | APIEmbed[],
- text: string | null,
- deleteOnExit: boolean,
+ /**
+ * The message that triggered the command
+ */
+ protected message: CommandMessage | SlashMessage,
+
+ /**
+ * The embeds to paginate
+ */
+ protected embeds: EmbedBuilder[] | APIEmbed[],
+
+ /**
+ * The optional text to send with the paginator
+ */
+ protected text: string | null,
+
+ /**
+ * Whether the paginator message gets deleted when the exit button is pressed
+ */
+ protected deleteOnExit: boolean,
startOn: number
) {
- this.message = message;
- this.embeds = embeds;
- this.text = text ? text : null;
- this.deleteOnExit = deleteOnExit;
this.curPage = startOn - 1;
// add footers
diff --git a/src/lib/common/ConfirmationPrompt.ts b/src/lib/common/ConfirmationPrompt.ts
index c95dbbc..4593d24 100644
--- a/src/lib/common/ConfirmationPrompt.ts
+++ b/src/lib/common/ConfirmationPrompt.ts
@@ -6,23 +6,20 @@ import { ActionRowBuilder, ButtonBuilder, ButtonStyle, type MessageComponentInte
*/
export class ConfirmationPrompt {
/**
- * Options for sending the message
- */
- protected messageOptions: MessageOptions;
-
- /**
- * The message that triggered the command
- */
- protected message: CommandMessage | SlashMessage;
-
- /**
* @param message The message to respond to
- * @param options The send message options
+ * @param messageOptions The send message options
*/
- protected constructor(message: CommandMessage | SlashMessage, messageOptions: MessageOptions) {
- this.message = message;
- this.messageOptions = messageOptions;
- }
+ protected constructor(
+ /**
+ * The message that triggered the command
+ */
+ protected message: CommandMessage | SlashMessage,
+
+ /**
+ * Options for sending the message
+ */
+ protected messageOptions: MessageOptions
+ ) {}
/**
* Sends a message with buttons for the user to confirm or cancel the action.
diff --git a/src/lib/common/DeleteButton.ts b/src/lib/common/DeleteButton.ts
index 91f4bfa..b561d94 100644
--- a/src/lib/common/DeleteButton.ts
+++ b/src/lib/common/DeleteButton.ts
@@ -15,23 +15,20 @@ import {
*/
export class DeleteButton {
/**
- * Options for sending the message
- */
- protected messageOptions: MessageOptions;
-
- /**
- * The message that triggered the command
- */
- protected message: CommandMessage | SlashMessage;
-
- /**
* @param message The message to respond to
- * @param options The send message options
+ * @param messageOptions The send message options
*/
- protected constructor(message: CommandMessage | SlashMessage, options: MessageOptions) {
- this.message = message;
- this.messageOptions = options;
- }
+ protected constructor(
+ /**
+ * The message that triggered the command
+ */
+ protected message: CommandMessage | SlashMessage,
+
+ /**
+ * Options for sending the message
+ */
+ protected messageOptions: MessageOptions
+ ) {}
/**
* Sends a message with a button for the user to delete it.
diff --git a/src/lib/common/HighlightManager.ts b/src/lib/common/HighlightManager.ts
index fdec322..caaa6a5 100644
--- a/src/lib/common/HighlightManager.ts
+++ b/src/lib/common/HighlightManager.ts
@@ -1,7 +1,7 @@
-import { Highlight, type HighlightWord } from '#lib';
+import { addToArray, format, Highlight, removeFromArray, timestamp, type HighlightWord } from '#lib';
import assert from 'assert';
import { Collection, type Message, type Snowflake } from 'discord.js';
-import { Time } from '../utils/BushConstants.js';
+import { colors, Time } from '../utils/BushConstants.js';
const NOTIFY_COOLDOWN = 5 * Time.Minute;
const OWNER_NOTIFY_COOLDOWN = 1 * Time.Minute;
@@ -162,7 +162,7 @@ export class HighlightManager {
if (highlight.words.some((w) => w.word === hl.word)) return `You have already highlighted "${hl.word}".`;
- highlight.words = util.addToArray(highlight.words, hl);
+ highlight.words = addToArray(highlight.words, hl);
return Boolean(await highlight.save().catch(() => false));
}
@@ -189,7 +189,7 @@ export class HighlightManager {
const toRemove = highlight.words.find((w) => w.word === hl);
if (!toRemove) return `Uhhhhh... This shouldn't happen.`;
- highlight.words = util.removeFromArray(highlight.words, toRemove);
+ highlight.words = removeFromArray(highlight.words, toRemove);
return Boolean(await highlight.save().catch(() => false));
}
@@ -271,20 +271,18 @@ export class HighlightManager {
return client.users
.send(user, {
// eslint-disable-next-line @typescript-eslint/no-base-to-string
- content: `In ${util.format.input(message.guild.name)} ${message.channel}, your highlight "${hl.word}" was matched:`,
+ content: `In ${format.input(message.guild.name)} ${message.channel}, your highlight "${hl.word}" was matched:`,
embeds: [
{
description: [...recentMessages, message]
.map(
(m) =>
- `${util.timestamp(m.createdAt, 't')} ${util.format.input(`${m.author.tag}:`)} ${m.cleanContent
- .trim()
- .substring(0, 512)}`
+ `${timestamp(m.createdAt, 't')} ${format.input(`${m.author.tag}:`)} ${m.cleanContent.trim().substring(0, 512)}`
)
.join('\n'),
author: { name: hl.regex ? `/${hl.word}/gi` : hl.word },
fields: [{ name: 'Source message', value: `[Jump to message](${message.url})` }],
- color: util.colors.default,
+ color: colors.default,
footer: { text: 'Triggered' },
timestamp: message.createdAt.toISOString()
}
diff --git a/src/lib/common/Sentry.ts b/src/lib/common/Sentry.ts
index e18555b..34bc06f 100644
--- a/src/lib/common/Sentry.ts
+++ b/src/lib/common/Sentry.ts
@@ -1,7 +1,7 @@
import { RewriteFrames } from '@sentry/integrations';
import * as SentryNode from '@sentry/node';
import { Integrations } from '@sentry/node';
-import config from './../../config/options.js';
+import config from '../../../config/options.js';
export class Sentry {
public constructor(rootdir: string) {
diff --git a/src/lib/common/util/Arg.ts b/src/lib/common/util/Arg.ts
index 51d8065..a7795b1 100644
--- a/src/lib/common/util/Arg.ts
+++ b/src/lib/common/util/Arg.ts
@@ -9,155 +9,150 @@ import { Argument, type Flag, type ParsedValuePredicate } from 'discord-akairo';
import { type Message } from 'discord.js';
/**
- * A wrapper for the {@link Argument} class that adds custom typings.
+ * Casts a phrase to this argument's type.
+ * @param type - The type to cast to.
+ * @param message - Message that called the command.
+ * @param phrase - Phrase to process.
*/
-export class Arg {
- /**
- * Casts a phrase to this argument's type.
- * @param type - The type to cast to.
- * @param message - Message that called the command.
- * @param phrase - Phrase to process.
- */
- public static async cast<T extends ATC>(type: T, message: CommandMessage | SlashMessage, phrase: string): Promise<ATCR<T>>;
- public static async cast<T extends KBAT>(type: T, message: CommandMessage | SlashMessage, phrase: string): Promise<BAT[T]>;
- public static async cast(type: AT | ATC, message: CommandMessage | SlashMessage, phrase: string): Promise<any>;
- public static async cast(type: ATC | AT, message: CommandMessage | SlashMessage, phrase: string): Promise<any> {
- return Argument.cast(type as any, client.commandHandler.resolver, message as Message, phrase);
- }
+export async function cast<T extends ATC>(type: T, message: CommandMessage | SlashMessage, phrase: string): Promise<ATCR<T>>;
+export async function cast<T extends KBAT>(type: T, message: CommandMessage | SlashMessage, phrase: string): Promise<BAT[T]>;
+export async function cast(type: AT | ATC, message: CommandMessage | SlashMessage, phrase: string): Promise<any>;
+export async function cast(type: ATC | AT, message: CommandMessage | SlashMessage, phrase: string): Promise<any> {
+ return Argument.cast(type as any, client.commandHandler.resolver, message as Message, phrase);
+}
- /**
- * Creates a type that is the left-to-right composition of the given types.
- * If any of the types fails, the entire composition fails.
- * @param types - Types to use.
- */
- public static compose<T extends ATC>(...types: T[]): ATCATCR<T>;
- public static compose<T extends KBAT>(...types: T[]): ATCBAT<T>;
- public static compose(...types: (AT | ATC)[]): ATC;
- public static compose(...types: (AT | ATC)[]): ATC {
- return Argument.compose(...(types as any));
- }
+/**
+ * Creates a type that is the left-to-right composition of the given types.
+ * If any of the types fails, the entire composition fails.
+ * @param types - Types to use.
+ */
+export function compose<T extends ATC>(...types: T[]): ATCATCR<T>;
+export function compose<T extends KBAT>(...types: T[]): ATCBAT<T>;
+export function compose(...types: (AT | ATC)[]): ATC;
+export function compose(...types: (AT | ATC)[]): ATC {
+ return Argument.compose(...(types as any));
+}
- /**
- * Creates a type that is the left-to-right composition of the given types.
- * If any of the types fails, the composition still continues with the failure passed on.
- * @param types - Types to use.
- */
- public static composeWithFailure<T extends ATC>(...types: T[]): ATCATCR<T>;
- public static composeWithFailure<T extends KBAT>(...types: T[]): ATCBAT<T>;
- public static composeWithFailure(...types: (AT | ATC)[]): ATC;
- public static composeWithFailure(...types: (AT | ATC)[]): ATC {
- return Argument.composeWithFailure(...(types as any));
- }
+/**
+ * Creates a type that is the left-to-right composition of the given types.
+ * If any of the types fails, the composition still continues with the failure passed on.
+ * @param types - Types to use.
+ */
+export function composeWithFailure<T extends ATC>(...types: T[]): ATCATCR<T>;
+export function composeWithFailure<T extends KBAT>(...types: T[]): ATCBAT<T>;
+export function composeWithFailure(...types: (AT | ATC)[]): ATC;
+export function composeWithFailure(...types: (AT | ATC)[]): ATC {
+ return Argument.composeWithFailure(...(types as any));
+}
- /**
- * Checks if something is null, undefined, or a fail flag.
- * @param value - Value to check.
- */
- public static isFailure(value: any): value is null | undefined | (Flag & { value: any }) {
- return Argument.isFailure(value);
- }
+/**
+ * Checks if something is null, undefined, or a fail flag.
+ * @param value - Value to check.
+ */
+export function isFailure(value: any): value is null | undefined | (Flag & { value: any }) {
+ return Argument.isFailure(value);
+}
- /**
- * Creates a type from multiple types (product type).
- * Only inputs where each type resolves with a non-void value are valid.
- * @param types - Types to use.
- */
- public static product<T extends ATC>(...types: T[]): ATCATCR<T>;
- public static product<T extends KBAT>(...types: T[]): ATCBAT<T>;
- public static product(...types: (AT | ATC)[]): ATC;
- public static product(...types: (AT | ATC)[]): ATC {
- return Argument.product(...(types as any));
- }
+/**
+ * Creates a type from multiple types (product type).
+ * Only inputs where each type resolves with a non-void value are valid.
+ * @param types - Types to use.
+ */
+export function product<T extends ATC>(...types: T[]): ATCATCR<T>;
+export function product<T extends KBAT>(...types: T[]): ATCBAT<T>;
+export function product(...types: (AT | ATC)[]): ATC;
+export function product(...types: (AT | ATC)[]): ATC {
+ return Argument.product(...(types as any));
+}
- /**
- * Creates a type where the parsed value must be within a range.
- * @param type - The type to use.
- * @param min - Minimum value.
- * @param max - Maximum value.
- * @param inclusive - Whether or not to be inclusive on the upper bound.
- */
- public static range<T extends ATC>(type: T, min: number, max: number, inclusive?: boolean): ATCATCR<T>;
- public static range<T extends KBAT>(type: T, min: number, max: number, inclusive?: boolean): ATCBAT<T>;
- public static range(type: AT | ATC, min: number, max: number, inclusive?: boolean): ATC;
- public static range(type: AT | ATC, min: number, max: number, inclusive?: boolean): ATC {
- return Argument.range(type as any, min, max, inclusive);
- }
+/**
+ * Creates a type where the parsed value must be within a range.
+ * @param type - The type to use.
+ * @param min - Minimum value.
+ * @param max - Maximum value.
+ * @param inclusive - Whether or not to be inclusive on the upper bound.
+ */
+export function range<T extends ATC>(type: T, min: number, max: number, inclusive?: boolean): ATCATCR<T>;
+export function range<T extends KBAT>(type: T, min: number, max: number, inclusive?: boolean): ATCBAT<T>;
+export function range(type: AT | ATC, min: number, max: number, inclusive?: boolean): ATC;
+export function range(type: AT | ATC, min: number, max: number, inclusive?: boolean): ATC {
+ return Argument.range(type as any, min, max, inclusive);
+}
- /**
- * Creates a type that parses as normal but also tags it with some data.
- * Result is in an object `{ tag, value }` and wrapped in `Flag.fail` when failed.
- * @param type - The type to use.
- * @param tag - Tag to add. Defaults to the `type` argument, so useful if it is a string.
- */
- public static tagged<T extends ATC>(type: T, tag?: any): ATCATCR<T>;
- public static tagged<T extends KBAT>(type: T, tag?: any): ATCBAT<T>;
- public static tagged(type: AT | ATC, tag?: any): ATC;
- public static tagged(type: AT | ATC, tag?: any): ATC {
- return Argument.tagged(type as any, tag);
- }
+/**
+ * Creates a type that parses as normal but also tags it with some data.
+ * Result is in an object `{ tag, value }` and wrapped in `Flag.fail` when failed.
+ * @param type - The type to use.
+ * @param tag - Tag to add. Defaults to the `type` argument, so useful if it is a string.
+ */
+export function tagged<T extends ATC>(type: T, tag?: any): ATCATCR<T>;
+export function tagged<T extends KBAT>(type: T, tag?: any): ATCBAT<T>;
+export function tagged(type: AT | ATC, tag?: any): ATC;
+export function tagged(type: AT | ATC, tag?: any): ATC {
+ return Argument.tagged(type as any, tag);
+}
- /**
- * Creates a type from multiple types (union type).
- * The first type that resolves to a non-void value is used.
- * Each type will also be tagged using `tagged` with themselves.
- * @param types - Types to use.
- */
- public static taggedUnion<T extends ATC>(...types: T[]): ATCATCR<T>;
- public static taggedUnion<T extends KBAT>(...types: T[]): ATCBAT<T>;
- public static taggedUnion(...types: (AT | ATC)[]): ATC;
- public static taggedUnion(...types: (AT | ATC)[]): ATC {
- return Argument.taggedUnion(...(types as any));
- }
+/**
+ * Creates a type from multiple types (union type).
+ * The first type that resolves to a non-void value is used.
+ * Each type will also be tagged using `tagged` with themselves.
+ * @param types - Types to use.
+ */
+export function taggedUnion<T extends ATC>(...types: T[]): ATCATCR<T>;
+export function taggedUnion<T extends KBAT>(...types: T[]): ATCBAT<T>;
+export function taggedUnion(...types: (AT | ATC)[]): ATC;
+export function taggedUnion(...types: (AT | ATC)[]): ATC {
+ return Argument.taggedUnion(...(types as any));
+}
- /**
- * Creates a type that parses as normal but also tags it with some data and carries the original input.
- * Result is in an object `{ tag, input, value }` and wrapped in `Flag.fail` when failed.
- * @param type - The type to use.
- * @param tag - Tag to add. Defaults to the `type` argument, so useful if it is a string.
- */
- public static taggedWithInput<T extends ATC>(type: T, tag?: any): ATCATCR<T>;
- public static taggedWithInput<T extends KBAT>(type: T, tag?: any): ATCBAT<T>;
- public static taggedWithInput(type: AT | ATC, tag?: any): ATC;
- public static taggedWithInput(type: AT | ATC, tag?: any): ATC {
- return Argument.taggedWithInput(type as any, tag);
- }
+/**
+ * Creates a type that parses as normal but also tags it with some data and carries the original input.
+ * Result is in an object `{ tag, input, value }` and wrapped in `Flag.fail` when failed.
+ * @param type - The type to use.
+ * @param tag - Tag to add. Defaults to the `type` argument, so useful if it is a string.
+ */
+export function taggedWithInput<T extends ATC>(type: T, tag?: any): ATCATCR<T>;
+export function taggedWithInput<T extends KBAT>(type: T, tag?: any): ATCBAT<T>;
+export function taggedWithInput(type: AT | ATC, tag?: any): ATC;
+export function taggedWithInput(type: AT | ATC, tag?: any): ATC {
+ return Argument.taggedWithInput(type as any, tag);
+}
- /**
- * Creates a type from multiple types (union type).
- * The first type that resolves to a non-void value is used.
- * @param types - Types to use.
- */
- public static union<T extends ATC>(...types: T[]): ATCATCR<T>;
- public static union<T extends KBAT>(...types: T[]): ATCBAT<T>;
- public static union(...types: (AT | ATC)[]): ATC;
- public static union(...types: (AT | ATC)[]): ATC {
- return Argument.union(...(types as any));
- }
+/**
+ * Creates a type from multiple types (union type).
+ * The first type that resolves to a non-void value is used.
+ * @param types - Types to use.
+ */
+export function union<T extends ATC>(...types: T[]): ATCATCR<T>;
+export function union<T extends KBAT>(...types: T[]): ATCBAT<T>;
+export function union(...types: (AT | ATC)[]): ATC;
+export function union(...types: (AT | ATC)[]): ATC {
+ return Argument.union(...(types as any));
+}
- /**
- * Creates a type with extra validation.
- * If the predicate is not true, the value is considered invalid.
- * @param type - The type to use.
- * @param predicate - The predicate function.
- */
- public static validate<T extends ATC>(type: T, predicate: ParsedValuePredicate): ATCATCR<T>;
- public static validate<T extends KBAT>(type: T, predicate: ParsedValuePredicate): ATCBAT<T>;
- public static validate(type: AT | ATC, predicate: ParsedValuePredicate): ATC;
- public static validate(type: AT | ATC, predicate: ParsedValuePredicate): ATC {
- return Argument.validate(type as any, predicate);
- }
+/**
+ * Creates a type with extra validation.
+ * If the predicate is not true, the value is considered invalid.
+ * @param type - The type to use.
+ * @param predicate - The predicate function.
+ */
+export function validate<T extends ATC>(type: T, predicate: ParsedValuePredicate): ATCATCR<T>;
+export function validate<T extends KBAT>(type: T, predicate: ParsedValuePredicate): ATCBAT<T>;
+export function validate(type: AT | ATC, predicate: ParsedValuePredicate): ATC;
+export function validate(type: AT | ATC, predicate: ParsedValuePredicate): ATC {
+ return Argument.validate(type as any, predicate);
+}
- /**
- * Creates a type that parses as normal but also carries the original input.
- * Result is in an object `{ input, value }` and wrapped in `Flag.fail` when failed.
- * @param type - The type to use.
- */
- public static withInput<T extends ATC>(type: T): ATC<ATCR<T>>;
- public static withInput<T extends KBAT>(type: T): ATCBAT<T>;
- public static withInput(type: AT | ATC): ATC;
- public static withInput(type: AT | ATC): ATC {
- return Argument.withInput(type as any);
- }
+/**
+ * Creates a type that parses as normal but also carries the original input.
+ * Result is in an object `{ input, value }` and wrapped in `Flag.fail` when failed.
+ * @param type - The type to use.
+ */
+export function withInput<T extends ATC>(type: T): ATC<ATCR<T>>;
+export function withInput<T extends KBAT>(type: T): ATCBAT<T>;
+export function withInput(type: AT | ATC): ATC;
+export function withInput(type: AT | ATC): ATC {
+ return Argument.withInput(type as any);
}
type BushArgumentTypeCasterReturn<R> = R extends BushArgumentTypeCaster<infer S> ? S : R;
diff --git a/src/lib/common/util/Format.ts b/src/lib/common/util/Format.ts
index 6cb6edc..260a0be 100644
--- a/src/lib/common/util/Format.ts
+++ b/src/lib/common/util/Format.ts
@@ -1,107 +1,112 @@
import { type CodeBlockLang } from '#lib';
-import { EscapeMarkdownOptions, Formatters, Util } from 'discord.js';
+import {
+ escapeBold,
+ escapeCodeBlock,
+ escapeInlineCode,
+ escapeItalic,
+ EscapeMarkdownOptions,
+ escapeSpoiler,
+ escapeStrikethrough,
+ escapeUnderline,
+ Formatters
+} from 'discord.js';
/**
- * Formats and escapes content for formatting
+ * Wraps the content inside a codeblock with no language.
+ * @param content The content to wrap.
*/
-export class Format {
- /**
- * Wraps the content inside a codeblock with no language.
- * @param content The content to wrap.
- */
- public static codeBlock(content: string): string;
+export function codeBlock(content: string): string;
- /**
- * Wraps the content inside a codeblock with the specified language.
- * @param language The language for the codeblock.
- * @param content The content to wrap.
- */
- public static codeBlock(language: CodeBlockLang, content: string): string;
- public static codeBlock(languageOrContent: string, content?: string): string {
- return typeof content === 'undefined'
- ? Formatters.codeBlock(Util.escapeCodeBlock(`${languageOrContent}`))
- : Formatters.codeBlock(`${languageOrContent}`, Util.escapeCodeBlock(`${content}`));
- }
+/**
+ * Wraps the content inside a codeblock with the specified language.
+ * @param language The language for the codeblock.
+ * @param content The content to wrap.
+ */
+export function codeBlock(language: CodeBlockLang, content: string): string;
+export function codeBlock(languageOrContent: string, content?: string): string {
+ return typeof content === 'undefined'
+ ? Formatters.codeBlock(escapeCodeBlock(`${languageOrContent}`))
+ : Formatters.codeBlock(`${languageOrContent}`, escapeCodeBlock(`${content}`));
+}
- /**
- * Wraps the content inside \`backticks\`, which formats it as inline code.
- * @param content The content to wrap.
- */
- public static inlineCode(content: string): string {
- return Formatters.inlineCode(Util.escapeInlineCode(`${content}`));
- }
+/**
+ * Wraps the content inside \`backticks\`, which formats it as inline code.
+ * @param content The content to wrap.
+ */
+export function inlineCode(content: string): string {
+ return Formatters.inlineCode(escapeInlineCode(`${content}`));
+}
- /**
- * Formats the content into italic text.
- * @param content The content to wrap.
- */
- public static italic(content: string): string {
- return Formatters.italic(Util.escapeItalic(`${content}`));
- }
+/**
+ * Formats the content into italic text.
+ * @param content The content to wrap.
+ */
+export function italic(content: string): string {
+ return Formatters.italic(escapeItalic(`${content}`));
+}
- /**
- * Formats the content into bold text.
- * @param content The content to wrap.
- */
- public static bold(content: string): string {
- return Formatters.bold(Util.escapeBold(`${content}`));
- }
+/**
+ * Formats the content into bold text.
+ * @param content The content to wrap.
+ */
+export function bold(content: string): string {
+ return Formatters.bold(escapeBold(`${content}`));
+}
- /**
- * Formats the content into underscored text.
- * @param content The content to wrap.
- */
- public static underscore(content: string): string {
- return Formatters.underscore(Util.escapeUnderline(`${content}`));
- }
+/**
+ * Formats the content into underscored text.
+ * @param content The content to wrap.
+ */
+export function underscore(content: string): string {
+ return Formatters.underscore(escapeUnderline(`${content}`));
+}
- /**
- * Formats the content into strike-through text.
- * @param content The content to wrap.
- */
- public static strikethrough(content: string): string {
- return Formatters.strikethrough(Util.escapeStrikethrough(`${content}`));
- }
+/**
+ * Formats the content into strike-through text.
+ * @param content The content to wrap.
+ */
+export function strikethrough(content: string): string {
+ return Formatters.strikethrough(escapeStrikethrough(`${content}`));
+}
- /**
- * Wraps the content inside spoiler (hidden text).
- * @param content The content to wrap.
- */
- public static spoiler(content: string): string {
- return Formatters.spoiler(Util.escapeSpoiler(`${content}`));
- }
+/**
+ * Wraps the content inside spoiler (hidden text).
+ * @param content The content to wrap.
+ */
+export function spoiler(content: string): string {
+ return Formatters.spoiler(escapeSpoiler(`${content}`));
+}
- /**
- * Escapes any Discord-flavour markdown in a string.
- * @param text Content to escape
- * @param options Options for escaping the markdown
- */
- public static escapeMarkdown(text: string, options?: EscapeMarkdownOptions): string {
- return Util.escapeMarkdown(`${text}`, options);
- }
+/**
+ * Escapes any Discord-flavour markdown in a string.
+ * @param text Content to escape
+ * @param options Options for escaping the markdown
+ */
+export function escapeMarkdown(text: string, options?: EscapeMarkdownOptions): string {
+ return escapeMarkdown(`${text}`, options);
+}
- /**
- * Formats input: makes it bold and escapes any other markdown
- * @param text The input
- */
- public static input(text: string): string {
- return this.bold(this.escapeMarkdown(this.sanitizeWtlAndControl(`${text}`)));
- }
+/**
+ * Formats input: makes it bold and escapes any other markdown
+ * @param text The input
+ */
+export function input(text: string): string {
+ return bold(escapeMarkdown(sanitizeWtlAndControl(`${text}`)));
+}
- /**
- * Formats input for logs: makes it highlighted
- * @param text The input
- */
- public static inputLog(text: string): string {
- return `<<${this.sanitizeWtlAndControl(`${text}`)}>>`;
- }
+/**
+ * Formats input for logs: makes it highlighted
+ * @param text The input
+ */
+export function inputLog(text: string): string {
+ return `<<${sanitizeWtlAndControl(`${text}`)}>>`;
+}
- /**
- * Removes all characters in a string that are either control characters or change the direction of text etc.
- * @param str The string you would like sanitized
- */
- public static sanitizeWtlAndControl(str: string) {
- // eslint-disable-next-line no-control-regex
- return `${str}`.replace(/[\u0000-\u001F\u007F-\u009F\u200B]/g, '');
- }
+/**
+ * Removes all characters in a string that are either control characters or change the direction of text etc.
+ * @param str The string you would like sanitized
+ */
+export function sanitizeWtlAndControl(str: string) {
+ // eslint-disable-next-line no-control-regex
+ return `${str}`.replace(/[\u0000-\u001F\u007F-\u009F\u200B]/g, '');
}
diff --git a/src/lib/common/util/Moderation.ts b/src/lib/common/util/Moderation.ts
index 6cdc141..a08dfa4 100644
--- a/src/lib/common/util/Moderation.ts
+++ b/src/lib/common/util/Moderation.ts
@@ -1,4 +1,16 @@
-import { ActivePunishment, ActivePunishmentType, Guild as GuildDB, ModLog, type ModLogType } from '#lib';
+import {
+ ActivePunishment,
+ ActivePunishmentType,
+ colors,
+ emojis,
+ format,
+ Guild as GuildDB,
+ handleError,
+ humanizeDuration,
+ ModLog,
+ resolveNonCachedUser,
+ type ModLogType
+} from '#lib';
import assert from 'assert';
import {
ActionRowBuilder,
@@ -40,275 +52,270 @@ enum reversedPunishMap {
}
/**
- * A utility class with moderation-related methods.
+ * Checks if a moderator can perform a moderation action on another user.
+ * @param moderator The person trying to perform the action.
+ * @param victim The person getting punished.
+ * @param type The type of punishment - used to format the response.
+ * @param checkModerator Whether or not to check if the victim is a moderator.
+ * @param force Override permissions checks.
+ * @returns `true` if the moderator can perform the action otherwise a reason why they can't.
*/
-export class Moderation {
- /**
- * Checks if a moderator can perform a moderation action on another user.
- * @param moderator The person trying to perform the action.
- * @param victim The person getting punished.
- * @param type The type of punishment - used to format the response.
- * @param checkModerator Whether or not to check if the victim is a moderator.
- * @param force Override permissions checks.
- * @returns `true` if the moderator can perform the action otherwise a reason why they can't.
- */
- public static async permissionCheck(
- moderator: GuildMember,
- victim: GuildMember,
- type:
- | 'mute'
- | 'unmute'
- | 'warn'
- | 'kick'
- | 'ban'
- | 'unban'
- | 'add a punishment role to'
- | 'remove a punishment role from'
- | 'block'
- | 'unblock'
- | 'timeout'
- | 'untimeout',
- checkModerator = true,
- force = false
- ): Promise<true | string> {
- if (force) return true;
-
- // If the victim is not in the guild anymore it will be undefined
- if ((!victim || !victim.guild) && !['ban', 'unban'].includes(type)) return true;
-
- if (moderator.guild.id !== victim.guild.id) {
- throw new Error('moderator and victim not in same guild');
- }
-
- const isOwner = moderator.guild.ownerId === moderator.id;
- if (moderator.id === victim.id && !type.startsWith('un')) {
- return `${util.emojis.error} You cannot ${type} yourself.`;
- }
- if (
- moderator.roles.highest.position <= victim.roles.highest.position &&
- !isOwner &&
- !(type.startsWith('un') && moderator.id === victim.id)
- ) {
- return `${util.emojis.error} You cannot ${type} **${victim.user.tag}** because they have higher or equal role hierarchy as you do.`;
- }
- if (
- victim.roles.highest.position >= victim.guild.members.me!.roles.highest.position &&
- !(type.startsWith('un') && moderator.id === victim.id)
- ) {
- return `${util.emojis.error} You cannot ${type} **${victim.user.tag}** because they have higher or equal role hierarchy as I do.`;
- }
- if (
- checkModerator &&
- victim.permissions.has(PermissionFlagsBits.ManageMessages) &&
- !(type.startsWith('un') && moderator.id === victim.id)
- ) {
- if (await moderator.guild.hasFeature('modsCanPunishMods')) {
- return true;
- } else {
- return `${util.emojis.error} You cannot ${type} **${victim.user.tag}** because they are a moderator.`;
- }
- }
- return true;
+export async function permissionCheck(
+ moderator: GuildMember,
+ victim: GuildMember,
+ type:
+ | 'mute'
+ | 'unmute'
+ | 'warn'
+ | 'kick'
+ | 'ban'
+ | 'unban'
+ | 'add a punishment role to'
+ | 'remove a punishment role from'
+ | 'block'
+ | 'unblock'
+ | 'timeout'
+ | 'untimeout',
+ checkModerator = true,
+ force = false
+): Promise<true | string> {
+ if (force) return true;
+
+ // If the victim is not in the guild anymore it will be undefined
+ if ((!victim || !victim.guild) && !['ban', 'unban'].includes(type)) return true;
+
+ if (moderator.guild.id !== victim.guild.id) {
+ throw new Error('moderator and victim not in same guild');
}
- /**
- * 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.
- * @returns An object with the modlog and the case number.
- */
- public static async createModLogEntry(
- options: CreateModLogEntryOptions,
- getCaseNumber = false
- ): Promise<{ log: ModLog | null; caseNum: number | null }> {
- const user = (await util.resolveNonCachedUser(options.user))!.id;
- const moderator = (await util.resolveNonCachedUser(options.moderator))!.id;
- const guild = client.guilds.resolveId(options.guild)!;
-
- return this.createModLogEntrySimple(
- {
- ...options,
- user: user,
- moderator: moderator,
- guild: guild
- },
- getCaseNumber
- );
+ const isOwner = moderator.guild.ownerId === moderator.id;
+ if (moderator.id === victim.id && !type.startsWith('un')) {
+ return `${emojis.error} You cannot ${type} yourself.`;
}
-
- /**
- * Creates a modlog entry with already resolved ids.
- * @param options Options for creating a modlog entry.
- * @param getCaseNumber Whether or not to get the case number of the entry.
- * @returns An object with the modlog and the case number.
- */
- public static async createModLogEntrySimple(
- options: SimpleCreateModLogEntryOptions,
- getCaseNumber = false
- ): Promise<{ log: ModLog | null; caseNum: number | null }> {
- // If guild does not exist create it so the modlog can reference a guild.
- await GuildDB.findOrCreate({
- where: { id: options.guild },
- defaults: { id: options.guild }
- });
-
- const modLogEntry = ModLog.build({
- type: options.type,
- user: options.user,
- moderator: options.moderator,
- reason: options.reason,
- duration: options.duration ? options.duration : undefined,
- guild: options.guild,
- pseudo: options.pseudo ?? false,
- evidence: options.evidence,
- hidden: options.hidden ?? false
- });
- const saveResult: ModLog | null = await modLogEntry.save().catch(async (e) => {
- await util.handleError('createModLogEntry', e);
- return null;
- });
-
- if (!getCaseNumber) return { log: saveResult, caseNum: null };
-
- const caseNum = (
- await ModLog.findAll({ where: { type: options.type, user: options.user, guild: options.guild, hidden: false } })
- )?.length;
- return { log: saveResult, caseNum };
+ if (
+ moderator.roles.highest.position <= victim.roles.highest.position &&
+ !isOwner &&
+ !(type.startsWith('un') && moderator.id === victim.id)
+ ) {
+ return `${emojis.error} You cannot ${type} **${victim.user.tag}** because they have higher or equal role hierarchy as you do.`;
}
-
- /**
- * Creates a punishment entry.
- * @param options Options for creating the punishment entry.
- * @returns The database entry, or null if no entry is created.
- */
- public static async createPunishmentEntry(options: CreatePunishmentEntryOptions): Promise<ActivePunishment | null> {
- const expires = options.duration ? new Date(+new Date() + options.duration ?? 0) : undefined;
- const user = (await util.resolveNonCachedUser(options.user))!.id;
- const guild = client.guilds.resolveId(options.guild)!;
- const type = this.findTypeEnum(options.type)!;
-
- const entry = ActivePunishment.build(
- options.extraInfo
- ? { user, type, guild, expires, modlog: options.modlog, extraInfo: options.extraInfo }
- : { user, type, guild, expires, modlog: options.modlog }
- );
- return await entry.save().catch(async (e) => {
- await util.handleError('createPunishmentEntry', e);
- return null;
- });
+ if (
+ victim.roles.highest.position >= victim.guild.members.me!.roles.highest.position &&
+ !(type.startsWith('un') && moderator.id === victim.id)
+ ) {
+ return `${emojis.error} You cannot ${type} **${victim.user.tag}** because they have higher or equal role hierarchy as I do.`;
}
-
- /**
- * Destroys a punishment entry.
- * @param options Options for destroying the punishment entry.
- * @returns Whether or not the entry was destroyed.
- */
- public static async removePunishmentEntry(options: RemovePunishmentEntryOptions): Promise<boolean> {
- const user = await util.resolveNonCachedUser(options.user);
- const guild = client.guilds.resolveId(options.guild);
- const type = this.findTypeEnum(options.type);
-
- if (!user || !guild) return false;
-
- let success = true;
-
- const entries = await ActivePunishment.findAll({
- // finding all cases of a certain type incase there were duplicates or something
- where: options.extraInfo
- ? { user: user.id, guild: guild, type, extraInfo: options.extraInfo }
- : { user: user.id, guild: guild, type }
- }).catch(async (e) => {
- await util.handleError('removePunishmentEntry', e);
- success = false;
- });
- if (entries) {
- const promises = entries.map(async (entry) =>
- entry.destroy().catch(async (e) => {
- await util.handleError('removePunishmentEntry', e);
- success = false;
- })
- );
-
- await Promise.all(promises);
+ if (
+ checkModerator &&
+ victim.permissions.has(PermissionFlagsBits.ManageMessages) &&
+ !(type.startsWith('un') && moderator.id === victim.id)
+ ) {
+ if (await moderator.guild.hasFeature('modsCanPunishMods')) {
+ return true;
+ } else {
+ return `${emojis.error} You cannot ${type} **${victim.user.tag}** because they are a moderator.`;
}
- return success;
}
+ return true;
+}
- /**
- * Returns the punishment type enum for the given type.
- * @param type The type of the punishment.
- * @returns The punishment type enum.
- */
- private static findTypeEnum(type: 'mute' | 'ban' | 'role' | 'block') {
- const typeMap = {
- ['mute']: ActivePunishmentType.MUTE,
- ['ban']: ActivePunishmentType.BAN,
- ['role']: ActivePunishmentType.ROLE,
- ['block']: ActivePunishmentType.BLOCK
- };
- return typeMap[type];
- }
+/**
+ * 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.
+ * @returns An object with the modlog and the case number.
+ */
+export async function createModLogEntry(
+ options: CreateModLogEntryOptions,
+ getCaseNumber = false
+): Promise<{ log: ModLog | null; caseNum: number | null }> {
+ const user = (await resolveNonCachedUser(options.user))!.id;
+ const moderator = (await resolveNonCachedUser(options.moderator))!.id;
+ const guild = client.guilds.resolveId(options.guild)!;
+
+ return createModLogEntrySimple(
+ {
+ ...options,
+ user: user,
+ moderator: moderator,
+ guild: guild
+ },
+ getCaseNumber
+ );
+}
- public static punishmentToPresentTense(punishment: PunishmentTypeDM): PunishmentTypePresent {
- return punishMap[punishment];
- }
+/**
+ * Creates a modlog entry with already resolved ids.
+ * @param options Options for creating a modlog entry.
+ * @param getCaseNumber Whether or not to get the case number of the entry.
+ * @returns An object with the modlog and the case number.
+ */
+export async function createModLogEntrySimple(
+ options: SimpleCreateModLogEntryOptions,
+ getCaseNumber = false
+): Promise<{ log: ModLog | null; caseNum: number | null }> {
+ // If guild does not exist create it so the modlog can reference a guild.
+ await GuildDB.findOrCreate({
+ where: { id: options.guild },
+ defaults: { id: options.guild }
+ });
+
+ const modLogEntry = ModLog.build({
+ type: options.type,
+ user: options.user,
+ moderator: options.moderator,
+ reason: options.reason,
+ duration: options.duration ? options.duration : undefined,
+ guild: options.guild,
+ pseudo: options.pseudo ?? false,
+ evidence: options.evidence,
+ hidden: options.hidden ?? false
+ });
+ const saveResult: ModLog | null = await modLogEntry.save().catch(async (e) => {
+ await handleError('createModLogEntry', e);
+ return null;
+ });
+
+ if (!getCaseNumber) return { log: saveResult, caseNum: null };
+
+ const caseNum = (
+ await ModLog.findAll({ where: { type: options.type, user: options.user, guild: options.guild, hidden: false } })
+ )?.length;
+ return { log: saveResult, caseNum };
+}
- public static punishmentToPastTense(punishment: PunishmentTypePresent): PunishmentTypeDM {
- return reversedPunishMap[punishment];
- }
+/**
+ * Creates a punishment entry.
+ * @param options Options for creating the punishment entry.
+ * @returns The database entry, or null if no entry is created.
+ */
+export async function createPunishmentEntry(options: CreatePunishmentEntryOptions): Promise<ActivePunishment | null> {
+ const expires = options.duration ? new Date(+new Date() + options.duration ?? 0) : undefined;
+ const user = (await resolveNonCachedUser(options.user))!.id;
+ const guild = client.guilds.resolveId(options.guild)!;
+ const type = findTypeEnum(options.type)!;
+
+ const entry = ActivePunishment.build(
+ options.extraInfo
+ ? { user, type, guild, expires, modlog: options.modlog, extraInfo: options.extraInfo }
+ : { user, type, guild, expires, modlog: options.modlog }
+ );
+ return await entry.save().catch(async (e) => {
+ await handleError('createPunishmentEntry', e);
+ return null;
+ });
+}
- /**
- * Notifies the specified user of their punishment.
- * @param options Options for notifying the user.
- * @returns Whether or not the dm was successfully sent.
- */
- public static async punishDM(options: PunishDMOptions): Promise<boolean> {
- const ending = await options.guild.getSetting('punishmentEnding');
- const dmEmbed =
- ending && ending.length && options.sendFooter
- ? new EmbedBuilder().setDescription(ending).setColor(util.colors.newBlurple)
- : undefined;
-
- const appealsEnabled = !!(
- (await options.guild.hasFeature('punishmentAppeals')) && (await options.guild.getLogChannel('appeals'))
+/**
+ * Destroys a punishment entry.
+ * @param options Options for destroying the punishment entry.
+ * @returns Whether or not the entry was destroyed.
+ */
+export async function removePunishmentEntry(options: RemovePunishmentEntryOptions): Promise<boolean> {
+ const user = await resolveNonCachedUser(options.user);
+ const guild = client.guilds.resolveId(options.guild);
+ const type = findTypeEnum(options.type);
+
+ if (!user || !guild) return false;
+
+ let success = true;
+
+ const entries = await ActivePunishment.findAll({
+ // finding all cases of a certain type incase there were duplicates or something
+ where: options.extraInfo
+ ? { user: user.id, guild: guild, type, extraInfo: options.extraInfo }
+ : { user: user.id, guild: guild, type }
+ }).catch(async (e) => {
+ await handleError('removePunishmentEntry', e);
+ success = false;
+ });
+ if (entries) {
+ const promises = entries.map(async (entry) =>
+ entry.destroy().catch(async (e) => {
+ await handleError('removePunishmentEntry', e);
+ success = false;
+ })
);
- let content = `You have been ${options.punishment} `;
- if (options.punishment.includes('blocked')) {
- assert(options.channel);
- content += `from <#${options.channel}> `;
- }
- content += `in ${util.format.input(options.guild.name)} `;
- if (options.duration !== null && options.duration !== undefined)
- content += options.duration ? `for ${util.humanizeDuration(options.duration)} ` : 'permanently ';
- const reason = options.reason?.trim() ? options.reason?.trim() : 'No reason provided';
- content += `for ${util.format.input(reason)}.`;
-
- let components;
- if (appealsEnabled && options.modlog)
- components = [
- new ActionRowBuilder<ButtonBuilder>({
- components: [
- new ButtonBuilder({
- customId: `appeal;${this.punishmentToPresentTense(options.punishment)};${options.guild.id};${client.users.resolveId(
- options.user
- )};${options.modlog}`,
- style: ButtonStyle.Primary,
- label: 'Appeal'
- }).toJSON()
- ]
- })
- ];
-
- const dmSuccess = await client.users
- .send(options.user, {
- content,
- embeds: dmEmbed ? [dmEmbed] : undefined,
- components
- })
- .catch(() => false);
- return !!dmSuccess;
+ await Promise.all(promises);
}
+ return success;
+}
+
+/**
+ * Returns the punishment type enum for the given type.
+ * @param type The type of the punishment.
+ * @returns The punishment type enum.
+ */
+function findTypeEnum(type: 'mute' | 'ban' | 'role' | 'block') {
+ const typeMap = {
+ ['mute']: ActivePunishmentType.MUTE,
+ ['ban']: ActivePunishmentType.BAN,
+ ['role']: ActivePunishmentType.ROLE,
+ ['block']: ActivePunishmentType.BLOCK
+ };
+ return typeMap[type];
+}
+
+export function punishmentToPresentTense(punishment: PunishmentTypeDM): PunishmentTypePresent {
+ return punishMap[punishment];
+}
+
+export function punishmentToPastTense(punishment: PunishmentTypePresent): PunishmentTypeDM {
+ return reversedPunishMap[punishment];
+}
+
+/**
+ * Notifies the specified user of their punishment.
+ * @param options Options for notifying the user.
+ * @returns Whether or not the dm was successfully sent.
+ */
+export async function punishDM(options: PunishDMOptions): Promise<boolean> {
+ const ending = await options.guild.getSetting('punishmentEnding');
+ const dmEmbed =
+ ending && ending.length && options.sendFooter
+ ? new EmbedBuilder().setDescription(ending).setColor(colors.newBlurple)
+ : undefined;
+
+ const appealsEnabled = !!(
+ (await options.guild.hasFeature('punishmentAppeals')) && (await options.guild.getLogChannel('appeals'))
+ );
+
+ let content = `You have been ${options.punishment} `;
+ if (options.punishment.includes('blocked')) {
+ assert(options.channel);
+ content += `from <#${options.channel}> `;
+ }
+ content += `in ${format.input(options.guild.name)} `;
+ if (options.duration !== null && options.duration !== undefined)
+ content += options.duration ? `for ${humanizeDuration(options.duration)} ` : 'permanently ';
+ const reason = options.reason?.trim() ? options.reason?.trim() : 'No reason provided';
+ content += `for ${format.input(reason)}.`;
+
+ let components;
+ if (appealsEnabled && options.modlog)
+ components = [
+ new ActionRowBuilder<ButtonBuilder>({
+ components: [
+ new ButtonBuilder({
+ customId: `appeal;${punishmentToPresentTense(options.punishment)};${options.guild.id};${client.users.resolveId(
+ options.user
+ )};${options.modlog}`,
+ style: ButtonStyle.Primary,
+ label: 'Appeal'
+ }).toJSON()
+ ]
+ })
+ ];
+
+ const dmSuccess = await client.users
+ .send(options.user, {
+ content,
+ embeds: dmEmbed ? [dmEmbed] : undefined,
+ components
+ })
+ .catch(() => false);
+ return !!dmSuccess;
}
interface BaseCreateModLogEntryOptions {