typings: Allow message component interaction collectors to infer collected interaction types (#6476)

This commit is contained in:
Suneet Tipirneni
2021-08-29 11:25:58 -04:00
committed by GitHub
parent 774e9609d2
commit 03d5549461
2 changed files with 100 additions and 14 deletions

82
typings/index.d.ts vendored
View File

@@ -1103,6 +1103,46 @@ export class LimitedCollection<K, V> extends Collection<K, V> {
public static filterByLifetime<K, V>(options?: LifetimeFilterOptions<K, V>): SweepFilter<K, V>; public static filterByLifetime<K, V>(options?: LifetimeFilterOptions<K, V>): SweepFilter<K, V>;
} }
// This is a general conditional type utility that allows for specific union members to be extracted given
// a tagged union.
type TaggedUnion<T, K extends keyof T, V extends T[K]> = T extends Record<K, V>
? T
: T extends Record<K, infer U>
? V extends U
? T
: never
: never;
// This creates a map of MessageComponentTypes to their respective `InteractionCollectorOptionsResolvable` variant.
type CollectorOptionsTypeResolver<U extends InteractionCollectorOptionsResolvable> = {
readonly [T in U['componentType']]: TaggedUnion<InteractionCollectorOptionsResolvable, 'componentType', T>;
};
// This basically says "Given a `InteractionCollectorOptionsResolvable` variant", I'll give the corresponding
// `InteractionCollector<T>` variant back.
type ConditionalInteractionCollectorType<T extends InteractionCollectorOptionsResolvable | undefined> =
T extends InteractionCollectorOptions<infer Item>
? InteractionCollector<Item>
: InteractionCollector<MessageComponentInteraction>;
// This maps each componentType key to each variant.
type MappedInteractionCollectorOptions = CollectorOptionsTypeResolver<InteractionCollectorOptionsResolvable>;
// Converts mapped types to complimentary collector types.
type InteractionCollectorReturnType<T extends MessageComponentType | MessageComponentTypes | undefined> = T extends
| MessageComponentType
| MessageComponentTypes
? ConditionalInteractionCollectorType<MappedInteractionCollectorOptions[T]>
: InteractionCollector<MessageComponentInteraction>;
type MessageCollectorOptionsParams<T> =
| ({ componentType?: T } & InteractionCollectorOptionsResolvable)
| InteractionCollectorOptions<MessageComponentInteraction>;
type AwaitMessageCollectorOptionsParams<T> =
| ({ componentType?: T } & Pick<InteractionCollectorOptionsResolvable, keyof AwaitMessageComponentOptions<any>>)
| AwaitMessageComponentOptions<MessageComponentInteraction>;
export class Message extends Base { export class Message extends Base {
public constructor(client: Client, data: RawMessageData); public constructor(client: Client, data: RawMessageData);
private _patch(data: RawPartialMessageData, partial: true): void; private _patch(data: RawPartialMessageData, partial: true): void;
@@ -1150,14 +1190,14 @@ export class Message extends Base {
public webhookId: Snowflake | null; public webhookId: Snowflake | null;
public flags: Readonly<MessageFlags>; public flags: Readonly<MessageFlags>;
public reference: MessageReference | null; public reference: MessageReference | null;
public awaitMessageComponent<T extends MessageComponentInteraction = MessageComponentInteraction>( public awaitMessageComponent<T extends MessageComponentType | MessageComponentTypes | undefined = undefined>(
options?: AwaitMessageComponentOptions<T>, options?: AwaitMessageCollectorOptionsParams<T>,
): Promise<T>; ): Promise<InteractionCollectorReturnType<T>>;
public awaitReactions(options?: AwaitReactionsOptions): Promise<Collection<Snowflake | string, MessageReaction>>; public awaitReactions(options?: AwaitReactionsOptions): Promise<Collection<Snowflake | string, MessageReaction>>;
public createReactionCollector(options?: ReactionCollectorOptions): ReactionCollector; public createReactionCollector(options?: ReactionCollectorOptions): ReactionCollector;
public createMessageComponentCollector<T extends MessageComponentInteraction = MessageComponentInteraction>( public createMessageComponentCollector<
options?: InteractionCollectorOptions<T>, T extends MessageComponentType | MessageComponentTypes | undefined = undefined,
): InteractionCollector<T>; >(options?: MessageCollectorOptionsParams<T>): InteractionCollectorReturnType<T>;
public delete(): Promise<Message>; public delete(): Promise<Message>;
public edit(content: string | MessageEditOptions | MessagePayload): Promise<Message>; public edit(content: string | MessageEditOptions | MessagePayload): Promise<Message>;
public equals(message: Message, rawData: unknown): boolean; public equals(message: Message, rawData: unknown): boolean;
@@ -2711,18 +2751,17 @@ export interface TextBasedChannelFields extends PartialTextBasedChannelFields {
readonly lastMessage: Message | null; readonly lastMessage: Message | null;
lastPinTimestamp: number | null; lastPinTimestamp: number | null;
readonly lastPinAt: Date | null; readonly lastPinAt: Date | null;
awaitMessageComponent<T extends MessageComponentInteraction = MessageComponentInteraction>( awaitMessageComponent<T extends MessageComponentType | MessageComponentTypes | undefined = undefined>(
options?: AwaitMessageComponentOptions<T>, options?: AwaitMessageCollectorOptionsParams<T>,
): Promise<T>; ): Promise<InteractionCollectorReturnType<T>>;
awaitMessages(options?: AwaitMessagesOptions): Promise<Collection<Snowflake, Message>>; awaitMessages(options?: AwaitMessagesOptions): Promise<Collection<Snowflake, Message>>;
bulkDelete( bulkDelete(
messages: Collection<Snowflake, Message> | readonly MessageResolvable[] | number, messages: Collection<Snowflake, Message> | readonly MessageResolvable[] | number,
filterOld?: boolean, filterOld?: boolean,
): Promise<Collection<Snowflake, Message>>; ): Promise<Collection<Snowflake, Message>>;
createMessageComponentCollector<T extends MessageComponentInteraction = MessageComponentInteraction>( createMessageComponentCollector<T extends MessageComponentType | MessageComponentTypes | undefined = undefined>(
options?: InteractionCollectorOptions<T>, options?: MessageCollectorOptionsParams<T>,
): InteractionCollector<T>; ): InteractionCollectorReturnType<T>;
createMessageCollector(options?: MessageCollectorOptions): MessageCollector;
sendTyping(): Promise<void>; sendTyping(): Promise<void>;
} }
@@ -3959,6 +3998,23 @@ export interface InteractionCollectorOptions<T extends Interaction> extends Coll
message?: Message | APIMessage; message?: Message | APIMessage;
} }
export interface ButtonInteractionCollectorOptions extends InteractionCollectorOptions<ButtonInteraction> {
componentType: 'BUTTON' | MessageComponentTypes.BUTTON;
}
export interface SelectMenuInteractionCollectorOptions extends InteractionCollectorOptions<SelectMenuInteraction> {
componentType: 'SELECT_MENU' | MessageComponentTypes.SELECT_MENU;
}
export interface MessageInteractionCollectorOptions extends InteractionCollectorOptions<MessageComponentInteraction> {
componentType: 'ACTION_ROW' | MessageComponentTypes.ACTION_ROW;
}
export type InteractionCollectorOptionsResolvable =
| MessageInteractionCollectorOptions
| SelectMenuInteractionCollectorOptions
| ButtonInteractionCollectorOptions;
export interface InteractionDeferReplyOptions { export interface InteractionDeferReplyOptions {
ephemeral?: boolean; ephemeral?: boolean;
fetchReply?: boolean; fetchReply?: boolean;

View File

@@ -8,6 +8,7 @@ import {
ApplicationCommandResolvable, ApplicationCommandResolvable,
ApplicationCommandSubCommandData, ApplicationCommandSubCommandData,
ApplicationCommandSubGroupData, ApplicationCommandSubGroupData,
ButtonInteraction,
CacheFactory, CacheFactory,
Caches, Caches,
CategoryChannel, CategoryChannel,
@@ -31,12 +32,14 @@ import {
GuildResolvable, GuildResolvable,
Intents, Intents,
Interaction, Interaction,
InteractionCollector,
LimitedCollection, LimitedCollection,
Message, Message,
MessageActionRow, MessageActionRow,
MessageAttachment, MessageAttachment,
MessageButton, MessageButton,
MessageCollector, MessageCollector,
MessageComponentInteraction,
MessageEmbed, MessageEmbed,
MessageManager, MessageManager,
MessageReaction, MessageReaction,
@@ -49,6 +52,7 @@ import {
ReactionCollector, ReactionCollector,
Role, Role,
RoleManager, RoleManager,
SelectMenuInteraction,
Serialized, Serialized,
ShardClientUtil, ShardClientUtil,
ShardingManager, ShardingManager,
@@ -469,7 +473,8 @@ client.on('messageReactionRemoveAll', async message => {
// This is to check that stuff is the right type // This is to check that stuff is the right type
declare const assertIsMessage: (m: Promise<Message>) => void; declare const assertIsMessage: (m: Promise<Message>) => void;
client.on('messageCreate', ({ channel }) => { client.on('messageCreate', message => {
const { channel } = message;
assertIsMessage(channel.send('string')); assertIsMessage(channel.send('string'));
assertIsMessage(channel.send({})); assertIsMessage(channel.send({}));
assertIsMessage(channel.send({ embeds: [] })); assertIsMessage(channel.send({ embeds: [] }));
@@ -484,6 +489,31 @@ client.on('messageCreate', ({ channel }) => {
channel.send(); channel.send();
// @ts-expect-error // @ts-expect-error
channel.send({ another: 'property' }); channel.send({ another: 'property' });
// Check collector creations.
// Verify that buttons interactions are inferred.
const buttonCollector = message.createMessageComponentCollector({ componentType: 'BUTTON' });
assertType<Promise<InteractionCollector<ButtonInteraction>>>(
message.awaitMessageComponent({ componentType: 'BUTTON' }),
);
assertType<InteractionCollector<ButtonInteraction>>(buttonCollector);
// Verify that select menus interaction are inferred.
const selectMenuCollector = message.createMessageComponentCollector({ componentType: 'SELECT_MENU' });
assertType<Promise<InteractionCollector<SelectMenuInteraction>>>(
message.awaitMessageComponent({ componentType: 'SELECT_MENU' }),
);
assertType<InteractionCollector<SelectMenuInteraction>>(selectMenuCollector);
// Verify that message component interactions are default collected types.
const defaultCollector = message.createMessageComponentCollector();
assertType<Promise<InteractionCollector<MessageComponentInteraction>>>(message.awaitMessageComponent());
assertType<InteractionCollector<MessageComponentInteraction>>(defaultCollector);
// Verify that additional options don't affect default collector types.
const semiDefaultCollector = message.createMessageComponentCollector({ interactionType: 'APPLICATION_COMMAND' });
assertType<InteractionCollector<MessageComponentInteraction>>(semiDefaultCollector);
}); });
client.on('interaction', async interaction => { client.on('interaction', async interaction => {