diff --git a/src/commands/general/help.ts b/src/commands/general/help.ts index a58e171..73ccb69 100644 --- a/src/commands/general/help.ts +++ b/src/commands/general/help.ts @@ -15,9 +15,10 @@ * along with ArgonBot. If not, see . */ import Command from '@structures/command'; +import config from 'config'; +import { MessageButton } from 'discord.js'; import { Client, Message, MessageEmbed } from 'discord.js'; import i18next from 'i18next'; -import path from 'path'; export default class extends Command { public constructor(client: Client, file: string) { @@ -36,7 +37,7 @@ export default class extends Command { public async run(message: Message, command: string): Promise { if(!command) { - const commandGroups: { name: string; embeds: MessageEmbed, commands: Command[] }[] = []; + const commandGroups: { name: string; embed: MessageEmbed, commands: Command[] }[] = []; this.client.commands.forEach((command) => { const findCommand = commandGroups.find((val) => val.name === command.options.group); @@ -46,13 +47,113 @@ export default class extends Command { } else { commandGroups.push({ name: command.options.group!, - embeds: new MessageEmbed(), + embed: new MessageEmbed(), commands: [command], }); } }); - console.log(path.basename(this.file)); + commandGroups.forEach((val, index) => { + val.embed = new MessageEmbed(); + val.embed.setAuthor(i18next.t('commands:help.embedName')); + val.embed.setTitle(val.name.toUpperCase()); + val.embed.setColor(message.member?.roles.highest.color ?? 0xFFFFFF); + val.embed.setFooter(`Page ${index+1}/${commandGroups.length}`); + val.embed.setTimestamp(); + + val.commands.forEach((command) => { + val.embed.addField(`${config.get('prefix')}${command.options.name}`, command.options.shortDescription ?? i18next.t('commands:generic.noShortDescription').toUpperCase(), false); + }); + }); + + let currentPage = 0; + + const nextCategoryBtn = new MessageButton(); + nextCategoryBtn.setCustomID('nextCategoryBtn'); + nextCategoryBtn.setLabel(i18next.t('commands:help.nextCategoryBtn')); + nextCategoryBtn.setStyle('PRIMARY'); + + const previousCategoryBtn = new MessageButton(); + previousCategoryBtn.setCustomID('previousCategoryBtn'); + previousCategoryBtn.setLabel(i18next.t('commands:help.previousCategoryBtn')); + previousCategoryBtn.setStyle('SECONDARY'); + previousCategoryBtn.setDisabled(true); + + const helpMsg = await message.channel.send({ + content: i18next.t('commands:help.helpScreenBtnHelp'), + embeds: [commandGroups[0]!.embed], + components: [[previousCategoryBtn, nextCategoryBtn]], + }); + + const buttonCollector = helpMsg.channel.createMessageComponentInteractionCollector({ time: 60000 }); + + buttonCollector.on('collect', async (interaction) => { + if(interaction.customID === 'nextCategoryBtn') { + currentPage++; + + if(currentPage+1 === commandGroups.length) { + nextCategoryBtn.setDisabled(true); + } + + if(currentPage !== 0) { + previousCategoryBtn.setDisabled(false); + } + + await interaction.update({ + content: i18next.t('commands:help.helpScreenBtnHelp'), + embeds: [commandGroups[currentPage]!.embed], + components: [[previousCategoryBtn, nextCategoryBtn]], + }); + } else { + currentPage--; + + if(currentPage === 0) { + previousCategoryBtn.setDisabled(true); + nextCategoryBtn.setDisabled(false); + } + + if(currentPage !== 0) { + nextCategoryBtn.setDisabled(false); + } + + await interaction.update({ + content: i18next.t('commands:help.helpScreenBtnHelp'), + embeds: [commandGroups[currentPage]!.embed], + components: [[previousCategoryBtn, nextCategoryBtn]], + }); + } + }); + + buttonCollector.on('end', async () => { + nextCategoryBtn.setDisabled(true); + previousCategoryBtn.setDisabled(true); + + await helpMsg.edit({ + content: i18next.t('commands:help.helpTimedOut'), + embeds: [commandGroups[currentPage]!.embed], + components: [[previousCategoryBtn, nextCategoryBtn]], + }); + }); + } else { + const findCommand = this.client.commands.find((cmd) => cmd.options.name === command); + + if(findCommand) { + const commandEmbed = new MessageEmbed(); + commandEmbed.setAuthor(i18next.t('commands:help.embedName')); + commandEmbed.setTitle(findCommand.options.name!.toUpperCase()); + commandEmbed.setColor(message.member?.roles.highest.color ?? 0xFFFFFF); + commandEmbed.setTimestamp(); + + commandEmbed.setDescription(i18next.t('commands:help.commandDescription', { + name: findCommand.options.name, + description: findCommand.options.extendedDescription ?? i18next.t('commands:generic.noExtendedDescription'), + usage: findCommand.options.usage ? `!${findCommand.options.name} ${findCommand.options.usage}` : i18next.t('commands:generic.noUsage'), + })); + + await message.channel.send({ embeds: [commandEmbed] }); + } else { + await message.reply(i18next.t('commands:help.unknownCommand')); + } } } } diff --git a/src/index.ts b/src/index.ts index 908e5c6..bf6d0ae 100644 --- a/src/index.ts +++ b/src/index.ts @@ -15,6 +15,7 @@ * along with ArgonBot. If not, see . */ import 'module-alias/register'; +import '@utils/augments'; import ArgonClient from '@lib/ArgonClient'; import { Defaults } from '@utils/defaults'; @@ -33,7 +34,6 @@ import Fluent from 'i18next-fluent'; import FSBackend from 'i18next-fs-backend'; import { Validator } from 'jsonschema'; import { DateTime } from 'luxon'; -import path from 'path'; import process from 'process'; let isBotReady = false; @@ -146,7 +146,7 @@ client.on('ready', async () => { // eslint-disable-next-line @typescript-eslint/no-var-requires const fileCommand = require(file); - const command = new fileCommand['default'](client, path.basename(file, path.extname(file))); + const command = new fileCommand['default'](client, file); client.commands.set(command.options.name, command); @@ -156,6 +156,7 @@ client.on('ready', async () => { info(`Finished loading commands! Found ${client.commands.size} commands.`, ELoggingScope.Startup); } catch(err) { + console.log(err); fatal(`An error has occurred while attempting to load command files! Please see error below\n${err.message}`, ELoggingScope.Startup); } diff --git a/src/lib/ArgonClient.ts b/src/lib/ArgonClient.ts index 16a43d0..e461e24 100644 --- a/src/lib/ArgonClient.ts +++ b/src/lib/ArgonClient.ts @@ -18,7 +18,7 @@ import type Command from '@structures/command'; import { Client, ClientOptions, Collection } from 'discord.js'; export default class extends Client { - public readonly commands: Collection = new Collection(); + public override readonly commands: Collection = new Collection(); public constructor(options: ClientOptions) { super(options); diff --git a/src/lib/structures/command.ts b/src/lib/structures/command.ts index e8bedd5..6d67c5d 100644 --- a/src/lib/structures/command.ts +++ b/src/lib/structures/command.ts @@ -15,7 +15,6 @@ * along with ArgonBot. If not, see . */ import type { ICommandOptions } from '@utils/types'; -import { ECommandRunIn } from '@utils/types'; import config from 'config'; import type { Client, Message } from 'discord.js'; import path from 'path'; @@ -31,11 +30,7 @@ export default abstract class Command { const defaultOptions: ICommandOptions = { name: path.basename(this.file, path.extname(this.file)), - group: path.dirname(this.file) === 'commands' ? '' : path.dirname(this.file), - ownerOnly: false, - runIn: ECommandRunIn.Both, - shortDescription: '', - extendedDescription: '', + group: path.basename(path.dirname(this.file)) === 'commands' ? '' : path.basename(path.dirname(this.file)), }; this.options = config.util.extendDeep(defaultOptions, options); diff --git a/src/lib/utils/augments.ts b/src/lib/utils/augments.ts index 2686a0b..c7b1075 100644 --- a/src/lib/utils/augments.ts +++ b/src/lib/utils/augments.ts @@ -14,3 +14,13 @@ * You should have received a copy of the GNU General Public License * along with ArgonBot. If not, see . */ + +import type ArgonClient from '@lib/ArgonClient'; +import type Command from '@structures/command'; + +declare module 'discord.js' { + export interface Client { + constructor: typeof ArgonClient; + readonly commands: Collection; + } +} diff --git a/src/lib/utils/types.ts b/src/lib/utils/types.ts index 948b1c1..36cd516 100644 --- a/src/lib/utils/types.ts +++ b/src/lib/utils/types.ts @@ -32,9 +32,10 @@ export enum ECommandRunIn { // Interfaces export interface ICommandOptions { name?: string; + group?: string; shortDescription?: string; extendedDescription?: string; - group?: string; + usage?: string; ownerOnly?: boolean; runIn?: ECommandRunIn; args?: { diff --git a/translations/en-US/commands.json b/translations/en-US/commands.json index 6e009f9..d1140d0 100644 --- a/translations/en-US/commands.json +++ b/translations/en-US/commands.json @@ -12,7 +12,13 @@ "shortDescription": "Shows help menu", "extendedDescription": "Shows an advanced help menu for commands to show usage", "commandArg": "The command to see", - "unknownCommand": "Unknown command given!" + "unknownCommand": "Unknown command given!", + "embedName": "Help Menu!", + "helpScreenBtnHelp": "Press the \"Forward\" or \"Back\" Button to move categories!", + "nextCategoryBtn": "Next Category", + "previousCategoryBtn": "Previous Category", + "helpTimedOut": "Timed Out", + "commandDescription": "Name: { $name }\nCategory: { $category }\nDescription: { $description }\nUsage: { $usage }" }, "errors": { "ownerOnly": "Only the bot owner can run this command!", @@ -23,6 +29,7 @@ "generic": { "noShortDescription": "No short description given!", "noExtendedDescription": "No extended description given!", - "noArgsDescription": "No description for { $arg } has been given!" + "noArgsDescription": "No description for { $arg } has been given!", + "noUsage": "No usage given!" } }