Skip to content

Commit

Permalink
types: allow message cached props to be narrowed (#6838)
Browse files Browse the repository at this point in the history
Co-authored-by: Almeida <almeidx@pm.me>
Co-authored-by: Antonio Román <kyradiscord@gmail.com>
Co-authored-by: Vlad Frangu <kingdgrizzle@gmail.com>
  • Loading branch information
4 people committed Oct 24, 2021
1 parent 579569a commit c3948f8
Show file tree
Hide file tree
Showing 3 changed files with 89 additions and 24 deletions.
8 changes: 8 additions & 0 deletions src/structures/Message.js
Original file line number Diff line number Diff line change
Expand Up @@ -889,6 +889,14 @@ class Message extends Base {
return equal;
}

/**
* Whether this message is from a guild.
* @returns {boolean}
*/
inGuild() {
return Boolean(this.guildId);
}

/**
* When concatenated with a string, this automatically concatenates the message's content instead of the object.
* @returns {string}
Expand Down
72 changes: 51 additions & 21 deletions typings/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -270,7 +270,7 @@ export class BaseClient extends EventEmitter {

export type GuildCacheMessage<Cached extends GuildCacheState> = CacheTypeReducer<
Cached,
Message,
GuildMessage<Cached> & Message,
APIMessage,
Message | APIMessage,
Message | APIMessage
Expand Down Expand Up @@ -315,10 +315,9 @@ export type CacheHelper<
Cached extends GuildCacheState,
> = T extends InteractionResponsesResolvable ? InteractionResponses<Cached> & T : GuildInteraction<Cached> & T;

export type GuildCached<T extends Interaction> = CacheHelper<T, 'cached'>;
export type GuildRaw<T extends Interaction> = CacheHelper<T, 'raw'>;
export type GuildPresent<T extends Interaction> = CacheHelper<T, 'present'>;

export type CachedInteraction<T extends Interaction = Interaction> = CacheHelper<T, 'cached'>;
export type RawInteraction<T extends Interaction = Interaction> = CacheHelper<T, 'raw'>;
export type PresentInteraction<T extends Interaction = Interaction> = CacheHelper<T, 'present'>;
export abstract class BaseGuild extends Base {
protected constructor(client: Client, data: RawBaseGuildData);
public readonly createdAt: Date;
Expand Down Expand Up @@ -1242,8 +1241,8 @@ export type TaggedUnion<T, K extends keyof T, V extends T[K]> = T extends Record
: never;

// This creates a map of MessageComponentTypes to their respective `InteractionCollectorOptionsResolvable` variant.
export type CollectorOptionsTypeResolver<U extends InteractionCollectorOptionsResolvable> = {
readonly [T in U['componentType']]: TaggedUnion<InteractionCollectorOptionsResolvable, 'componentType', T>;
export type CollectorOptionsTypeResolver<U extends InteractionCollectorOptionsResolvable<Cached>, Cached = boolean> = {
readonly [T in U['componentType']]: TaggedUnion<U, 'componentType', T>;
};

// This basically says "Given a `InteractionCollectorOptionsResolvable` variant", I'll give the corresponding
Expand All @@ -1254,18 +1253,23 @@ export type ConditionalInteractionCollectorType<T extends InteractionCollectorOp
: InteractionCollector<MessageComponentInteraction>;

// This maps each componentType key to each variant.
export type MappedInteractionCollectorOptions = CollectorOptionsTypeResolver<InteractionCollectorOptionsResolvable>;
export type MappedInteractionCollectorOptions<Cached = boolean> = CollectorOptionsTypeResolver<
InteractionCollectorOptionsResolvable<Cached>,
Cached
>;

// Converts mapped types to complimentary collector types.
export type InteractionCollectorReturnType<T extends MessageComponentType | MessageComponentTypes | undefined> =
T extends MessageComponentType | MessageComponentTypes
? ConditionalInteractionCollectorType<MappedInteractionCollectorOptions[T]>
: InteractionCollector<MessageComponentInteraction>;
export type InteractionCollectorReturnType<
T extends MessageComponentType | MessageComponentTypes | undefined,
Cached extends boolean = false,
> = T extends MessageComponentType | MessageComponentTypes
? ConditionalInteractionCollectorType<MappedInteractionCollectorOptions<Cached>[T]>
: InteractionCollector<MessageComponentInteraction>;

export type InteractionExtractor<T extends MessageComponentType | MessageComponentTypes | undefined> = T extends
| MessageComponentType
| MessageComponentTypes
? MappedInteractionCollectorOptions[T] extends InteractionCollectorOptions<infer Item>
? MappedInteractionCollectorOptions<false>[T] extends InteractionCollectorOptions<infer Item>
? Item
: never
: MessageComponentInteraction;
Expand All @@ -1281,6 +1285,23 @@ export type AwaitMessageCollectorOptionsParams<T extends MessageComponentType |
keyof AwaitMessageComponentOptions<any>
>;

export type GuildTextBasedChannel = Exclude<TextBasedChannels, PartialDMChannel | DMChannel>;

export type CachedMessage = GuildMessage<'cached'> & Message;
export interface GuildMessage<Cached extends GuildCacheState = GuildCacheState> {
awaitMessageComponent<
T extends MessageComponentType | MessageComponentTypes | undefined = MessageComponentTypes.ACTION_ROW,
>(
options?: AwaitMessageCollectorOptionsParams<T>,
): Promise<InteractionResponses<Cached> & InteractionExtractor<T>>;

createMessageComponentCollector<T extends MessageComponentType | MessageComponentTypes | undefined = undefined>(
options?: MessageCollectorOptionsParams<T>,
): InteractionCollectorReturnType<T, true>;

readonly channel: CacheTypeReducer<Cached, GuildTextBasedChannel>;
}

export class Message extends Base {
private constructor(client: Client, data: RawMessageData);
private _patch(data: RawPartialMessageData | RawMessageData): void;
Expand Down Expand Up @@ -1350,6 +1371,7 @@ export class Message extends Base {
public toJSON(): unknown;
public toString(): string;
public unpin(): Promise<Message>;
public inGuild(): this is GuildMessage<'cached'> & this;
}

export class MessageActionRow extends BaseMessageComponent {
Expand Down Expand Up @@ -4247,23 +4269,31 @@ export interface InteractionCollectorOptions<T extends Interaction> extends Coll
message?: Message | APIMessage;
}

export interface ButtonInteractionCollectorOptions extends MessageComponentCollectorOptions<ButtonInteraction> {
export interface ButtonInteractionCollectorOptions<Cached = boolean>
extends MessageComponentCollectorOptions<
Cached extends true ? CachedInteraction<ButtonInteraction> : ButtonInteraction
> {
componentType: 'BUTTON' | MessageComponentTypes.BUTTON;
}

export interface SelectMenuInteractionCollectorOptions extends MessageComponentCollectorOptions<SelectMenuInteraction> {
export interface SelectMenuInteractionCollectorOptions<Cached = boolean>
extends MessageComponentCollectorOptions<
Cached extends true ? CachedInteraction<SelectMenuInteraction> : SelectMenuInteraction
> {
componentType: 'SELECT_MENU' | MessageComponentTypes.SELECT_MENU;
}

export interface MessageInteractionCollectorOptions
extends MessageComponentCollectorOptions<MessageComponentInteraction> {
export interface MessageInteractionCollectorOptions<Cached = boolean>
extends MessageComponentCollectorOptions<
Cached extends true ? CachedInteraction<MessageComponentInteraction> : MessageComponentInteraction
> {
componentType: 'ACTION_ROW' | MessageComponentTypes.ACTION_ROW;
}

export type InteractionCollectorOptionsResolvable =
| MessageInteractionCollectorOptions
| SelectMenuInteractionCollectorOptions
| ButtonInteractionCollectorOptions;
export type InteractionCollectorOptionsResolvable<Cached = boolean> =
| MessageInteractionCollectorOptions<Cached>
| SelectMenuInteractionCollectorOptions<Cached>
| ButtonInteractionCollectorOptions<Cached>;

export interface InteractionDeferReplyOptions {
ephemeral?: boolean;
Expand Down
33 changes: 30 additions & 3 deletions typings/tests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,15 +33,19 @@ import {
DMChannel,
Guild,
GuildApplicationCommandManager,
GuildCached,
CachedInteraction,
GuildChannelManager,
GuildEmoji,
GuildEmojiManager,
GuildMember,
GuildMessage,
GuildResolvable,
GuildTextBasedChannel,
GuildTextChannelResolvable,
Intents,
Interaction,
InteractionCollector,
InteractionResponses,
LimitedCollection,
Message,
MessageActionRow,
Expand Down Expand Up @@ -73,6 +77,7 @@ import {
Typing,
User,
VoiceChannel,
CachedMessage,
} from '.';
import { ApplicationCommandOptionTypes } from './enums';

Expand Down Expand Up @@ -492,6 +497,22 @@ client.on('messageCreate', async message => {
assertIsMessage(channel.send({ embeds: [embed] }));
assertIsMessage(channel.send({ embeds: [embed], files: [attachment] }));

if (message.inGuild()) {
assertType<CachedMessage>(message);
const component = await message.awaitMessageComponent({ componentType: 'BUTTON' });
assertType<InteractionResponses<'cached'>>(component);
assertType<Promise<CachedMessage>>(component.reply({ fetchReply: true }));

const buttonCollector = message.createMessageComponentCollector({ componentType: 'BUTTON' });
assertType<InteractionCollector<CachedInteraction<ButtonInteraction>>>(buttonCollector);
assertType<GuildTextBasedChannel>(message.channel);
}

assertType<TextBasedChannels>(message.channel);

// @ts-expect-error
assertType<GuildTextBasedChannel>(message.channel);

// @ts-expect-error
channel.send();
// @ts-expect-error
Expand Down Expand Up @@ -876,8 +897,8 @@ declare const booleanValue: boolean;
if (interaction.inGuild()) assertType<Snowflake>(interaction.guildId);

client.on('interactionCreate', async interaction => {
const consumeCachedCommand = (_i: GuildCached<CommandInteraction>) => {};
const consumeCachedInteraction = (_i: GuildCached<Interaction>) => {};
const consumeCachedCommand = (_i: CachedInteraction<CommandInteraction>) => {};
const consumeCachedInteraction = (_i: CachedInteraction<Interaction>) => {};

if (interaction.inCachedGuild()) {
assertType<GuildMember>(interaction.member);
Expand Down Expand Up @@ -969,6 +990,12 @@ client.on('interactionCreate', async interaction => {
assertType<APIInteractionDataResolvedGuildMember | null>(interaction.options.getMember('test'));
assertType<APIInteractionDataResolvedGuildMember>(interaction.options.getMember('test', true));
} else if (interaction.inCachedGuild()) {
const msg = await interaction.reply({ fetchReply: true });
const btn = await msg.awaitMessageComponent({ componentType: 'BUTTON' });

assertType<Message>(msg);
assertType<CachedInteraction<ButtonInteraction>>(btn);

consumeCachedCommand(interaction);
assertType<GuildMember>(interaction.options.getMember('test', true));
assertType<GuildMember | null>(interaction.options.getMember('test'));
Expand Down

0 comments on commit c3948f8

Please sign in to comment.