UNPKG

djs-menu-v13

Version:

A module for create simple and complex menu for your Discord Bot

257 lines (234 loc) 7.9 kB
import {MenuPage} from './Page'; import {MenuSelectPage} from './SelectPage'; import {EventEmitter} from 'events'; import { CommandInteraction, MessageActionRow, MessageButton, MessageSelectMenu, } from 'discord.js'; import {pagesType} from '..'; /** * The class that handle all the Menu logic * @class Menu * @extends EventEmitter * @noInheritDoc */ export class Menu extends EventEmitter { pages: pagesType[]; collector: any; interaction: CommandInteraction; ephemeral: boolean; /** * The interaction that the menu will use. * @param {CommandInteraction} interaction */ constructor(interaction: CommandInteraction) { super(); this.pages = []; this.interaction = interaction; this.collector = {}; this.ephemeral = false; } /** * A function for add Page in the Menu * @param {pagesType} page * @return {Menu} */ addPage(page: pagesType):Menu { if (!page) throw new Error('The page is undefined'); this.pages.push(page); return this; } /** * A function for start the menu * @param {string} id * @return {Menu} */ start(id: string):Menu { if (!id) throw new Error('The id is undefined'); if (!this.interaction) throw new Error('The interaction is invalid'); const findPage = this.pages.find((page) => page.id === id); if (!findPage) throw new Error(`The page with id ${id} was not found`); const startPage = id && findPage ? findPage : this.pages[0]; this.setPage(startPage); return this; } /** * A function for stop the menu * @return {void} */ stop():void { this.emit('stop', this.interaction, 'stop', this.pages); } /** * A function to set the page with a given MenuPage * @param {pagesType} page * @return {void} */ setPage(page: pagesType):void { this.displayPage(page); // This filter check by spliting the customId // If there is a ID linked to the button // Example : myid.123456789 const filter = (i) => i.user.id === this.interaction.user.id && i.customId.split('.')[1] === this.interaction.user.id; this.collector = this.interaction.channel?.createMessageComponentCollector( {filter, time: page?.timeout, max: 1}, ); // When the page is set, that mean the page changed. this.emit('pageChanged', page, this.interaction, this.pages); // Stop the collector if the stop event is emitted this.once('stop', () => { this.collector.stop(); }); this.collector?.on('collect', (i) => { // Get the ID of the button i.deferUpdate(); const btnPage = (page as MenuPage); if (btnPage.type === 'MenuPage') { const id = i.customId.split('.')[0]; // Get the button by the ID const findBtn = btnPage.buttons.find( (button) => button.id === id, ); if (findBtn && typeof findBtn.target !== 'function') { // Get the target from the button // To know what is the next page to display const target = findBtn.target; // Find the page to display with the target const nextPage = this.pages.find((ptf) => ptf.id === target); this.collector.stop(); if (nextPage) this.setPage(nextPage); } else if (findBtn) { // Execute the function of the target with parameters const target = findBtn.target; const funcTarget = ( target as ( page:MenuPage|MenuSelectPage, interaction:CommandInteraction, Menu:this ) => void ); funcTarget(page, this.interaction, this); } } else if (btnPage.type === 'MenuSelectPage') { // Get the value of the select wich is randomId.pageId const value = i.values[0].split('.'); // Find the page who is linked to the select const currentPage = this.pages.find((ptf) => ptf.id === value[1]); // Find the target of the select const currentChoice = (currentPage as MenuSelectPage).options.find( (option) => option.value.split('.')[0] === value[0], ); if (!currentChoice) { throw new Error('The target was not found'); } if (typeof currentChoice.target === 'function') { const funcTarget = ( currentChoice.target as ( page:MenuPage|MenuSelectPage, interaction:CommandInteraction, Menu:this ) => void ); funcTarget(page, this.interaction, this); } else { this.collector.stop(); const nextPage = this.pages.find( (ptf) => ptf.id === currentChoice.target, ); if (nextPage) this.setPage(nextPage); } } }); this.collector?.on('end', (collected) => { if (collected.size === 0) { // If the menu don't get any response stop the menu with noReply reason this.emit('stop', this.interaction, 'noReply', this.pages); } }); } /** * A function for make all the message in the menu epehemeral * @param {boolean} isEphemeral * @return {Menu} */ isEphemeral(isEphemeral:boolean):Menu { this.ephemeral = isEphemeral; return this; } /** * A function to display a page given page * @param {pagesType} page? * @return {void} */ displayPage(page?: pagesType):void { const btnPage = (page as MenuPage); if (btnPage.type === 'MenuPage') { const content = page?.content || '.'; const buttons = btnPage?.buttons || []; const raw = new MessageActionRow(); for (const button of buttons) { const btn = new MessageButton() .setCustomId(`${button.id}.${this.interaction.user.id}`) .setLabel(`${button.label}`) .setStyle(button.style); if (button.emoji) { btn.setEmoji(button.emoji); } if (button?.url) { btn.setURL(`${button?.url}`); } raw.addComponents(btn); } // FIXME : Find a way to not have duplicate image if (this.interaction.replied) { this.interaction.editReply({ embeds: btnPage?.embeds, content: `${content}`, components: [raw], files: btnPage?.files, }); } else { this.interaction.reply({ embeds: btnPage?.embeds, content: `${content}`, components: [raw], ephemeral: this.ephemeral, files: btnPage?.files, }); } } else { const selectPage = (page as MenuSelectPage); const content = page?.content || '.'; const selectMenuOptions = selectPage.options; const raw = new MessageActionRow(); const selectMenu = new MessageSelectMenu() .addOptions(selectMenuOptions) .setCustomId(`${selectPage.id}.${this.interaction.user.id}`) .setPlaceholder(selectPage.placeholder); selectMenu.setMinValues(1); selectMenu.setMaxValues(1); raw.addComponents(selectMenu); // FIXME : Find a way to not have duplicate image if (this.interaction.replied) { this.interaction.editReply({ embeds: selectPage?.embeds, content: `${content}`, components: [raw], files: selectPage?.files, }); } else { this.interaction.reply({ embeds: selectPage?.embeds, content: `${content}`, components: [raw], ephemeral: this.ephemeral, files: selectPage?.files, }); } } } }