diff --git a/src/structures/Interaction.js b/src/structures/Interaction.js index 82fe70881..2afdacee0 100644 --- a/src/structures/Interaction.js +++ b/src/structures/Interaction.js @@ -120,6 +120,22 @@ class Interaction extends Base { return Boolean(this.guildId && this.member); } + /** + * Indicates whether or not this interaction is both cached and received from a guild. + * @returns {boolean} + */ + inCachedGuild() { + return Boolean(this.guild && this.member); + } + + /** + * Indicates whether or not this interaction is received from an uncached guild. + * @returns {boolean} + */ + inRawGuild() { + return Boolean(this.guildId && !this.guild && this.member); + } + /** * Indicates whether this interaction is a {@link CommandInteraction}. * @returns {boolean} diff --git a/tslint.json b/tslint.json index a2facf751..d66f7bdf3 100644 --- a/tslint.json +++ b/tslint.json @@ -22,7 +22,7 @@ "member-access": true, "no-unnecessary-class": false, "array-type": [true, "array"], - + "one-line": false, "no-any-union": false, "void-return": false } diff --git a/typings/index.d.ts b/typings/index.d.ts index 6f6f5a1af..c53fe2b50 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -267,6 +267,22 @@ export class BaseClient extends EventEmitter { public toJSON(...props: Record[]): unknown; } +export type GuildCacheMessage = CacheTypeReducer< + Cached, + Message, + APIMessage, + Message | APIMessage, + Message | APIMessage +>; + +export interface BaseGuildCommandInteraction + extends GuildInteraction { + deferReply(options: InteractionDeferReplyOptions & { fetchReply: true }): Promise>; + editReply(options: string | MessagePayload | WebhookEditMessageOptions): Promise>; + fetchReply(): Promise>; + reply(options: InteractionReplyOptions & { fetchReply: true }): Promise>; +} + export abstract class BaseCommandInteraction extends Interaction { public readonly command: ApplicationCommand | ApplicationCommand<{ guild: GuildResolvable }> | null; public readonly channel: TextBasedChannels | null; @@ -277,6 +293,9 @@ export abstract class BaseCommandInteraction extends Interaction { public ephemeral: boolean | null; public replied: boolean; public webhook: InteractionWebhook; + public inGuild(): this is BaseGuildCommandInteraction<'present'> & this; + public inCachedGuild(): this is BaseGuildCommandInteraction<'cached'> & this; + public inRawGuild(): this is BaseGuildCommandInteraction<'raw'> & this; public deferReply(options: InteractionDeferReplyOptions & { fetchReply: true }): Promise; public deferReply(options?: InteractionDeferReplyOptions): Promise; public deleteReply(): Promise; @@ -579,7 +598,10 @@ export abstract class Collector extends EventEmi public once(event: 'end', listener: (collected: Collection, reason: string) => Awaitable): this; } -export class CommandInteraction extends BaseCommandInteraction { +export type GuildCommandInteraction = + BaseGuildCommandInteraction & CommandInteraction; + +export class CommandInteraction extends BaseCommandInteraction implements GuildCachedInteraction { public options: CommandInteractionOptionResolver; } @@ -639,7 +661,19 @@ export class CommandInteractionOptionResolver { public getMessage(name: string, required?: boolean): NonNullable | null; } -export class ContextMenuInteraction extends BaseCommandInteraction { +export type GuildContextMenuInteraction = + BaseGuildCommandInteraction & ContextMenuInteraction; + +export interface GuildCachedInteraction { + inGuild(): this is BaseGuildCommandInteraction<'present'> & T; + inCachedGuild(): this is BaseGuildCommandInteraction<'cached'> & T; + inRawGuild(): this is BaseGuildCommandInteraction<'raw'> & T; +} + +export class ContextMenuInteraction + extends BaseCommandInteraction + implements GuildCachedInteraction +{ public options: CommandInteractionOptionResolver; public targetId: Snowflake; public targetType: Exclude; @@ -1042,6 +1076,29 @@ export class Intents extends BitField { public static resolve(bit?: BitFieldResolvable): number; } +export type GuildCacheState = 'cached' | 'raw' | 'present'; + +type CacheTypeReducer< + State extends GuildCacheState, + CachedType, + RawType = CachedType, + PresentType = CachedType | RawType, + Fallback = PresentType | null, +> = State extends 'cached' + ? CachedType + : State extends 'raw' + ? RawType + : State extends 'present' + ? PresentType + : Fallback; + +export interface GuildInteraction extends Interaction { + guildId: Snowflake; + member: CacheTypeReducer; + readonly guild: CacheTypeReducer; + channel: CacheTypeReducer | null>; +} + export class Interaction extends Base { protected constructor(client: Client, data: RawInteractionData); public applicationId: Snowflake; @@ -1058,11 +1115,9 @@ export class Interaction extends Base { public user: User; public version: number; public memberPermissions: Readonly | null; - public inGuild(): this is this & { - guildId: Snowflake; - member: GuildMember | APIInteractionGuildMember; - readonly channel: Exclude | null; - }; + public inGuild(): this is GuildInteraction<'present'> & this; + public inCachedGuild(): this is GuildInteraction<'cached'> & this; + public inRawGuild(): this is GuildInteraction<'raw'> & this; public isButton(): this is ButtonInteraction; public isCommand(): this is CommandInteraction; public isContextMenu(): this is ContextMenuInteraction; diff --git a/typings/tests.ts b/typings/tests.ts index 3d7a5d435..1fa6a99ce 100644 --- a/typings/tests.ts +++ b/typings/tests.ts @@ -1,4 +1,4 @@ -import { APIInteractionGuildMember } from 'discord-api-types'; +import { APIGuildMember, APIInteractionGuildMember, APIMessage } from 'discord-api-types/v9'; import { ApplicationCommand, ApplicationCommandChannelOption, @@ -25,6 +25,7 @@ import { CommandOptionChoiceResolvableType, CommandOptionNonChoiceResolvableType, Constants, + ContextMenuInteraction, DMChannel, Guild, GuildApplicationCommandManager, @@ -856,7 +857,82 @@ declare const booleanValue: boolean; if (interaction.inGuild()) assertType(interaction.guildId); client.on('interactionCreate', async interaction => { + if (interaction.inCachedGuild()) { + assertType(interaction.member); + } else if (interaction.inRawGuild()) { + assertType(interaction.member); + } else { + assertType(interaction.member); + } + + if (interaction.isContextMenu()) { + assertType(interaction); + if (interaction.inCachedGuild()) { + assertType(interaction); + assertType(interaction.guild); + } else if (interaction.inRawGuild()) { + assertType(interaction); + assertType(interaction.guild); + } else if (interaction.inGuild()) { + assertType(interaction); + assertType(interaction.guild); + } + } + + if (interaction.isButton()) { + assertType(interaction); + if (interaction.inCachedGuild()) { + assertType(interaction); + assertType(interaction.guild); + } else if (interaction.inRawGuild()) { + assertType(interaction); + assertType(interaction.guild); + } else if (interaction.inGuild()) { + assertType(interaction); + assertType(interaction.guild); + } + } + + if (interaction.isMessageComponent()) { + assertType(interaction); + if (interaction.inCachedGuild()) { + assertType(interaction); + assertType(interaction.guild); + } else if (interaction.inRawGuild()) { + assertType(interaction); + assertType(interaction.guild); + } else if (interaction.inGuild()) { + assertType(interaction); + assertType(interaction.guild); + } + } + + if (interaction.isSelectMenu()) { + assertType(interaction); + if (interaction.inCachedGuild()) { + assertType(interaction); + assertType(interaction.guild); + } else if (interaction.inRawGuild()) { + assertType(interaction); + assertType(interaction.guild); + } else if (interaction.inGuild()) { + assertType(interaction); + assertType(interaction.guild); + } + } + if (interaction.isCommand()) { + if (interaction.inRawGuild()) { + assertType(interaction); + assertType>(interaction.reply({ fetchReply: true })); + } else if (interaction.inCachedGuild()) { + assertType(interaction); + assertType>(interaction.reply({ fetchReply: true })); + } else { + assertType(interaction); + assertType>(interaction.reply({ fetchReply: true })); + } + assertType(interaction); assertType(interaction.options); assertType(interaction.options.data);