From 6bb1474d2001b76773954c959b2c2687e1df0136 Mon Sep 17 00:00:00 2001 From: Jiralite <33201955+Jiralite@users.noreply.github.com> Date: Mon, 22 Aug 2022 09:31:02 +0100 Subject: [PATCH] types: Inference of guild in `MessageManager` (#8538) * types: better inference of message manager * types: alter helper methods Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- packages/discord.js/typings/index.d.ts | 102 ++++++++++++-------- packages/discord.js/typings/index.test-d.ts | 38 +++++++- 2 files changed, 96 insertions(+), 44 deletions(-) diff --git a/packages/discord.js/typings/index.d.ts b/packages/discord.js/typings/index.d.ts index b62f38870..351a33220 100644 --- a/packages/discord.js/typings/index.d.ts +++ b/packages/discord.js/typings/index.d.ts @@ -123,6 +123,7 @@ import { FormattingPatterns, APIEmbedProvider, AuditLogOptionsType, + TextChannelType, } from 'discord-api-types/v10'; import { ChildProcess } from 'node:child_process'; import { EventEmitter } from 'node:events'; @@ -521,7 +522,7 @@ export class BaseGuildEmoji extends Emoji { public requiresColons: boolean | null; } -export class BaseGuildTextChannel extends TextBasedChannelMixin(GuildChannel) { +export class BaseGuildTextChannel extends TextBasedChannelMixin(GuildChannel, true) { protected constructor(guild: Guild, data?: RawGuildChannelData, client?: Client, immediatePatch?: boolean); public defaultAutoArchiveDuration?: ThreadAutoArchiveDuration; public rateLimitPerUser: number | null; @@ -1049,7 +1050,7 @@ export class DataResolver extends null { public static resolveGuildTemplateCode(data: GuildTemplateResolvable): string; } -export class DMChannel extends TextBasedChannelMixin(BaseChannel, [ +export class DMChannel extends TextBasedChannelMixin(BaseChannel, false, [ 'bulkDelete', 'fetchWebhooks', 'createWebhook', @@ -1675,8 +1676,8 @@ export interface MappedInteractionTypes { [ComponentType.SelectMenu]: SelectMenuInteraction>; } -export class Message extends Base { - private readonly _cacheType: Cached; +export class Message extends Base { + private readonly _cacheType: InGuild; private constructor(client: Client, data: RawMessageData); private _patch(data: RawPartialMessageData | RawMessageData): void; @@ -1684,7 +1685,7 @@ export class Message extends Base { public applicationId: Snowflake | null; public attachments: Collection; public author: User; - public get channel(): If; + public get channel(): If; public channelId: Snowflake; public get cleanContent(): string; public components: ActionRow[]; @@ -1698,8 +1699,8 @@ export class Message extends Base { public editedTimestamp: number | null; public embeds: Embed[]; public groupActivityApplication: ClientApplication | null; - public guildId: If; - public get guild(): If; + public guildId: If; + public get guild(): If; public get hasThread(): boolean; public id: Snowflake; public interaction: MessageInteraction | null; @@ -1720,30 +1721,30 @@ export class Message extends Base { public flags: Readonly; public reference: MessageReference | null; public awaitMessageComponent( - options?: AwaitMessageCollectorOptionsParams, - ): Promise[T]>; + options?: AwaitMessageCollectorOptionsParams, + ): Promise[T]>; public awaitReactions(options?: AwaitReactionsOptions): Promise>; public createReactionCollector(options?: ReactionCollectorOptions): ReactionCollector; public createMessageComponentCollector( - options?: MessageCollectorOptionsParams, - ): InteractionCollector[T]>; - public delete(): Promise; - public edit(content: string | MessageEditOptions | MessagePayload): Promise; + options?: MessageCollectorOptionsParams, + ): InteractionCollector[T]>; + public delete(): Promise>; + public edit(content: string | MessageEditOptions | MessagePayload): Promise>; public equals(message: Message, rawData: unknown): boolean; - public fetchReference(): Promise; + public fetchReference(): Promise>; public fetchWebhook(): Promise; - public crosspost(): Promise; - public fetch(force?: boolean): Promise; - public pin(reason?: string): Promise; + public crosspost(): Promise>; + public fetch(force?: boolean): Promise>; + public pin(reason?: string): Promise>; public react(emoji: EmojiIdentifierResolvable): Promise; - public removeAttachments(): Promise; - public reply(options: string | MessagePayload | ReplyMessageOptions): Promise; + public removeAttachments(): Promise>; + public reply(options: string | MessagePayload | ReplyMessageOptions): Promise>; public resolveComponent(customId: string): MessageActionRowComponent | null; public startThread(options: StartThreadOptions): Promise; - public suppressEmbeds(suppress?: boolean): Promise; + public suppressEmbeds(suppress?: boolean): Promise>; public toJSON(): unknown; public toString(): string; - public unpin(reason?: string): Promise; + public unpin(reason?: string): Promise>; public inGuild(): this is Message & this; } @@ -2506,7 +2507,11 @@ export interface PrivateThreadChannel extends ThreadChannel { type: ChannelType.GuildPrivateThread; } -export class ThreadChannel extends TextBasedChannelMixin(BaseChannel, ['fetchWebhooks', 'createWebhook', 'setNSFW']) { +export class ThreadChannel extends TextBasedChannelMixin(BaseChannel, true, [ + 'fetchWebhooks', + 'createWebhook', + 'setNSFW', +]) { private constructor(guild: Guild, data?: RawThreadChannelData, client?: Client, fromInteraction?: boolean); public archived: boolean | null; public get archivedAt(): Date | null; @@ -2752,7 +2757,10 @@ export type ComponentData = | ModalActionRowComponentData | ActionRowData; -export class VoiceChannel extends TextBasedChannelMixin(BaseGuildVoiceChannel, ['lastPinTimestamp', 'lastPinAt']) { +export class VoiceChannel extends TextBasedChannelMixin(BaseGuildVoiceChannel, true, [ + 'lastPinTimestamp', + 'lastPinAt', +]) { public videoQualityMode: VideoQualityMode | null; public get speakable(): boolean; public type: ChannelType.GuildVoice; @@ -3518,15 +3526,22 @@ export class GuildMemberRoleManager extends DataManager; } -export class MessageManager extends CachedManager { +export class MessageManager extends CachedManager< + Snowflake, + Message, + MessageResolvable +> { private constructor(channel: TextBasedChannel, iterable?: Iterable); - public channel: TextBasedChannel; - public crosspost(message: MessageResolvable): Promise; + public channel: If; + public crosspost(message: MessageResolvable): Promise>; public delete(message: MessageResolvable): Promise; - public edit(message: MessageResolvable, options: string | MessagePayload | MessageEditOptions): Promise; - public fetch(options: MessageResolvable | FetchMessageOptions): Promise; - public fetch(options?: FetchMessagesOptions): Promise>; - public fetchPinned(cache?: boolean): Promise>; + public edit( + message: MessageResolvable, + options: string | MessagePayload | MessageEditOptions, + ): Promise>; + public fetch(options: MessageResolvable | FetchMessageOptions): Promise>; + public fetch(options?: FetchMessagesOptions): Promise>>; + public fetchPinned(cache?: boolean): Promise>>; public react(message: MessageResolvable, emoji: EmojiIdentifierResolvable): Promise; public pin(message: MessageResolvable, reason?: string): Promise; public unpin(message: MessageResolvable, reason?: string): Promise; @@ -3649,22 +3664,31 @@ export class VoiceStateManager extends CachedManager = abstract new (...args: any[]) => T; -export function PartialTextBasedChannel(Base?: Constructable): Constructable; -export function TextBasedChannelMixin( +export function PartialTextBasedChannel( Base?: Constructable, - ignore?: I[], -): Constructable>; +): Constructable>; -export interface PartialTextBasedChannelFields { - send(options: string | MessagePayload | MessageOptions): Promise; +export function TextBasedChannelMixin< + T, + InGuild extends boolean = boolean, + I extends keyof TextBasedChannelFields = never, +>( + Base?: Constructable, + inGuild?: InGuild, + ignore?: I[], +): Constructable, I>>; + +export interface PartialTextBasedChannelFields { + send(options: string | MessagePayload | MessageOptions): Promise>; } -export interface TextBasedChannelFields extends PartialTextBasedChannelFields { +export interface TextBasedChannelFields + extends PartialTextBasedChannelFields { lastMessageId: Snowflake | null; get lastMessage(): Message | null; lastPinTimestamp: number | null; get lastPinAt(): Date | null; - messages: MessageManager; + messages: MessageManager; awaitMessageComponent( options?: AwaitMessageCollectorOptionsParams, ): Promise; @@ -5521,7 +5545,7 @@ export type Channel = | AnyThreadChannel | VoiceChannel; -export type TextBasedChannel = Extract; +export type TextBasedChannel = Exclude, PartialGroupDMChannel>; export type TextBasedChannelTypes = TextBasedChannel['type']; diff --git a/packages/discord.js/typings/index.test-d.ts b/packages/discord.js/typings/index.test-d.ts index 995c60ae4..79c7bf06f 100644 --- a/packages/discord.js/typings/index.test-d.ts +++ b/packages/discord.js/typings/index.test-d.ts @@ -1049,11 +1049,11 @@ declare const user: User; declare const guildMember: GuildMember; // Test whether the structures implement send -expectType(dmChannel.send); -expectType(threadChannel.send); -expectType(newsChannel.send); -expectType(textChannel.send); -expectType(voiceChannel.send); +expectType['send']>(dmChannel.send); +expectType['send']>(threadChannel.send); +expectType['send']>(newsChannel.send); +expectType['send']>(textChannel.send); +expectType['send']>(voiceChannel.send); expectAssignable(user); expectAssignable(guildMember); @@ -1190,6 +1190,34 @@ declare const guildChannelManager: GuildChannelManager; expectType>>(guildChannelManager.fetch()); expectType>>(guildChannelManager.fetch(undefined, {})); expectType>(guildChannelManager.fetch('0')); + + const channel = guildChannelManager.cache.first()!; + + if (channel.isTextBased()) { + const { messages } = channel; + const message = await messages.fetch('123'); + expectType>(messages); + expectType>>(messages.crosspost('1234567890')); + expectType>>(messages.edit('1234567890', 'text')); + expectType>>(messages.fetch('1234567890')); + expectType>>>(messages.fetchPinned()); + expectType(message.guild); + expectType(message.guildId); + expectType(message.channel.messages.channel); + } +} + +{ + const { messages } = dmChannel; + const message = await messages.fetch('123'); + expectType>(messages); + expectType>>(messages.crosspost('1234567890')); // This shouldn't even exist! + expectType>>(messages.edit('1234567890', 'text')); + expectType>>(messages.fetch('1234567890')); + expectType>>>(messages.fetchPinned()); + expectType(message.guild); + expectType(message.guildId); + expectType(message.channel.messages.channel); } declare const messageManager: MessageManager;