Skip to content

Commit

Permalink
refactor: changed no match errors to emit on a subcommand listener in…
Browse files Browse the repository at this point in the history
…stead of the root MessageCommandError / ChatInputCommandError (#601)

BREAKING CHANGE: If no subcommand matched the error was emitted to `MessageCommandError` or `ChatInputCommandError`.
This was inconsistent with errors during runtime emitted to `MessageSubcommandError` and `ChatInputSubcommandError` respectively.
Therefore these events are now emitted to the new events `PluginMessageSubcommandNoMatch` and `PluginChatInputSubcommandNoMatch` respectively.
New built-in listeners for these events will log the infractions to the console at the error level. You can override these listeners to provide your functionality.
  • Loading branch information
favna authored Aug 7, 2024
1 parent 1299f99 commit 6e7a5bf
Show file tree
Hide file tree
Showing 5 changed files with 71 additions and 14 deletions.
33 changes: 20 additions & 13 deletions packages/subcommands/src/lib/Subcommand.ts
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,7 @@ export class Subcommand<PreParseReturn extends Args = Args, Options extends Subc
* Whether this command has message-based subcommands or not
* @returns `true` if this command has message-based subcommands, otherwise `false`
*/
public override supportsMessageCommands(): boolean {
public override supportsMessageCommands(): this is MessageCommand {
return this.#supportsCommandType('messageRun');
}

Expand Down Expand Up @@ -330,15 +330,17 @@ export class Subcommand<PreParseReturn extends Args = Args, Options extends Subc
return this.#handleMessageRun(message, args, context, defaultCommand, subcommandGroupName);
}

// No match and no subcommand, return an err:
throw new UserError({
const commandPrefix = this.#getCommandPrefix(message.content, args.commandContext.prefix);
const prefixLessContent = message.content.slice(commandPrefix.length).trim();

// No match and no subcommand, emit an error:
this.container.client.emit(SubcommandPluginEvents.MessageSubcommandNoMatch, message, args, {
...context,
command: this,
identifier: SubcommandPluginIdentifiers.MessageSubcommandNoMatch,
message: 'No subcommand was matched with the provided arguments.',
context: {
...context,
possibleSubcommandName: subcommandName.unwrapOr(null),
possibleSubcommandGroupOrName: subcommandOrGroup.unwrapOr(null)
}
message: `Unable to match a subcommand on message command "${this.name}" at path "${this.location.full}" with content ${prefixLessContent}`,
possibleSubcommandName: subcommandName.unwrapOr(null),
possibleSubcommandGroupOrName: subcommandOrGroup.unwrapOr(null)
});
}

Expand Down Expand Up @@ -376,14 +378,19 @@ export class Subcommand<PreParseReturn extends Args = Args, Options extends Subc
}
}

// No match and no subcommand, return an err:
throw new UserError({
// No match and no subcommand, emit an error:
this.container.client.emit(SubcommandPluginEvents.ChatInputSubcommandNoMatch, interaction, {
...context,
command: this,
identifier: SubcommandPluginIdentifiers.ChatInputSubcommandNoMatch,
message: 'No subcommand was matched with the provided command.',
context
message: `Unable to match a subcommand on chat input command "${this.name}" at path "${this.location.full}"`
});
}

#getCommandPrefix(content: string, prefix: string | RegExp): string {
return typeof prefix === 'string' ? prefix : prefix.exec(content)![0];
}

async #getMessageParametersAsString(args: Args): Promise<Partial<Pick<MessageCommandDeniedPayload, 'parameters'>>> {
args.save();
const parameters = await args.restResult('string');
Expand Down
15 changes: 14 additions & 1 deletion packages/subcommands/src/lib/types/Events.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { ChatInputCommand, MessageCommand, MessageCommandDeniedPayload, UserError } from '@sapphire/framework';
import type { Args, ChatInputCommand, ChatInputCommandContext, MessageCommand, MessageCommandDeniedPayload, UserError } from '@sapphire/framework';
import type { Message } from 'discord.js';
import type { Subcommand } from '../Subcommand';
import type { ChatInputCommandSubcommandMappingMethod, MessageSubcommandMappingMethod, SubcommandMappingMethod } from './SubcommandMappings';
Expand All @@ -8,11 +8,13 @@ export const SubcommandPluginEvents = {
ChatInputSubcommandRun: 'chatInputSubcommandRun' as const,
ChatInputSubcommandSuccess: 'chatInputSubcommandSuccess' as const,
ChatInputSubcommandError: 'chatInputSubcommandError' as const,
ChatInputSubcommandNoMatch: 'chatInputSubcommandNoMatch' as const,

MessageSubcommandDenied: 'messageSubcommandDenied' as const,
MessageSubcommandRun: 'messageSubcommandRun' as const,
MessageSubcommandSuccess: 'messageSubcommandSuccess' as const,
MessageSubcommandError: 'messageSubcommandError' as const,
MessageSubcommandNoMatch: 'messageSubcommandNoMatch' as const,

SubcommandMappingIsMissingMessageCommandHandler: 'subcommandMappingIsMissingMessageCommandHandler' as const,
SubcommandMappingIsMissingChatInputCommandHandler: 'subcommandMappingIsMissingChatInputCommandHandler' as const
Expand All @@ -25,10 +27,19 @@ export enum SubcommandPluginIdentifiers {
}

export interface MessageSubcommandNoMatchContext extends MessageCommand.RunContext {
command: Subcommand;
identifier: SubcommandPluginIdentifiers.MessageSubcommandNoMatch;
message: string;
possibleSubcommandName: string | null;
possibleSubcommandGroupOrName: string | null;
}

export interface ChatInputSubcommandNoMatchContext extends ChatInputCommandContext {
command: Subcommand;
identifier: SubcommandPluginIdentifiers.ChatInputSubcommandNoMatch;
message: string;
}

export interface IMessageSubcommandPayload {
message: Message;
command: Subcommand;
Expand Down Expand Up @@ -87,6 +98,7 @@ declare module 'discord.js' {
payload: ChatInputSubcommandSuccessPayload
];
[SubcommandPluginEvents.ChatInputSubcommandError]: [error: unknown, payload: ChatInputSubcommandErrorPayload];
[SubcommandPluginEvents.ChatInputSubcommandNoMatch]: [interaction: ChatInputCommand.Interaction, context: ChatInputSubcommandNoMatchContext];

[SubcommandPluginEvents.MessageSubcommandDenied]: [error: UserError, payload: MessageSubcommandDeniedPayload];
[SubcommandPluginEvents.MessageSubcommandRun]: [
Expand All @@ -100,6 +112,7 @@ declare module 'discord.js' {
payload: MessageSubcommandSuccessPayload
];
[SubcommandPluginEvents.MessageSubcommandError]: [error: unknown, payload: MessageSubcommandErrorPayload];
[SubcommandPluginEvents.MessageSubcommandNoMatch]: [message: Message, args: Args, context: MessageSubcommandNoMatchContext];

[SubcommandPluginEvents.SubcommandMappingIsMissingMessageCommandHandler]: [
message: Message,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { Listener, type ChatInputCommand } from '@sapphire/framework';
import { SubcommandPluginEvents, type ChatInputSubcommandNoMatchContext } from '../lib/types/Events';

export class PluginListener extends Listener<typeof SubcommandPluginEvents.ChatInputSubcommandNoMatch> {
public constructor(context: Listener.LoaderContext) {
super(context, { event: SubcommandPluginEvents.ChatInputSubcommandNoMatch });
}

public override run(_interaction: ChatInputCommand.Interaction, context: ChatInputSubcommandNoMatchContext) {
this.container.logger.error(context.message);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { Args, Listener } from '@sapphire/framework';
import type { Message } from 'discord.js';
import { SubcommandPluginEvents, type MessageSubcommandNoMatchContext } from '../lib/types/Events';

export class PluginListener extends Listener<typeof SubcommandPluginEvents.MessageSubcommandNoMatch> {
public constructor(context: Listener.LoaderContext) {
super(context, { event: SubcommandPluginEvents.MessageSubcommandNoMatch });
}

public override run(_message: Message, _args: Args, context: MessageSubcommandNoMatchContext) {
this.container.logger.error(context.message);
}
}
12 changes: 12 additions & 0 deletions packages/subcommands/src/listeners/_load.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { container } from '@sapphire/pieces';
import { PluginListener as PluginChatInputSubcommandError } from './PluginChatInputSubcommandError';
import { PluginListener as PluginChatInputSubcommandNoMatch } from './PluginChatInputSubcommandNoMatch';
import { PluginListener as PluginMessageSubcommandError } from './PluginMessageSubcommandError';
import { PluginListener as PluginMessageSubcommandNoMatch } from './PluginMessageSubcommandNoMatch';
import { PluginListener as PluginSubcommandMappingIsMissingChatInputCommandHandler } from './PluginSubcommandMappingIsMissingChatInputCommandHandler';
import { PluginListener as PluginSubcommandMappingIsMissingMessageCommandHandler } from './PluginSubcommandMappingIsMissingMessageCommandHandler';

Expand All @@ -18,4 +20,14 @@ export function loadListeners() {
piece: PluginSubcommandMappingIsMissingMessageCommandHandler,
store
});
void container.stores.loadPiece({
name: 'PluginMessageSubcommandNoMatch',
piece: PluginMessageSubcommandNoMatch,
store
});
void container.stores.loadPiece({
name: 'PluginChatInputSubcommandNoMatch',
piece: PluginChatInputSubcommandNoMatch,
store
});
}

0 comments on commit 6e7a5bf

Please sign in to comment.