@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
JavaScript
"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;
;