aboutsummaryrefslogtreecommitdiff
path: root/src/lib/common
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib/common')
-rw-r--r--src/lib/common/ButtonPaginator.ts130
-rw-r--r--src/lib/common/DeleteButton.ts41
-rw-r--r--src/lib/common/util/Format.ts107
3 files changed, 233 insertions, 45 deletions
diff --git a/src/lib/common/ButtonPaginator.ts b/src/lib/common/ButtonPaginator.ts
index d193b4d..83f4219 100644
--- a/src/lib/common/ButtonPaginator.ts
+++ b/src/lib/common/ButtonPaginator.ts
@@ -1,50 +1,55 @@
import { DeleteButton, type BushMessage, type BushSlashMessage } from '#lib';
import { CommandUtil } from 'discord-akairo';
import {
- Constants,
MessageActionRow,
MessageButton,
MessageEmbed,
type MessageComponentInteraction,
type MessageEmbedOptions
} from 'discord.js';
+import { MessageButtonStyles } from 'discord.js/typings/enums';
+/**
+ * Sends multiple embeds with controls to switch between them
+ */
export class ButtonPaginator {
+ /**
+ * The message that triggered the command
+ */
protected message: BushMessage | BushSlashMessage;
+
+ /**
+ * The embeds to paginate
+ */
protected embeds: MessageEmbed[] | MessageEmbedOptions[];
+
+ /**
+ * 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;
+
+ /**
+ * The paginator message
+ */
protected sentMessage: BushMessage | undefined;
/**
- * Sends multiple embeds with controls to switch between them
* @param message The message to respond to
* @param embeds The embeds to switch between
* @param text The text send with the embeds (optional)
* @param deleteOnExit Whether to delete the message when the exit button is clicked (defaults to true)
* @param startOn The page to start from (**not** the index)
*/
- public static async send(
- message: BushMessage | BushSlashMessage,
- embeds: MessageEmbed[] | MessageEmbedOptions[],
- text: string | null = null,
- deleteOnExit = true,
- startOn = 1
- ) {
- // no need to paginate if there is only one page
- if (embeds.length === 1) return DeleteButton.send(message, { embeds: embeds });
-
- return await new ButtonPaginator(message, embeds, text, deleteOnExit, startOn).send();
- }
-
- /**
- * The number of pages in the paginator
- */
- protected get numPages(): number {
- return this.embeds.length;
- }
-
protected constructor(
message: BushMessage | BushSlashMessage,
embeds: MessageEmbed[] | MessageEmbedOptions[],
@@ -62,7 +67,7 @@ export class ButtonPaginator {
// add footers
for (let i = 0; i < embeds.length; i++) {
if (embeds[i] instanceof MessageEmbed) {
- (embeds[i] as MessageEmbed).setFooter(`Page ${(i + 1).toLocaleString()}/${embeds.length.toLocaleString()}`);
+ (embeds[i] as MessageEmbed).setFooter({ text: `Page ${(i + 1).toLocaleString()}/${embeds.length.toLocaleString()}` });
} else {
(embeds[i] as MessageEmbedOptions).footer = {
text: `Page ${(i + 1).toLocaleString()}/${embeds.length.toLocaleString()}`
@@ -71,6 +76,16 @@ export class ButtonPaginator {
}
}
+ /**
+ * The number of pages in the paginator
+ */
+ protected get numPages(): number {
+ return this.embeds.length;
+ }
+
+ /**
+ * Sends the paginator message
+ */
protected async send() {
this.sentMessage = (await this.message.util.reply({
content: this.text,
@@ -87,6 +102,10 @@ export class ButtonPaginator {
collector.on('end', () => void this.end());
}
+ /**
+ * Handles interactions with the paginator
+ * @param interaction The interaction received
+ */
protected async collect(interaction: MessageComponentInteraction) {
if (interaction.user.id !== this.message.author.id && !client.config.owners.includes(interaction.user.id))
return await interaction?.deferUpdate().catch(() => null);
@@ -94,35 +113,44 @@ export class ButtonPaginator {
switch (interaction.customId) {
case 'paginate_beginning':
this.curPage = 0;
- return this.edit(interaction);
+ await this.edit(interaction);
+ break;
case 'paginate_back':
this.curPage--;
- return await this.edit(interaction);
+ await this.edit(interaction);
+ break;
case 'paginate_stop':
if (this.deleteOnExit) {
await interaction.deferUpdate().catch(() => null);
- return await this.sentMessage!.delete().catch(() => null);
+ await this.sentMessage!.delete().catch(() => null);
+ break;
} else {
- return await interaction
+ await interaction
?.update({
content: `${this.text ? `${this.text}\n` : ''}Command closed by user.`,
embeds: [],
components: []
})
.catch(() => null);
+ break;
}
case 'paginate_next':
this.curPage++;
- return await this.edit(interaction);
+ await this.edit(interaction);
+ break;
case 'paginate_end':
this.curPage = this.embeds.length - 1;
- return await this.edit(interaction);
+ await this.edit(interaction);
+ break;
}
}
+ /**
+ * Ends the paginator
+ */
protected async end() {
if (this.sentMessage && !CommandUtil.deletedMessages.has(this.sentMessage.id))
- return await this.sentMessage
+ await this.sentMessage
.edit({
content: this.text,
embeds: [this.embeds[this.curPage]],
@@ -131,8 +159,12 @@ export class ButtonPaginator {
.catch(() => null);
}
+ /**
+ * Edits the paginator message
+ * @param interaction The interaction received
+ */
protected async edit(interaction: MessageComponentInteraction) {
- return interaction
+ await interaction
?.update({
content: this.text,
embeds: [this.embeds[this.curPage]],
@@ -141,40 +173,66 @@ export class ButtonPaginator {
.catch(() => null);
}
+ /**
+ * Generates the pagination row based on the class properties
+ * @param disableAll Whether to disable all buttons
+ * @returns The generated {@link MessageActionRow}
+ */
protected getPaginationRow(disableAll = false): MessageActionRow {
return new MessageActionRow().addComponents(
new MessageButton({
- style: Constants.MessageButtonStyles.PRIMARY,
+ style: MessageButtonStyles.PRIMARY,
customId: 'paginate_beginning',
emoji: PaginateEmojis.BEGGING,
disabled: disableAll || this.curPage === 0
}),
new MessageButton({
- style: Constants.MessageButtonStyles.PRIMARY,
+ style: MessageButtonStyles.PRIMARY,
customId: 'paginate_back',
emoji: PaginateEmojis.BACK,
disabled: disableAll || this.curPage === 0
}),
new MessageButton({
- style: Constants.MessageButtonStyles.PRIMARY,
+ style: MessageButtonStyles.PRIMARY,
customId: 'paginate_stop',
emoji: PaginateEmojis.STOP,
disabled: disableAll
}),
new MessageButton({
- style: Constants.MessageButtonStyles.PRIMARY,
+ style: MessageButtonStyles.PRIMARY,
customId: 'paginate_next',
emoji: PaginateEmojis.FORWARD,
disabled: disableAll || this.curPage === this.numPages - 1
}),
new MessageButton({
- style: Constants.MessageButtonStyles.PRIMARY,
+ style: MessageButtonStyles.PRIMARY,
customId: 'paginate_end',
emoji: PaginateEmojis.END,
disabled: disableAll || this.curPage === this.numPages - 1
})
);
}
+
+ /**
+ * Sends multiple embeds with controls to switch between them
+ * @param message The message to respond to
+ * @param embeds The embeds to switch between
+ * @param text The text send with the embeds (optional)
+ * @param deleteOnExit Whether to delete the message when the exit button is clicked (defaults to true)
+ * @param startOn The page to start from (**not** the index)
+ */
+ public static async send(
+ message: BushMessage | BushSlashMessage,
+ embeds: MessageEmbed[] | MessageEmbedOptions[],
+ text: string | null = null,
+ deleteOnExit = true,
+ startOn = 1
+ ) {
+ // no need to paginate if there is only one page
+ if (embeds.length === 1) return DeleteButton.send(message, { embeds: embeds });
+
+ return await new ButtonPaginator(message, embeds, text, deleteOnExit, startOn).send();
+ }
}
export const enum PaginateEmojis {
diff --git a/src/lib/common/DeleteButton.ts b/src/lib/common/DeleteButton.ts
index e2509a9..b666a4f 100644
--- a/src/lib/common/DeleteButton.ts
+++ b/src/lib/common/DeleteButton.ts
@@ -1,25 +1,34 @@
import { PaginateEmojis, type BushMessage, type BushSlashMessage } from '#lib';
import { CommandUtil } from 'discord-akairo';
-import { Constants, MessageActionRow, MessageButton, type MessageComponentInteraction, type MessageOptions } from 'discord.js';
+import { MessageActionRow, MessageButton, type MessageComponentInteraction, type MessageOptions } from 'discord.js';
+import { MessageButtonStyles } from 'discord.js/typings/enums';
+/**
+ * Sends a message with a button for the user to delete it.
+ */
export class DeleteButton {
+ /**
+ * Options for sending the message
+ */
protected messageOptions: MessageOptions;
- protected message: BushMessage | BushSlashMessage;
/**
- * Sends a message with a button for the user to delete it.
- * @param message - The message to respond to
- * @param options - The send message options
+ * The message that triggered the command
*/
- static async send(message: BushMessage | BushSlashMessage, options: Omit<MessageOptions, 'components'>) {
- return new DeleteButton(message, options).send();
- }
+ protected message: BushMessage | BushSlashMessage;
+ /**
+ * @param message The message to respond to
+ * @param options The send message options
+ */
protected constructor(message: BushMessage | BushSlashMessage, options: MessageOptions) {
this.message = message;
this.messageOptions = options;
}
+ /**
+ * Sends a message with a button for the user to delete it.
+ */
protected async send() {
this.updateComponents();
@@ -43,11 +52,16 @@ export class DeleteButton {
});
}
+ /**
+ * Generates the components for the message
+ * @param edit Whether or not the message is being edited
+ * @param disable Whether or not to disable the buttons
+ */
protected updateComponents(edit = false, disable = false): void {
this.messageOptions.components = [
new MessageActionRow().addComponents(
new MessageButton({
- style: Constants.MessageButtonStyles.PRIMARY,
+ style: MessageButtonStyles.PRIMARY,
customId: 'paginate__stop',
emoji: PaginateEmojis.STOP,
disabled: disable
@@ -58,4 +72,13 @@ export class DeleteButton {
this.messageOptions.reply = undefined;
}
}
+
+ /**
+ * Sends a message with a button for the user to delete it.
+ * @param message The message to respond to
+ * @param options The send message options
+ */
+ public static async send(message: BushMessage | BushSlashMessage, options: Omit<MessageOptions, 'components'>) {
+ return new DeleteButton(message, options).send();
+ }
}
diff --git a/src/lib/common/util/Format.ts b/src/lib/common/util/Format.ts
new file mode 100644
index 0000000..6cb6edc
--- /dev/null
+++ b/src/lib/common/util/Format.ts
@@ -0,0 +1,107 @@
+import { type CodeBlockLang } from '#lib';
+import { EscapeMarkdownOptions, Formatters, Util } from 'discord.js';
+
+/**
+ * Formats and escapes content for formatting
+ */
+export class Format {
+ /**
+ * Wraps the content inside a codeblock with no language.
+ * @param content The content to wrap.
+ */
+ public static 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 \`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}`));
+ }
+
+ /**
+ * 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 bold text.
+ * @param content The content to wrap.
+ */
+ public static bold(content: string): string {
+ return Formatters.bold(Util.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 strike-through text.
+ * @param content The content to wrap.
+ */
+ public static strikethrough(content: string): string {
+ return Formatters.strikethrough(Util.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}`));
+ }
+
+ /**
+ * 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);
+ }
+
+ /**
+ * 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 for logs: makes it highlighted
+ * @param text The input
+ */
+ public static inputLog(text: string): string {
+ return `<<${this.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, '');
+ }
+}