Skip to content

Commit 68d8c87

Browse files
committed
fix: move stuff to base class and use name option
1 parent 2292237 commit 68d8c87

File tree

7 files changed

+135
-216
lines changed

7 files changed

+135
-216
lines changed

src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,8 @@ export * from './lib/errors/ArgumentError';
6767
export * from './lib/errors/Identifiers';
6868
export * from './lib/errors/PreconditionError';
6969
export * from './lib/errors/UserError';
70+
export * from './lib/parsers/Args';
71+
export * from './lib/parsers/ChatInputCommandArgs';
7072
export * from './lib/parsers/MessageArgs';
7173
export * from './lib/plugins/Plugin';
7274
export * from './lib/plugins/PluginManager';

src/lib/parsers/Args.ts

Lines changed: 72 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import type { ChannelTypes, GuildBasedChannelTypes } from '@sapphire/discord.js-utilities';
2-
import { Result, type Option } from '@sapphire/result';
1+
import type { AnyInteraction, ChannelTypes, GuildBasedChannelTypes } from '@sapphire/discord.js-utilities';
2+
import { Result } from '@sapphire/result';
33
import type {
44
CategoryChannel,
55
ChannelType,
@@ -15,10 +15,13 @@ import type {
1515
VoiceChannel
1616
} from 'discord.js';
1717
import { ArgumentError } from '../errors/ArgumentError';
18-
import type { UserError } from '../errors/UserError';
18+
import { UserError } from '../errors/UserError';
1919
import type { EmojiObject } from '../resolvers/emoji';
2020
import type { Argument, IArgument } from '../structures/Argument';
2121
import type { Awaitable } from '@sapphire/utilities';
22+
import { Identifiers } from '../errors/Identifiers';
23+
import { container } from '@sapphire/pieces';
24+
import type { Command } from '../structures/Command';
2225

2326
export abstract class Args {
2427
public abstract start(): this;
@@ -28,9 +31,32 @@ export abstract class Args {
2831
public abstract rest<T extends ArgsOptions>(options: T): Promise<InferArgReturnType<T>>;
2932
public abstract repeatResult<T extends ArgsOptions>(options: T): Promise<ArrayResultType<InferArgReturnType<T>>>;
3033
public abstract repeat<T extends ArgsOptions>(options: T): Promise<InferArgReturnType<T>[]>;
31-
public abstract peekResult<T extends ArgsOptions>(options: T): Promise<ResultType<InferArgReturnType<T>>>;
32-
public abstract peek<T extends ArgsOptions>(options: T): Promise<InferArgReturnType<T>>;
33-
// nextMaybe, next, getFlags, getOptionResult, getOption, getOptionsResult, getOptions should only go on message args
34+
public abstract peekResult<T extends PeekArgsOptions>(options: T): Promise<ResultType<InferArgReturnType<T>>>;
35+
public abstract peek<T extends PeekArgsOptions>(options: T): Promise<InferArgReturnType<T>>;
36+
37+
protected unavailableArgument<T>(type: string | IArgument<T>): Result.Err<UserError> {
38+
const name = typeof type === 'string' ? type : type.name;
39+
return Result.err(
40+
new UserError({
41+
identifier: Identifiers.ArgsUnavailable,
42+
message: `The argument "${name}" was not found.`,
43+
context: { name, ...this.toJSON() }
44+
})
45+
);
46+
}
47+
48+
protected missingArguments(): Result.Err<UserError> {
49+
return Result.err(new UserError({ identifier: Identifiers.ArgsMissing, message: 'There are no more arguments.', context: this.toJSON() }));
50+
}
51+
52+
/**
53+
* Resolves an argument.
54+
* @param arg The argument name or {@link IArgument} instance.
55+
*/
56+
protected resolveArgument<T>(arg: keyof ArgType | IArgument<T>): IArgument<T> | undefined {
57+
if (typeof arg === 'object') return arg;
58+
return container.stores.get('arguments').get(arg as string) as IArgument<T> | undefined;
59+
}
3460

3561
/**
3662
* Converts a callback into a usable argument.
@@ -56,6 +82,46 @@ export abstract class Args {
5682
public static error<T>(options: ArgumentError.Options<T>): Result.Err<ArgumentError<T>> {
5783
return Result.err(new ArgumentError<T>(options));
5884
}
85+
86+
/**
87+
* Defines the `JSON.stringify` override.
88+
*/
89+
public abstract toJSON(): ArgsJson;
90+
}
91+
92+
export interface ArgsJson {
93+
message: Message | AnyInteraction;
94+
command: Command;
95+
commandContext: Record<PropertyKey, unknown>;
96+
}
97+
98+
export interface ArgType {
99+
boolean: boolean;
100+
channel: ChannelTypes;
101+
date: Date;
102+
dmChannel: DMChannel;
103+
emoji: EmojiObject;
104+
float: number;
105+
guildCategoryChannel: CategoryChannel;
106+
guildChannel: GuildBasedChannelTypes;
107+
guildNewsChannel: NewsChannel;
108+
guildNewsThreadChannel: ThreadChannel & { type: ChannelType.AnnouncementThread; parent: NewsChannel | null };
109+
guildPrivateThreadChannel: ThreadChannel & { type: ChannelType.PrivateThread; parent: TextChannel | null };
110+
guildPublicThreadChannel: ThreadChannel & { type: ChannelType.PublicThread; parent: TextChannel | null };
111+
guildStageVoiceChannel: StageChannel;
112+
guildTextChannel: TextChannel;
113+
guildThreadChannel: ThreadChannel;
114+
guildVoiceChannel: VoiceChannel;
115+
hyperlink: URL;
116+
integer: number;
117+
member: GuildMember;
118+
message: Message;
119+
number: number;
120+
role: Role;
121+
string: string;
122+
url: URL;
123+
user: User;
124+
enum: string;
59125
}
60126

61127
export interface ArgsOptions<T = unknown, K extends keyof ArgType = keyof ArgType>
@@ -125,13 +191,3 @@ export interface ArgType {
125191

126192
export type ResultType<T> = Result<T, UserError | ArgumentError<T>>;
127193
export type ArrayResultType<T> = Result<T[], UserError | ArgumentError<T>>;
128-
129-
/**
130-
* The callback used for {@link Args.nextMaybe} and {@link Args.next}.
131-
*/
132-
export interface ArgsNextCallback<T> {
133-
/**
134-
* The value to be mapped.
135-
*/
136-
(value: string): Option<T>;
137-
}

src/lib/parsers/ChatInputCommandArgs.ts

Lines changed: 24 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,21 @@
1-
import type { AnyInteraction, ChannelTypes, GuildBasedChannelTypes } from '@sapphire/discord.js-utilities';
21
import { join, type Parameter } from '@sapphire/lexure';
3-
import { container } from '@sapphire/pieces';
4-
import { Option, Result } from '@sapphire/result';
5-
import type {
6-
CategoryChannel,
7-
ChannelType,
8-
ChatInputCommandInteraction,
9-
CommandInteraction,
10-
DMChannel,
11-
GuildMember,
12-
Message,
13-
NewsChannel,
14-
Role,
15-
StageChannel,
16-
TextChannel,
17-
ThreadChannel,
18-
User,
19-
VoiceChannel
20-
} from 'discord.js';
21-
import type { URL } from 'node:url';
2+
import { Result } from '@sapphire/result';
3+
import type { ChatInputCommandInteraction, CommandInteractionOption } from 'discord.js';
224
import { ArgumentError } from '../errors/ArgumentError';
23-
import { Identifiers } from '../errors/Identifiers';
245
import { UserError } from '../errors/UserError';
25-
import type { EmojiObject } from '../resolvers/emoji';
26-
import type { IArgument } from '../structures/Argument';
276
import { Command } from '../structures/Command';
28-
import { Args, type ArgsOptions, type InferArgReturnType, type PeekArgsOptions, type RepeatArgsOptions } from './Args';
7+
import {
8+
Args,
9+
type ArgsJson,
10+
type ArgsOptions,
11+
type ArrayResultType,
12+
type InferArgReturnType,
13+
type PeekArgsOptions,
14+
type RepeatArgsOptions,
15+
type ResultType
16+
} from './Args';
2917
import type { ChatInputParser } from './ChatInputParser';
18+
import type { ChatInputCommand } from '../types/CommandTypes';
3019

3120
/**
3221
* The argument parser to be used in {@link Command}.
@@ -40,7 +29,7 @@ export class ChatInputCommandArgs extends Args {
4029
/**
4130
* The command that is being run.
4231
*/
43-
public readonly command: Command;
32+
public readonly command: ChatInputCommand;
4433

4534
/**
4635
* The context of the command being run.
@@ -57,9 +46,14 @@ export class ChatInputCommandArgs extends Args {
5746
* @see Args#save
5847
* @see Args#restore
5948
*/
60-
private readonly states: number[] = [];
61-
62-
public constructor(interaction: ChatInputCommandInteraction, command: Command, parser: ChatInputParser, context: Record<PropertyKey, unknown>) {
49+
private readonly states: Set<CommandInteractionOption>[] = [];
50+
51+
public constructor(
52+
interaction: ChatInputCommandInteraction,
53+
command: ChatInputCommand,
54+
parser: ChatInputParser,
55+
context: Record<PropertyKey, unknown>
56+
) {
6357
super();
6458
this.interaction = interaction;
6559
this.command = command;
@@ -126,7 +120,7 @@ export class ChatInputCommandArgs extends Args {
126120
const argument = this.resolveArgument(options.type);
127121
if (!argument) return this.unavailableArgument(options.type);
128122

129-
const result = await this.parser.singleParseAsync(async (arg) =>
123+
const result = await this.parser.singleParseAsync(options.name, async (arg) =>
130124
argument.run(arg, {
131125
args: this,
132126
argument,
@@ -317,7 +311,7 @@ export class ChatInputCommandArgs extends Args {
317311
const output: InferArgReturnType<T>[] = [];
318312

319313
for (let i = 0, times = options.times ?? Infinity; i < times; i++) {
320-
const result = await this.parser.singleParseAsync(async (arg) =>
314+
const result = await this.parser.singleParseAsync(options.name, async (arg) =>
321315
argument.run(arg, {
322316
args: this,
323317
argument,
@@ -551,76 +545,4 @@ export class ChatInputCommandArgs extends Args {
551545
public toJSON(): ArgsJson {
552546
return { message: this.interaction, command: this.command, commandContext: this.commandContext };
553547
}
554-
555-
protected unavailableArgument<T>(type: string | IArgument<T>): Result.Err<UserError> {
556-
const name = typeof type === 'string' ? type : type.name;
557-
return Result.err(
558-
new UserError({
559-
identifier: Identifiers.ArgsUnavailable,
560-
message: `The argument "${name}" was not found.`,
561-
context: { name, ...this.toJSON() }
562-
})
563-
);
564-
}
565-
566-
protected missingArguments(): Result.Err<UserError> {
567-
return Result.err(new UserError({ identifier: Identifiers.ArgsMissing, message: 'There are no more arguments.', context: this.toJSON() }));
568-
}
569-
570-
/**
571-
* Resolves an argument.
572-
* @param arg The argument name or {@link IArgument} instance.
573-
*/
574-
private resolveArgument<T>(arg: keyof ArgType | IArgument<T>): IArgument<T> | undefined {
575-
if (typeof arg === 'object') return arg;
576-
return container.stores.get('arguments').get(arg as string) as IArgument<T> | undefined;
577-
}
578548
}
579-
580-
export interface ArgsJson {
581-
message: Message | AnyInteraction;
582-
command: Command;
583-
commandContext: Record<PropertyKey, unknown>;
584-
}
585-
586-
export interface ArgType {
587-
boolean: boolean;
588-
channel: ChannelTypes;
589-
date: Date;
590-
dmChannel: DMChannel;
591-
emoji: EmojiObject;
592-
float: number;
593-
guildCategoryChannel: CategoryChannel;
594-
guildChannel: GuildBasedChannelTypes;
595-
guildNewsChannel: NewsChannel;
596-
guildNewsThreadChannel: ThreadChannel & { type: ChannelType.AnnouncementThread; parent: NewsChannel | null };
597-
guildPrivateThreadChannel: ThreadChannel & { type: ChannelType.PrivateThread; parent: TextChannel | null };
598-
guildPublicThreadChannel: ThreadChannel & { type: ChannelType.PublicThread; parent: TextChannel | null };
599-
guildStageVoiceChannel: StageChannel;
600-
guildTextChannel: TextChannel;
601-
guildThreadChannel: ThreadChannel;
602-
guildVoiceChannel: VoiceChannel;
603-
hyperlink: URL;
604-
integer: number;
605-
member: GuildMember;
606-
message: Message;
607-
number: number;
608-
role: Role;
609-
string: string;
610-
url: URL;
611-
user: User;
612-
enum: string;
613-
}
614-
615-
/**
616-
* The callback used for {@link Args.nextMaybe} and {@link Args.next}.
617-
*/
618-
export interface ArgsNextCallback<T> {
619-
/**
620-
* The value to be mapped.
621-
*/
622-
(value: CommandInteraction): Option<T>;
623-
}
624-
625-
export type ResultType<T> = Result<T, UserError | ArgumentError<T>>;
626-
export type ArrayResultType<T> = Result<T[], UserError | ArgumentError<T>>;

src/lib/parsers/ChatInputParser.ts

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,35 +3,39 @@ import { Option, Result } from '@sapphire/result';
33
import { type Parameter } from '@sapphire/lexure';
44

55
export class ChatInputParser {
6-
public position: number = 0;
6+
public used: Set<CommandInteractionOption> = new Set();
77

88
public constructor(public interaction: CommandInteraction) {}
99

1010
public get finished(): boolean {
11-
return this.position === this.interaction.options.data.length;
11+
return this.used.size === this.interaction.options.data.length;
1212
}
1313

1414
public reset(): void {
15-
this.position = 0;
15+
this.used.clear();
1616
}
1717

18-
public save(): number {
19-
return this.position;
18+
public save(): Set<CommandInteractionOption> {
19+
return new Set(this.used);
2020
}
2121

22-
public restore(state: number): void {
23-
this.position = state;
22+
public restore(state: Set<CommandInteractionOption>): void {
23+
this.used = state;
2424
}
2525

2626
public async singleParseAsync<T, E>(
27+
name: string,
2728
predicate: (arg: CommandInteractionOption) => Promise<Result<T, E>>,
2829
useAnyways?: boolean
2930
): Promise<Result<T, E | null>> {
3031
if (this.finished) return Result.err(null);
3132

32-
const result = await predicate(this.interaction.options.data[this.position]);
33+
const option = this.interaction.options.data.find((option) => option.name === name);
34+
if (!option) return Result.err(null);
35+
36+
const result = await predicate(option);
3337
if (result.isOk() || useAnyways) {
34-
this.position++;
38+
this.used.add(option);
3539
}
3640
return result;
3741
}

0 commit comments

Comments
 (0)