diff options
Diffstat (limited to 'src/lib/common')
-rw-r--r-- | src/lib/common/ButtonPaginator.ts | 130 | ||||
-rw-r--r-- | src/lib/common/DeleteButton.ts | 41 | ||||
-rw-r--r-- | src/lib/common/util/Format.ts | 107 |
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, ''); + } +} |