Skip to content

Commit 99ab31e

Browse files
authoredOct 15, 2023
feat(command): make it possible for runIn to be command type specific (#673)
1 parent 5bafcba commit 99ab31e

File tree

3 files changed

+111
-24
lines changed

3 files changed

+111
-24
lines changed
 

‎src/lib/structures/Command.ts

+67-12
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { ArgumentStream, Lexer, Parser, type IUnorderedStrategy } from '@sapphire/lexure';
22
import { AliasPiece, type AliasPieceJSON } from '@sapphire/pieces';
3-
import { isNullish, type Awaitable, type NonNullObject } from '@sapphire/utilities';
3+
import { isNullish, isObject, type Awaitable, type NonNullObject, type Nullish } from '@sapphire/utilities';
44
import {
55
ChannelType,
66
ChatInputCommandInteraction,
@@ -23,6 +23,14 @@ import type { CommandStore } from './CommandStore';
2323

2424
const ChannelTypes = Object.values(ChannelType).filter((type) => typeof type === 'number') as readonly ChannelType[];
2525
const GuildChannelTypes = ChannelTypes.filter((type) => type !== ChannelType.DM && type !== ChannelType.GroupDM) as readonly ChannelType[];
26+
function runInTypeIsSpecificsObject(types: Command.Options['runIn']): types is CommandSpecificRunIn {
27+
if (!isObject(types)) {
28+
return false;
29+
}
30+
31+
const specificTypes = types as CommandSpecificRunIn;
32+
return Boolean(specificTypes.chatInputRun || specificTypes.messageRun || specificTypes.contextMenuRun);
33+
}
2634

2735
export class Command<PreParseReturn = Args, O extends Command.Options = Command.Options> extends AliasPiece<O> {
2836
/**
@@ -361,9 +369,31 @@ export class Command<PreParseReturn = Args, O extends Command.Options = Command.
361369
* @param options The command options given from the constructor.
362370
*/
363371
protected parseConstructorPreConditionsRunIn(options: Command.Options) {
364-
const types = this.resolveConstructorPreConditionsRunType(options.runIn);
365-
if (types !== null) {
366-
this.preconditions.append({ name: CommandPreConditions.RunIn, context: { types } });
372+
// Early return if there's no runIn option:
373+
if (isNullish(options.runIn)) return;
374+
375+
if (runInTypeIsSpecificsObject(options.runIn)) {
376+
const messageRunTypes = this.resolveConstructorPreConditionsRunType(options.runIn.messageRun);
377+
const chatInputRunTypes = this.resolveConstructorPreConditionsRunType(options.runIn.chatInputRun);
378+
const contextMenuRunTypes = this.resolveConstructorPreConditionsRunType(options.runIn.contextMenuRun);
379+
380+
if (messageRunTypes !== null || chatInputRunTypes !== null || contextMenuRunTypes !== null) {
381+
this.preconditions.append({
382+
name: CommandPreConditions.RunIn,
383+
context: {
384+
types: {
385+
messageRun: messageRunTypes ?? [],
386+
chatInputRun: chatInputRunTypes ?? [],
387+
contextMenuRun: contextMenuRunTypes ?? []
388+
}
389+
}
390+
});
391+
}
392+
} else {
393+
const types = this.resolveConstructorPreConditionsRunType(options.runIn);
394+
if (types !== null) {
395+
this.preconditions.append({ name: CommandPreConditions.RunIn, context: { types } });
396+
}
367397
}
368398
}
369399

@@ -421,7 +451,7 @@ export class Command<PreParseReturn = Args, O extends Command.Options = Command.
421451
* @param types The types to resolve.
422452
* @returns The resolved types, or `null` if no types were resolved.
423453
*/
424-
protected resolveConstructorPreConditionsRunType(types: Command.Options['runIn']): readonly ChannelType[] | null {
454+
protected resolveConstructorPreConditionsRunType(types: CommandRunInUnion): readonly ChannelType[] | null {
425455
if (isNullish(types)) return null;
426456
if (typeof types === 'number') return [types];
427457
if (typeof types === 'string') {
@@ -530,6 +560,28 @@ export type CommandOptionsRunType =
530560
| 'GUILD_PRIVATE_THREAD'
531561
| 'GUILD_ANY';
532562

563+
/**
564+
* The allowed values for {@link CommandOptions.runIn}.
565+
* @since 4.7.0
566+
*/
567+
export type CommandRunInUnion =
568+
| ChannelType
569+
| Command.RunInTypes
570+
| CommandOptionsRunTypeEnum
571+
| readonly (ChannelType | Command.RunInTypes | CommandOptionsRunTypeEnum)[]
572+
| Nullish;
573+
574+
/**
575+
* A more detailed structure for {@link CommandOptions.runIn} when you want to have a different `runIn` for each
576+
* command type.
577+
* @since 4.7.0
578+
*/
579+
export interface CommandSpecificRunIn {
580+
chatInputRun?: CommandRunInUnion;
581+
messageRun?: CommandRunInUnion;
582+
contextMenuRun?: CommandRunInUnion;
583+
}
584+
533585
/**
534586
* The allowed values for {@link Command.Options.runIn} as an enum.
535587
* @since 2.0.0
@@ -695,16 +747,17 @@ export interface CommandOptions extends AliasPiece.Options, FlagStrategyOptions
695747
requiredUserPermissions?: PermissionResolvable;
696748

697749
/**
698-
* The channels the command should run in. If set to `null`, no precondition entry will be added. Some optimizations are applied when given an array to reduce the amount of preconditions run (e.g. `'GUILD_TEXT'` and `'GUILD_NEWS'` becomes `'GUILD_ANY'`, and if both `'DM'` and `'GUILD_ANY'` are defined, then no precondition entry is added as it runs in all channels).
750+
* The channels the command should run in. If set to `null`, no precondition entry will be added.
751+
* Some optimizations are applied when given an array to reduce the amount of preconditions run
752+
* (e.g. `'GUILD_TEXT'` and `'GUILD_NEWS'` becomes `'GUILD_ANY'`, and if both `'DM'` and `'GUILD_ANY'` are defined,
753+
* then no precondition entry is added as it runs in all channels).
754+
*
755+
* This can be both {@link CommandRunInUnion} which will have the same precondition apply to all the types of commands,
756+
* or you can use {@link CommandSpecificRunIn} to apply different preconditions to different types of commands.
699757
* @since 2.0.0
700758
* @default null
701759
*/
702-
runIn?:
703-
| ChannelType
704-
| Command.RunInTypes
705-
| CommandOptionsRunTypeEnum
706-
| readonly (ChannelType | Command.RunInTypes | CommandOptionsRunTypeEnum)[]
707-
| null;
760+
runIn?: CommandRunInUnion | CommandSpecificRunIn;
708761

709762
/**
710763
* If {@link SapphireClient.typing} is true, this option will override it.
@@ -776,6 +829,8 @@ export namespace Command {
776829
export type JSON = CommandJSON;
777830
export type Context = AliasPiece.Context;
778831
export type RunInTypes = CommandOptionsRunType;
832+
export type RunInUnion = CommandRunInUnion;
833+
export type SpecificRunIn = CommandSpecificRunIn;
779834
export type ChatInputCommandInteraction<Cached extends import('discord.js').CacheType = import('discord.js').CacheType> =
780835
import('discord.js').ChatInputCommandInteraction<Cached>;
781836
export type ContextMenuCommandInteraction<Cached extends import('discord.js').CacheType = import('discord.js').CacheType> =

‎src/lib/structures/Precondition.ts

+11-1
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ export interface Preconditions {
141141
GuildThreadOnly: never;
142142
NSFW: never;
143143
RunIn: {
144-
types: readonly ChannelType[];
144+
types: readonly ChannelType[] | RunInPreconditionCommandSpecificData;
145145
};
146146
ClientPermissions: {
147147
permissions: PermissionsBitField;
@@ -151,6 +151,16 @@ export interface Preconditions {
151151
};
152152
}
153153

154+
/**
155+
* The specific data for the precondition types for the `RunIn` precondition, when the command
156+
* specified the types for specific command types.
157+
*/
158+
export interface RunInPreconditionCommandSpecificData {
159+
messageRun: readonly ChannelType[];
160+
chatInputRun: readonly ChannelType[];
161+
contextMenuRun: readonly ChannelType[];
162+
}
163+
154164
export type PreconditionKeys = keyof Preconditions;
155165
export type SimplePreconditionKeys = {
156166
[K in PreconditionKeys]: Preconditions[K] extends never ? K : never;

‎src/preconditions/RunIn.ts

+33-11
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,55 @@
11
import type { ChannelType, ChatInputCommandInteraction, ContextMenuCommandInteraction, Message } from 'discord.js';
22
import { Identifiers } from '../lib/errors/Identifiers';
33
import type { ChatInputCommand, ContextMenuCommand, MessageCommand } from '../lib/structures/Command';
4-
import { AllFlowsPrecondition } from '../lib/structures/Precondition';
4+
import { AllFlowsPrecondition, type Preconditions, type RunInPreconditionCommandSpecificData } from '../lib/structures/Precondition';
55

66
export interface RunInPreconditionContext extends AllFlowsPrecondition.Context {
7-
types?: readonly ChannelType[];
7+
types?: Preconditions['RunIn']['types'];
88
}
99

1010
export class CorePrecondition extends AllFlowsPrecondition {
1111
public override messageRun(message: Message<boolean>, _: MessageCommand, context: RunInPreconditionContext): AllFlowsPrecondition.Result {
12-
return context.types && context.types.includes(message.channel.type) //
13-
? this.ok()
14-
: this.makeSharedError(context);
12+
if (!context.types) return this.ok();
13+
14+
const channelType = message.channel.type;
15+
16+
if (typesIsArray(context.types)) {
17+
return context.types.includes(channelType) ? this.ok() : this.makeSharedError(context);
18+
}
19+
20+
return context.types.messageRun.includes(channelType) ? this.ok() : this.makeSharedError(context);
1521
}
1622

1723
public override async chatInputRun(
1824
interaction: ChatInputCommandInteraction,
1925
_: ChatInputCommand,
2026
context: RunInPreconditionContext
2127
): AllFlowsPrecondition.AsyncResult {
22-
return context.types && context.types.includes((await this.fetchChannelFromInteraction(interaction)).type)
23-
? this.ok()
24-
: this.makeSharedError(context);
28+
if (!context.types) return this.ok();
29+
30+
const channelType = (await this.fetchChannelFromInteraction(interaction)).type;
31+
32+
if (typesIsArray(context.types)) {
33+
return context.types.includes(channelType) ? this.ok() : this.makeSharedError(context);
34+
}
35+
36+
return context.types.chatInputRun.includes(channelType) ? this.ok() : this.makeSharedError(context);
2537
}
2638

2739
public override async contextMenuRun(
2840
interaction: ContextMenuCommandInteraction,
2941
_: ContextMenuCommand,
3042
context: RunInPreconditionContext
3143
): AllFlowsPrecondition.AsyncResult {
32-
return context.types && context.types.includes((await this.fetchChannelFromInteraction(interaction)).type)
33-
? this.ok()
34-
: this.makeSharedError(context);
44+
if (!context.types) return this.ok();
45+
46+
const channelType = (await this.fetchChannelFromInteraction(interaction)).type;
47+
48+
if (typesIsArray(context.types)) {
49+
return context.types.includes(channelType) ? this.ok() : this.makeSharedError(context);
50+
}
51+
52+
return context.types.contextMenuRun.includes(channelType) ? this.ok() : this.makeSharedError(context);
3553
}
3654

3755
private makeSharedError(context: RunInPreconditionContext): AllFlowsPrecondition.Result {
@@ -42,3 +60,7 @@ export class CorePrecondition extends AllFlowsPrecondition {
4260
});
4361
}
4462
}
63+
64+
function typesIsArray(types: readonly ChannelType[] | RunInPreconditionCommandSpecificData): types is readonly ChannelType[] {
65+
return Array.isArray(types);
66+
}

0 commit comments

Comments
 (0)
Please sign in to comment.