import type DiscordBot from '../../Structures/DiscordBot'; import { type CacheType, type CommandInteraction, type MessageComponentInteraction, Formatters, MessageEmbed, MessageActionRow, MessageSelectMenu, Message, MessageButton, } from 'discord.js'; import { DurationFormatter } from '@sapphire/time-utilities'; import ApplicationCommand from '../../Structures/Registries/ApplicationCommand.js'; export = class HelpCommand extends ApplicationCommand { private constructor(client: DiscordBot) { super(client, 'help', { description: 'Get help with commands.', category: 'Utility', guildOnly: true, cooldown: 5000, }); } public async run( interaction: CommandInteraction ): Promise | void | undefined> { try { const embed = new MessageEmbed() .setAuthor({ name: this.client.user?.username || interaction.user.username, iconURL: this.client.user?.displayAvatarURL({ dynamic: true }), }) .setTitle('Help Menu') .setDescription('Select a category to see all the associated commands that come with it.') .setColor(this.client.color) .setTimestamp(); const menu = new MessageSelectMenu() .setPlaceholder('Select a Category!') .setCustomId('help-command-menu') .setOptions([ { label: 'Utility', value: 'Utility', description: 'Utility commands that are used for general purposes.', emoji: this.client.config.customEmojis.omegaGear, }, { label: 'General', value: 'General', description: 'General bot commands.', emoji: this.client.config.customEmojis.omegaGear, }, { label: 'Miscellaneous', value: 'Miscellaneous', description: "Miscellaneous commands that probably don't do much.", emoji: this.client.config.customEmojis.omegaGear, }, ]) .setMinValues(1) .setMaxValues(1); const menuRow = new MessageActionRow().addComponents(menu); const msg = await interaction.reply({ embeds: [embed], components: [menuRow], fetchReply: true, }); if (!(msg instanceof Message)) return; const collector = msg.createMessageComponentCollector({ time: 120000, filter: (i) => i.user.id === interaction.user.id, componentType: 'SELECT_MENU', }); const backward = new MessageButton() .setCustomId('backward') .setEmoji('⬅️') .setStyle('SECONDARY'); const trash = new MessageButton().setCustomId('trash').setEmoji('🗑').setStyle('SECONDARY'); const forward = new MessageButton() .setCustomId('forward') .setEmoji('➡️') .setStyle('SECONDARY'); const row = new MessageActionRow().addComponents(backward, trash, forward); let pages = new Array(); let page = 0; const time = 30000; collector.on('collect', async (collected) => { if (pages.length) pages = new Array(); const value = collected.values[0]; const collection = this.client.commands.application.filter( (c) => c.category.toLowerCase() === (value.toLowerCase() as string) ); await collected.deferUpdate(); if (!collection.size) { await collected.editReply({ embeds: [ embed .setTitle('Oops!') .setDescription( 'Nothing to see here! No commands for this category are available.' ), ], components: [menuRow], }); return; } else { for (const [name, command] of collection) { const embed = new MessageEmbed() .setTitle(name.title()) .setDescription(command.description) .addFields([ { name: 'Usage', value: Array.isArray(command.usage) ? command.usage.map((u) => Formatters.codeBlock('xml', u)).join('\n') : Formatters.codeBlock('xml', command.usage), }, { name: 'Cooldown', value: new DurationFormatter().format(command.cooldown / 1000), }, ]) .setColor(this.client.color) .setFooter({ text: '<> are required options and [] are optional optio.', iconURL: this.client.user?.displayAvatarURL({ dynamic: true }), }) .setTimestamp(); if (command.memberPermissions.length || command.botPermissions.length) { let value = new String().toString(); if (command.memberPermissions.length) { value += `${Formatters.bold('Member')}: ${command.memberPermissions .map((p) => this.client.utils.formatPermissions(p)) .join(', ')}.\n`; } if (command.botPermissions.length) { value += `${Formatters.bold('Bot')}: ${command.botPermissions .map((p) => this.client.utils.formatPermissions(p)) .join(', ')}.`; } embed.addField('Required Permissions', value); } pages.push(embed); } const filter = async (i: MessageComponentInteraction) => { i.customId === 'backward' || i.customId === 'forward' || i.customId === 'trash'; if (i.user.id !== interaction.user.id) { await i.reply({ content: 'This button is not for you to use.', ephemeral: true, }); return false; } else { return true; } }; if (pages.length <= 1) { await interaction.editReply({ embeds: [pages[page].setFooter({ text: `Page ${page + 1} / ${pages.length}` })], components: [menuRow], }); } else { const curPage = await interaction.editReply({ embeds: [pages[page].setFooter({ text: `Page ${page + 1} / ${pages.length}` })], components: [row, menuRow], }); if (!(curPage instanceof Message)) return; const collector = curPage.createMessageComponentCollector({ filter, time: time, }); collector.on('collect', async (b) => { switch (b.customId) { case 'backward': page = page - 1 > pages.length ? --page : 0; console.log(b.deferred, b.replied); await b.update({ embeds: [pages[page].setFooter({ text: `Page ${page + 1} / ${pages.length}` })], components: [row, menuRow], }); collector.resetTimer(); break; case 'forward': page = page + 1 < pages.length ? ++page : 0; console.log(b.deferred, b.replied); await b.update({ embeds: [pages[page].setFooter({ text: `Page ${page + 1} / ${pages.length}` })], components: [row, menuRow], }); collector.resetTimer(); break; case 'trash': if (!this.client.utils.isMessageDeleted(curPage)) { await curPage?.delete(); this.client.utils.markMessageAsDeleted(curPage); } collector.stop(); break; default: break; } }); collector.once('end', async (_collection, reason) => { if (this.client.utils.isMessageDeleted(curPage) || reason === 'messageDelete') { return; } else { const newRow = new MessageActionRow().addComponents( backward.setDisabled(true), trash.setDisabled(true), forward.setDisabled(true) ); await curPage.edit({ embeds: [pages[page].setFooter({ text: `Page ${page + 1} / ${pages.length}` })], components: [newRow], }); } return; }); } } }); collector.once('end', async (collection, reason) => { if ( this.client.utils.isMessageDeleted(collection.first()?.message as Message) || reason === 'messageDelete' ) { return; } else { await msg.edit({ embeds: [embed], components: [new MessageActionRow().addComponents(menu.setDisabled(true))], }); } return; }); } catch (error) { console.error(error); return; } } };