types: allow message cached props to be narrowed (#6838)

Co-authored-by: Almeida <almeidx@pm.me>
Co-authored-by: Antonio Román <kyradiscord@gmail.com>
Co-authored-by: Vlad Frangu <kingdgrizzle@gmail.com>
This commit is contained in:
Suneet Tipirneni
2021-10-24 15:31:42 -04:00
committed by GitHub
parent 579569ae18
commit c3948f8253
3 changed files with 89 additions and 24 deletions

View File

@@ -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}

72
typings/index.d.ts vendored
View File

@@ -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
@@ -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;
@@ -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
@@ -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;
@@ -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;
@@ -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 {
@@ -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;

View File

@@ -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,
@@ -73,6 +77,7 @@ import {
Typing,
User,
VoiceChannel,
CachedMessage,
} from '.';
import { ApplicationCommandOptionTypes } from './enums';
@@ -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
@@ -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);
@@ -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'));