UNPKG

@notoiro/djs-button-pages

Version:

A simple yet powerful module for implementing customizable embed pages with buttons in Discord chat. Works only with Discord.js.

301 lines (300 loc) 11.1 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const discord_js_1 = require("discord.js"); const StopReason_1 = __importDefault(require("../Enums/StopReason")); const Constants_1 = __importDefault(require("../Enums/Constants")); const PaginationState_1 = __importDefault(require("../Enums/PaginationState")); /** * Class that represents pagination that is already sent. */ class PaginationSent { _data; _message; _page; _beforeStopAction; _onStopAction; _state = PaginationState_1.default.NotReady; _collector; /** * Class that represents pagination that is already sent. * @param {PaginationData} _data Embeds, buttons and so on. * @param {Message | RepliableInteraction} _message Message or interaction that pagination should be assigned to. * @param {number} _page Page which pagination should start from. * @param {StopAction | undefined} _beforeStopAction Action that is completed before the pagination is stopped. * @param {StopAction | undefined} _onStopAction Action that is completed after the pagination is stopped. */ constructor(_data, _message, _page = 0, _beforeStopAction = undefined, _onStopAction = undefined) { this._data = _data; this._message = _message; this._page = _page; this._beforeStopAction = _beforeStopAction; this._onStopAction = _onStopAction; } ; /** * @returns {number} Current page. */ get page() { return this._page; } ; /** * @returns {PaginationData} Embeds, buttons and so on. */ get data() { return this._data; } ; /** * @returns {boolean} Is pagination active or not. */ get isActive() { return this._state === PaginationState_1.default.Ready; } ; /** * @returns {PaginationState} Pagination's state. */ get state() { return this._state; } ; /** * @returns {Message | RepliableInteraction} Message or interaction that pagination should be assigned to. */ get attachedTo() { return this._message; } ; /** * Gets button wrapper by custom id. * @param {string} custom_id Custom id. * @returns {ButtonWrapper | undefined} Button wrapper. */ getButtonByCustomId(custom_id) { return this._data.buttons.find((button) => button.data.custom_id === custom_id); } ; /** * Initializes pagination. Can be called only once. * Default pagination wrapper calls it so you shouldn't. * @returns {Promise<this>} */ async init() { if (this._state !== PaginationState_1.default.NotReady) throw new Error("[DJS-Button-Pages]: Pagination is already active!"); await this._formCollector(); this._state = PaginationState_1.default.Ready; await this.update(); return this; } ; /** * Deletes pagination and stops it. * If pagination is an ephemeral interaction stops it instead. * @returns {Promise<void>} */ async delete() { if (this._state !== PaginationState_1.default.Ready) throw new Error("[DJS-Button-Pages]: Pagination should be active!"); try { this._message instanceof discord_js_1.Message ? await this._message.delete() : await this._message.deleteReply(); } catch (e) { /*Catch unexpected errors.*/ } ; this._collector.stop(StopReason_1.default.InternalDeletion); return; } ; /** * Switches pagination to the next page. * It does not update the pagination, so you should call {@link update} by yourself! * @param {number} page Page number. * @returns {this} */ setPage(page) { if (this._state !== PaginationState_1.default.Ready) throw new Error("[DJS-Button-Pages]: Pagination should be active!"); if (!Number.isInteger(page) || page < 0) throw new RangeError("[DJS-Button-Pages]: Page number should be integer!"); if (page > this._data.embeds.length - 1) { page = this._data.embeds.length - 1; console.warn("[DJS-Button-Pages]: You're passing in page number that is greater than pagination has."); } ; this._page = page; return this; } ; /** * Updates pagination state. * @returns {Promise<void>} */ async update() { if (this._state !== PaginationState_1.default.Ready) throw new Error("[DJS-Button-Pages]: Pagination should be active!"); const embed = this._data.embeds[this._page]; if (!embed) throw new Error("[DJS-Button-Pages]: Page for this button is not defined!"); const rows = await this._buildPagedActionRows(), update = { embeds: [embed], components: rows }; try { this._message instanceof discord_js_1.Message ? await this._message.edit(update) : await this._message.editReply(update); } catch (e) { /*Catch unexpected errors.*/ } ; if (this._data.filterOptions.resetTimer) this._collector.resetTimer(); return; } ; /** * Stops pagination. * @returns {void} */ stop() { if (this._state !== PaginationState_1.default.Ready) throw new Error("[DJS-Button-Pages]: Pagination should be active!"); this._collector.stop(StopReason_1.default.InternalStop); return; } ; /** * Called than the collector collects interaction. * @param {ButtonInteraction} interaction Interaction that is fired. * @returns {Promise<void>} */ async _collected(interaction) { const wrapper = this.getButtonByCustomId(interaction.customId); if (!wrapper) throw new Error("[DJS-Button-Pages]: Button should be defined!"); await wrapper.action(this, interaction); return; } ; /** * Called than the pagination stopped. * @param {string} reason Reason why the pagination is stopped. * @returns {Promise<void>} */ async _stopped(reason) { this._state = PaginationState_1.default.Over; if (this._beforeStopAction) await this._beforeStopAction(reason, this, this._message); if ((!(this._message instanceof discord_js_1.Message) && this._message.ephemeral) || (reason !== StopReason_1.default.InternalDeletion && reason !== 'messageDelete')) { const rows = this._buildActionRows(true), update = { components: this._data.filterOptions.removeButtonsOnEnd ? [] : rows }; try { this._message instanceof discord_js_1.Message ? await this._message.edit(update) : await this._message.editReply(update); } catch (e) { /*Catch unexpected errors.*/ } ; } ; if (this._onStopAction) await this._onStopAction(reason, this, this._message); return; } ; /** * Builds action rows with enabled or disabled buttons. * @param {boolean} disabled Should be buttons disabled or not. * @returns {Array<ActionRowBuilder<ButtonBuilder>>} */ _buildActionRows(disabled = false) { const rows = []; for (let i = 0; i < Constants_1.default.DiscordMaxRowsPerMessage - 1; i++) { rows.push(new discord_js_1.ActionRowBuilder()); this._data.buttons .slice(i * Constants_1.default.DiscordMaxButtonsPerRow, (i + 1) * Constants_1.default.DiscordMaxButtonsPerRow) .forEach((button) => { const row = rows[i]; if (!row) return; row.addComponents(button.builtComponent.setDisabled(disabled)); }); } ; rows.forEach((row) => { if (row.components.length === 0) rows.splice(rows.indexOf(row)); }); return rows; } ; /** * Builds action rows for specific page. * @returns {Promise<Array<ActionRowBuilder<ButtonBuilder>>>} */ async _buildPagedActionRows() { const rows = this._buildActionRows(); for (const row of rows) for (const button of row.components) { const wrapper = this.getButtonByCustomId(button.data.custom_id); if (!wrapper) throw new Error("[DJS-Button-Pages]: Button should be defined!"); button.setDisabled(wrapper.switch ? await wrapper.switch(this) : false); } ; return rows; } ; /** * Forms collector for pages. * @returns {Promise<void>} */ async _formCollector() { if (!this._message || this._state !== PaginationState_1.default.NotReady) throw new Error("[DJS-Button-Pages]: This method should be executed after the message is defined."); const message = this._message instanceof discord_js_1.Message ? this._message : await this._message.fetchReply(); this._collector = message.createMessageComponentCollector({ componentType: discord_js_1.ComponentType.Button, time: this._data.time, maxUsers: this._data.filterOptions.maxUsers, max: this._data.filterOptions.maxInteractions, idle: this._data.filterOptions.maxIdleTime, filter: this._formFilter(message.id), }); this._collector.on("collect", async (interaction) => this._collected(interaction)); this._collector.once("end", async (_collected, reason) => this._stopped(reason)); return; } ; /** * Forms filtering function for collector. * @param {string} messageId Reply id. * @returns {Promise<boolean>} */ _formFilter(messageId) { return async (interaction) => { if (interaction.message.id !== messageId) return false; if (this._data.filterOptions.filterUsers && this._data.filterOptions.allowedUsers && !this._data.filterOptions.allowedUsers.includes(interaction.user.id)) { if (this._data.filterOptions.noAccessReply && this._data.filterOptions.noAccessReplyContent) await interaction.reply(typeof this._data.filterOptions.noAccessReplyContent === "string" ? { content: this._data.filterOptions.noAccessReplyContent } : this._data.filterOptions.noAccessReplyContent); return false; } ; await interaction.deferUpdate(); return true; }; } ; } exports.default = PaginationSent; ;