diff --git a/@types/DiscordClient.d.ts b/@types/DiscordClient.d.ts index 5a59e5e..db6e30f 100644 --- a/@types/DiscordClient.d.ts +++ b/@types/DiscordClient.d.ts @@ -36,7 +36,8 @@ export type CommandDefinition = { help?: string, limited?: Snowflake[], showUsage?: boolean, - sameVc?: boolean + sameVc?: boolean, + description?: string, } & Omit export type ObserverOptions = { diff --git a/package.json b/package.json index f8d2a8f..d90964e 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,7 @@ "dependencies": { "@discordjs/opus": "^0.9.0", "@discordjs/voice": "^0.16.1", - "@navy.gif/commandparser": "^1.6.7", + "@navy.gif/commandparser": "^1.6.8", "@navy.gif/logger": "^2.5.4", "@navy.gif/timestring": "^6.0.6", "@types/node": "^20.11.30", diff --git a/src/client/DiscordClient.ts b/src/client/DiscordClient.ts index 3168185..3c328ba 100644 --- a/src/client/DiscordClient.ts +++ b/src/client/DiscordClient.ts @@ -98,7 +98,8 @@ class DiscordClient extends Client this.#built = true; this.emit('built'); - this.#musicPlayer.initialise(); + if (!process.env.DISABLE_MUSIC) + this.#musicPlayer.initialise(); return this; } diff --git a/src/client/components/MusicLibrary.ts b/src/client/components/MusicLibrary.ts index d8d1227..b0268e4 100644 --- a/src/client/components/MusicLibrary.ts +++ b/src/client/components/MusicLibrary.ts @@ -240,6 +240,7 @@ class MusicLibrary implements Initialisable return this.#logger.info('No index file found'); const raw = fs.readFileSync(indexPath, { encoding: 'utf-8' }); const parsed = JSON.parse(raw) as MusicIndexEntry[]; + this.#index.clear(); for (const entry of parsed) { if (entry.id >= this.#currentId) diff --git a/src/client/components/Registry.ts b/src/client/components/Registry.ts index ef53ee8..cf6f51c 100644 --- a/src/client/components/Registry.ts +++ b/src/client/components/Registry.ts @@ -74,7 +74,7 @@ class Registry throw new Error('Attempted to load an invalid component.'); if (this.#components.has(component.resolveable)) - throw new Error(`Attempted to reload an existing component: ${component.resolveable}`); + throw new Error(`Attempted to load an existing component: ${component.resolveable}`); if (directory) component.directory = directory; diff --git a/src/client/components/commands/List.ts b/src/client/components/commands/List.ts new file mode 100644 index 0000000..0abfb90 --- /dev/null +++ b/src/client/components/commands/List.ts @@ -0,0 +1,42 @@ +import { CommandOpts, OptionType } from '@navy.gif/commandparser'; +import Command from '../../../interfaces/Command.js'; +import DiscordClient from '../../DiscordClient.js'; +import { Message } from 'discord.js'; + +class ListCommand extends Command +{ + constructor (client: DiscordClient) + { + super(client, { + name: 'list', + description: 'Display a list of things on the bot.', + options: [{ + name: 'commands', + type: OptionType.SUB_COMMAND, + options: [{ + name: 'all', + type: OptionType.BOOLEAN, + valueOptional: true, + defaultValue: true, + flag: true + }] + }] + }); + } + + async execute (_message: Message, { subcommand, args }: CommandOpts) + { + const { all } = args; + if (subcommand === 'commands') + return this.#listCommands(all?.value as boolean); + return 'Invalid subcommand'; + } + + #listCommands (all?: boolean) + { + const commands= this.client.registry.commands.filter(cmd => !cmd.restricted || all); + return `**Available commands**\n*Use \`${this.client.prefix}command --help\` to display command options*\n${commands.map(cmd => `\t\\- __${cmd.name}__: ${cmd.description}`).join('\n')}`; + } +} + +export default ListCommand; \ No newline at end of file diff --git a/src/client/components/commands/Ping.ts b/src/client/components/commands/Ping.ts index b35e112..c4e19a8 100644 --- a/src/client/components/commands/Ping.ts +++ b/src/client/components/commands/Ping.ts @@ -6,7 +6,8 @@ class PingCommand extends Command constructor (client: DiscordClient) { super(client, { - name: 'ping' + name: 'ping', + description: 'pong' }); } diff --git a/src/client/components/commands/Queue.ts b/src/client/components/commands/Queue.ts index e0fbf6d..01c53af 100644 --- a/src/client/components/commands/Queue.ts +++ b/src/client/components/commands/Queue.ts @@ -10,6 +10,7 @@ class QueueCommand extends Command super(client, { name: 'queue', aliases: [ 'q' ], + description: 'Queue a song (must be already downloaded, use search to look up songs), or display the current queue.', options: [{ name: 'artist', flag: true diff --git a/src/client/components/commands/Request.ts b/src/client/components/commands/Request.ts index 1a36530..909cc25 100644 --- a/src/client/components/commands/Request.ts +++ b/src/client/components/commands/Request.ts @@ -12,6 +12,7 @@ class RequestCommand extends Command super(client, { name: 'request', aliases: [ 'r', 'req' ], + description: 'Request a song to be downloaded and queued.', guildOnly: true, sameVc: true, showUsage: true, diff --git a/src/client/components/commands/Rescan.ts b/src/client/components/commands/Rescan.ts index bdb81c3..3f64d2c 100644 --- a/src/client/components/commands/Rescan.ts +++ b/src/client/components/commands/Rescan.ts @@ -10,6 +10,7 @@ class PingCommand extends Command super(client, { name: 'rescan', restricted: true, + description: 'Rescan the library and add to index.', options: [{ name: 'rebuild', type: OptionType.BOOLEAN, diff --git a/src/client/components/commands/Reshuffle.ts b/src/client/components/commands/Reshuffle.ts index 0d54c8b..41d500e 100644 --- a/src/client/components/commands/Reshuffle.ts +++ b/src/client/components/commands/Reshuffle.ts @@ -7,6 +7,7 @@ class ReshuffleCommand extends Command { super(client, { name: 'reshuffle', + description: 'Reshuffles the playlist (does not apply to queue).', sameVc: true, guildOnly: true, }); diff --git a/src/client/components/commands/Search.ts b/src/client/components/commands/Search.ts index a8bed87..ad419e9 100644 --- a/src/client/components/commands/Search.ts +++ b/src/client/components/commands/Search.ts @@ -9,7 +9,8 @@ class SearchCommand extends Command { super(client, { name: 'search', - aliases: [ 's' ], + aliases: [ 's', 'query' ], + description: 'Search the index for an already downloaded song.', showUsage: true, options: [{ name: 'keyword', diff --git a/src/client/components/commands/SetAvatar.ts b/src/client/components/commands/SetAvatar.ts index 47db357..c4d2583 100644 --- a/src/client/components/commands/SetAvatar.ts +++ b/src/client/components/commands/SetAvatar.ts @@ -9,6 +9,7 @@ class SetAvatarCommand extends Command { super(client, { name: 'set', + description: 'Sets various things on the bot.', options: [{ name: 'avatar', type: OptionType.SUB_COMMAND, diff --git a/src/client/components/commands/Skip.ts b/src/client/components/commands/Skip.ts index 17f15fa..5cad59f 100644 --- a/src/client/components/commands/Skip.ts +++ b/src/client/components/commands/Skip.ts @@ -8,6 +8,7 @@ class SkipCommand extends Command { super(client, { name: 'skip', + description: 'Skips the current song.', guildOnly: true, restricted: true, sameVc: true diff --git a/src/client/components/commands/Volume.ts b/src/client/components/commands/Volume.ts index 7627a54..c038238 100644 --- a/src/client/components/commands/Volume.ts +++ b/src/client/components/commands/Volume.ts @@ -10,6 +10,7 @@ class VolumeCommand extends Command super(client, { name: 'volume', aliases: [ 'v', 'vol' ], + description: 'Display or set the volume.', options: [ { name: 'volume', diff --git a/src/client/components/observers/CommandHandler.ts b/src/client/components/observers/CommandHandler.ts index 6d3695a..eddc38f 100644 --- a/src/client/components/observers/CommandHandler.ts +++ b/src/client/components/observers/CommandHandler.ts @@ -30,6 +30,7 @@ class CommandHandler extends Observer commands: this.client.commands.values(), prefix: client.prefix, resolver: client.resolver, + allowIncompleteReturn: true, globalFlags: [{ name: 'help', flag: true, @@ -47,6 +48,8 @@ class CommandHandler extends Observer { const allowedMentions = { repliedUser: false }; const { author } = message; + if (author.bot) + return; let result = null, inhibitions: InhibitorResponse[] | null = null; try @@ -128,10 +131,10 @@ class CommandHandler extends Observer } if (rest.globalFlags.help) - return this.#showUsage(message, command as Command); + return this.#showUsage(message, command as Command, rest); if ((command as Command).showUsage && !Object.keys(rest.args).length && !rest.subcommand && !rest.subcommandGroup) - return this.#showUsage(message, command as Command); + return this.#showUsage(message, command as Command, rest); this.#executeCommand(message, command, rest); } @@ -234,14 +237,39 @@ class CommandHandler extends Observer } } - #showUsage (message: Message, command: Command): void | PromiseLike + #showUsage (message: Message, command: Command, opts: CommandOpts): void | PromiseLike { - const { options } = command; + const { subcommand, subcommandGroup } = opts; + let sbcmdstr = ''; + if (subcommandGroup) + sbcmdstr += `${subcommand}`; + if (subcommand) + sbcmdstr += ` ${subcommand}`; + sbcmdstr = sbcmdstr.trim(); + + let { options } = command; + let usageStr = `${this.client.prefix}${command.name} `; + if (sbcmdstr) + usageStr += sbcmdstr; + else if (options.length) + usageStr += '[OPTIONS]'; + + let output = stripIndents` + USAGE: \`${usageStr.trim()} [FLAGS]\` + `; + + if (subcommand) + { + const sbcmd = command.subcommand(subcommand); + ({ options } = sbcmd!); + } + else if (subcommandGroup) + { + const group = command.subcommandGroup(subcommandGroup); + ({ options } = group!); + } const flags = options.filter(option => option.flag); const nonFlags = options.filter(option => !option.flag); - let output = stripIndents` - USAGE: \`${this.client.prefix}${command.name} [OPTIONS] [FLAGS]\` - `; if (nonFlags.length) { diff --git a/src/interfaces/Command.ts b/src/interfaces/Command.ts index 4a7ba4b..326be08 100644 --- a/src/interfaces/Command.ts +++ b/src/interfaces/Command.ts @@ -11,6 +11,7 @@ abstract class Command extends Component implements ICommand #aliases: string[]; #options: CommandOption[]; #help?: string | undefined; + #description?: string; #logger: LoggerClient; @@ -35,6 +36,7 @@ abstract class Command extends Component implements ICommand this.#dmOnly = def.dmOnly ?? false; this.#limited = def.limited ?? null; this.#showUsage = def.showUsage ?? false; + this.#description = def.description; this.#options = []; @@ -69,6 +71,11 @@ abstract class Command extends Component implements ICommand throw new CommandError(this, { reason: 'Command timed out', user }); } + get description () + { + return this.#description ?? '[No description given]'; + } + get showUsage () { return this.#showUsage; diff --git a/yarn.lock b/yarn.lock index 10330f0..78baa64 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1628,10 +1628,10 @@ __metadata: languageName: node linkType: hard -"@navy.gif/commandparser@npm:^1.6.7": - version: 1.6.7 - resolution: "@navy.gif/commandparser@npm:1.6.7" - checksum: 10/2f3ac85ca0d7168af96fd5d03ff85e00949e8f02ff7ac409e01f492c9712442723df536103897bed7d3c3091066525e78254e36591e49d1a9cade16f472383e9 +"@navy.gif/commandparser@npm:^1.6.8": + version: 1.6.8 + resolution: "@navy.gif/commandparser@npm:1.6.8" + checksum: 10/53f2d4f9a8f98a83d2304297bcabbe7defe5cf5500024f9d9c63efb4d0e631fe6545b2621421b43f05f8e9af1a2dad3ac79b5898faceb995fbb5b7db2cb3bb1d languageName: node linkType: hard @@ -3577,7 +3577,7 @@ __metadata: "@babel/preset-typescript": "npm:^7.24.1" "@discordjs/opus": "npm:^0.9.0" "@discordjs/voice": "npm:^0.16.1" - "@navy.gif/commandparser": "npm:^1.6.7" + "@navy.gif/commandparser": "npm:^1.6.8" "@navy.gif/logger": "npm:^2.5.4" "@navy.gif/timestring": "npm:^6.0.6" "@types/babel__core": "npm:^7"