Skip to content

Commit

Permalink
feat: add interaction support to args
Browse files Browse the repository at this point in the history
  • Loading branch information
samfundev committed Feb 17, 2024
1 parent 3a80eaa commit 386e9e2
Show file tree
Hide file tree
Showing 25 changed files with 189 additions and 58 deletions.
2 changes: 1 addition & 1 deletion src/arguments/CoreChannel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export class CoreArgument extends Argument<ChannelTypes> {
}

public run(parameter: string, context: Argument.Context): Argument.Result<ChannelTypes> {
const resolved = resolveChannel(parameter, context.message);
const resolved = resolveChannel(parameter, context.messageOrInteraction);
return resolved.mapErrInto((identifier) =>
this.error({
parameter,
Expand Down
2 changes: 1 addition & 1 deletion src/arguments/CoreDMChannel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export class CoreArgument extends Argument<DMChannel> {
}

public run(parameter: string, context: Argument.Context): Argument.Result<DMChannel> {
const resolved = resolveDMChannel(parameter, context.message);
const resolved = resolveDMChannel(parameter, context.messageOrInteraction);
return resolved.mapErrInto((identifier) =>
this.error({
parameter,
Expand Down
2 changes: 1 addition & 1 deletion src/arguments/CoreGuildCategoryChannel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export class CoreArgument extends Argument<CategoryChannel> {
}

public run(parameter: string, context: Argument.Context): Argument.Result<CategoryChannel> {
const { guild } = context.message;
const { guild } = context.messageOrInteraction;
if (!guild) {
return this.error({
parameter,
Expand Down
2 changes: 1 addition & 1 deletion src/arguments/CoreGuildChannel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export class CoreArgument extends Argument<GuildBasedChannelTypes> {
}

public run(parameter: string, context: Argument.Context): Argument.Result<GuildBasedChannelTypes> {
const { guild } = context.message;
const { guild } = context.messageOrInteraction;
if (!guild) {
return this.error({
parameter,
Expand Down
2 changes: 1 addition & 1 deletion src/arguments/CoreGuildNewsChannel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export class CoreArgument extends Argument<NewsChannel> {
}

public run(parameter: string, context: Argument.Context): Argument.Result<NewsChannel> {
const { guild } = context.message;
const { guild } = context.messageOrInteraction;
if (!guild) {
return this.error({
parameter,
Expand Down
2 changes: 1 addition & 1 deletion src/arguments/CoreGuildNewsThreadChannel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export class CoreArgument extends Argument<ThreadChannel> {
}

public run(parameter: string, context: Argument.Context): Argument.Result<ThreadChannel> {
const { guild } = context.message;
const { guild } = context.messageOrInteraction;
if (!guild) {
return this.error({
parameter,
Expand Down
2 changes: 1 addition & 1 deletion src/arguments/CoreGuildPrivateThreadChannel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export class CoreArgument extends Argument<ThreadChannel> {
}

public run(parameter: string, context: Argument.Context): Argument.Result<ThreadChannel> {
const { guild } = context.message;
const { guild } = context.messageOrInteraction;
if (!guild) {
return this.error({
parameter,
Expand Down
2 changes: 1 addition & 1 deletion src/arguments/CoreGuildPublicThreadChannel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export class CoreArgument extends Argument<ThreadChannel> {
}

public run(parameter: string, context: Argument.Context): Argument.Result<ThreadChannel> {
const { guild } = context.message;
const { guild } = context.messageOrInteraction;
if (!guild) {
return this.error({
parameter,
Expand Down
2 changes: 1 addition & 1 deletion src/arguments/CoreGuildStageVoiceChannel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export class CoreArgument extends Argument<StageChannel> {
}

public run(parameter: string, context: Argument.Context): Argument.Result<StageChannel> {
const { guild } = context.message;
const { guild } = context.messageOrInteraction;
if (!guild) {
return this.error({
parameter,
Expand Down
2 changes: 1 addition & 1 deletion src/arguments/CoreGuildTextChannel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export class CoreArgument extends Argument<TextChannel> {
}

public run(parameter: string, context: Argument.Context): Argument.Result<TextChannel> {
const { guild } = context.message;
const { guild } = context.messageOrInteraction;
if (!guild) {
return this.error({
parameter,
Expand Down
2 changes: 1 addition & 1 deletion src/arguments/CoreGuildThreadChannel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export class CoreArgument extends Argument<ThreadChannel> {
}

public run(parameter: string, context: Argument.Context): Argument.Result<ThreadChannel> {
const { guild } = context.message;
const { guild } = context.messageOrInteraction;
if (!guild) {
return this.error({
parameter,
Expand Down
2 changes: 1 addition & 1 deletion src/arguments/CoreGuildVoiceChannel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export class CoreArgument extends Argument<VoiceChannel> {
}

public run(parameter: string, context: Argument.Context): Argument.Result<VoiceChannel> {
const { guild } = context.message;
const { guild } = context.messageOrInteraction;
if (!guild) {
return this.error({
parameter,
Expand Down
2 changes: 1 addition & 1 deletion src/arguments/CoreMember.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export class CoreArgument extends Argument<GuildMember> {
}

public async run(parameter: string, context: MemberArgumentContext): Argument.AsyncResult<GuildMember> {
const { guild } = context.message;
const { guild } = context.messageOrInteraction;

if (!guild) {
return this.error({
Expand Down
4 changes: 2 additions & 2 deletions src/arguments/CoreMessage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ export class CoreArgument extends Argument<Message> {
}

public async run(parameter: string, context: MessageArgumentContext): Argument.AsyncResult<Message> {
const channel = context.channel ?? context.message.channel;
const channel = context.channel ?? context.messageOrInteraction.channel;
const resolved = await resolveMessage(parameter, {
messageOrInteraction: context.message,
messageOrInteraction: context.messageOrInteraction,
channel: context.channel,
scan: context.scan ?? false
});
Expand Down
2 changes: 1 addition & 1 deletion src/arguments/CorePartialDMChannel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export class CoreArgument extends Argument<DMChannel | PartialDMChannel> {
}

public run(parameter: string, context: Argument.Context): Argument.Result<DMChannel | PartialDMChannel> {
const resolved = resolvePartialDMChannel(parameter, context.message);
const resolved = resolvePartialDMChannel(parameter, context.messageOrInteraction);
return resolved.mapErrInto((identifier) =>
this.error({
parameter,
Expand Down
2 changes: 1 addition & 1 deletion src/arguments/CoreRole.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export class CoreArgument extends Argument<Role> {
}

public async run(parameter: string, context: Argument.Context): Argument.AsyncResult<Role> {
const { guild } = context.message;
const { guild } = context.messageOrInteraction;
if (!guild) {
return this.error({
parameter,
Expand Down
55 changes: 31 additions & 24 deletions src/lib/parsers/Args.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import type { ChannelTypes, GuildBasedChannelTypes } from '@sapphire/discord.js-utilities';
import { join, type ArgumentStream, type Parameter } from '@sapphire/lexure';
import type { AnyInteraction, ChannelTypes, GuildBasedChannelTypes } from '@sapphire/discord.js-utilities';
import { join, type Parameter } from '@sapphire/lexure';
import { container } from '@sapphire/pieces';
import { Option, Result } from '@sapphire/result';
import type { Awaitable } from '@sapphire/utilities';
import type {
CategoryChannel,
ChannelType,
ChatInputCommandInteraction,
DMChannel,
GuildMember,
Message,
Expand All @@ -23,41 +24,47 @@ import { Identifiers } from '../errors/Identifiers';
import { UserError } from '../errors/UserError';
import type { EmojiObject } from '../resolvers/emoji';
import type { Argument, IArgument } from '../structures/Argument';
import type { MessageCommand } from '../types/CommandTypes';
import { Command } from '../structures/Command';
import type { Parser, Arg } from './Parser';

/**
* The argument parser to be used in {@link Command}.
*/
export class Args {
/**
* The original message that triggered the command.
* The original message or interaction that triggered the command.
*/
public readonly message: Message;
public readonly messageOrInteraction: Message | ChatInputCommandInteraction;

/**
* The command that is being run.
*/
public readonly command: MessageCommand;
public readonly command: Command;

/**
* The context of the command being run.
*/
public readonly commandContext: MessageCommand.RunContext;
public readonly commandContext: Record<PropertyKey, unknown>;

/**
* The internal Lexure parser.
*/
protected readonly parser: ArgumentStream;
protected readonly parser: Parser;

/**
* The states stored in the args.
* @see Args#save
* @see Args#restore
*/
private readonly states: ArgumentStream.State[] = [];
private readonly states: unknown[] = [];

public constructor(message: Message, command: MessageCommand, parser: ArgumentStream, context: MessageCommand.RunContext) {
this.message = message;
public constructor(
messageOrInteraction: Message | ChatInputCommandInteraction,
command: Command,
parser: Parser,
context: Record<PropertyKey, unknown>
) {
this.messageOrInteraction = messageOrInteraction;
this.command = command;
this.parser = parser;
this.commandContext = context;
Expand Down Expand Up @@ -127,7 +134,7 @@ export class Args {
argument.run(arg, {
args: this,
argument,
message: this.message,
messageOrInteraction: this.messageOrInteraction,
command: this.command,
commandContext: this.commandContext,
...options
Expand Down Expand Up @@ -233,7 +240,7 @@ export class Args {
const result = await argument.run(data, {
args: this,
argument,
message: this.message,
messageOrInteraction: this.messageOrInteraction,
command: this.command,
commandContext: this.commandContext,
...options
Expand Down Expand Up @@ -322,7 +329,7 @@ export class Args {
argument.run(arg, {
args: this,
argument,
message: this.message,
messageOrInteraction: this.messageOrInteraction,
command: this.command,
commandContext: this.commandContext,
...options
Expand Down Expand Up @@ -540,7 +547,7 @@ export class Args {
* // -> { exists: true, value: '1' }
* ```
*/
public nextMaybe(): Option<string>;
public nextMaybe(): Option<Arg>;
/**
* Retrieves the value of the next unused ordered token, but only if it could be transformed.
* That token will now be used if the transformation succeeds.
Expand All @@ -559,8 +566,8 @@ export class Args {
* ```
*/
public nextMaybe<T>(cb: ArgsNextCallback<T>): Option<T>;
public nextMaybe<T>(cb?: ArgsNextCallback<T>): Option<T | string> {
return Option.from<T | string>(typeof cb === 'function' ? this.parser.singleMap(cb) : this.parser.single());
public nextMaybe<T>(cb?: ArgsNextCallback<T>): Option<T | Arg> {
return Option.from<T | Arg>(typeof cb === 'function' ? this.parser.singleMap(cb) : this.parser.single());
}

/**
Expand Down Expand Up @@ -591,8 +598,8 @@ export class Args {
* ```
*/
public next<T>(cb: ArgsNextCallback<T>): T;
public next<T>(cb?: ArgsNextCallback<T>): T | string | null {
const value = cb ? this.nextMaybe<T | string | null>(cb) : this.nextMaybe();
public next<T>(cb?: ArgsNextCallback<T>): T | Arg | null {
const value = cb ? this.nextMaybe<T | Arg | null>(cb) : this.nextMaybe();
return value.unwrapOr(null);
}

Expand Down Expand Up @@ -730,7 +737,7 @@ export class Args {
* Defines the `JSON.stringify` override.
*/
public toJSON(): ArgsJson {
return { message: this.message, command: this.command, commandContext: this.commandContext };
return { message: this.messageOrInteraction, command: this.command }; // , commandContext: this.commandContext };
}

protected unavailableArgument<T>(type: string | IArgument<T>): Result.Err<UserError> {
Expand Down Expand Up @@ -784,9 +791,9 @@ export class Args {
}

export interface ArgsJson {
message: Message<boolean>;
command: MessageCommand;
commandContext: MessageCommand.RunContext;
message: Message | AnyInteraction;
command: Command;
// commandContext: C;
}

export interface ArgType {
Expand Down Expand Up @@ -835,7 +842,7 @@ export interface ArgsNextCallback<T> {
/**
* The value to be mapped.
*/
(value: string): Option<T>;
(value: Arg): Option<T>;
}

export type ResultType<T> = Result<T, UserError | ArgumentError<T>>;
Expand Down
87 changes: 87 additions & 0 deletions src/lib/parsers/ChatInputParser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import { type CommandInteraction } from 'discord.js';
import type { Arg, Parser } from './Parser';
import { Option, Result } from '@sapphire/result';
import { type Parameter } from '@sapphire/lexure';

export class ChatInputParser implements Parser {
public position: number = 0;

public constructor(public interaction: CommandInteraction) {}

public get finished(): boolean {
return this.position === this.interaction.options.data.length;
}

public reset(): void {
this.position = 0;
}

public save(): number {
return this.position;
}

public restore(state: number): void {
this.position = state;
}

public single(): Option<Arg> {
if (this.finished) return Option.none;
return Option.some(this.interaction.options.data[this.position++]);
}

public singleMap<T>(predicate: (value: Arg) => Option<T>, useAnyways?: boolean): Option<T> {
if (this.finished) return Option.none;

const result = predicate(this.interaction.options.data[this.position]);
if (result.isSome() || useAnyways) {
this.position++;
}
return result;
}

public async singleParseAsync<T, E>(predicate: (arg: Arg) => Promise<Result<T, E>>, useAnyways?: boolean): Promise<Result<T, E | null>> {
if (this.finished) return Result.err(null);

const result = await predicate(this.interaction.options.data[this.position]);
if (result.isOk() || useAnyways) {
this.position++;
}
return result;
}

// TODO: This method doesn't really make sense for slash commands. Currently tries to convert CommandInteractionOptions back to strings. Any suggestions?
public many(): Option<Parameter[]> {
const parameters: Parameter[] = [];
for (const option of this.interaction.options.data) {
const keys = ['value', 'user', 'member', 'channel', 'role', 'attachment', 'message'] as const;
let value = '';
for (const key of keys) {
const optionValue = option[key];
if (optionValue) {
// eslint-disable-next-line @typescript-eslint/no-base-to-string
value = optionValue.toString();
break;
}
}

parameters.push({ value, raw: value, separators: [], leading: '' });
}

return parameters.length === 0 ? Option.none : Option.some(parameters);
}

public flag(..._names: string[]): boolean {
// TODO: Figure out what to do for slash commands
return false;
}

public option(..._names: string[]): Option<string> {
// TODO: Figure out what to do for slash commands
return Option.none;
}

public options(..._names: string[]): Option<string[]> {
// TODO: Figure out what to do for slash commands
return Option.none;
}
}
Loading

0 comments on commit 386e9e2

Please sign in to comment.