From 7b65a04cb12a501628621553baa4de2486be1aab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antonio=20Rom=C3=A1n?= Date: Wed, 22 Dec 2021 11:13:01 +0100 Subject: [PATCH] types: make channel types a lot stricter (#7120) --- typings/index.d.ts | 160 +++++++++++++++++++++------------------- typings/index.test-d.ts | 40 +++++++--- 2 files changed, 114 insertions(+), 86 deletions(-) diff --git a/typings/index.d.ts b/typings/index.d.ts index e0eaa6a3aca4..577e4127c0b5 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -386,7 +386,7 @@ export class BaseGuildEmoji extends Emoji { public requiresColons: boolean | null; } -export class BaseGuildTextChannel extends TextBasedChannel(GuildChannel) { +export class BaseGuildTextChannel extends TextBasedChannelMixin(GuildChannel) { protected constructor(guild: Guild, data?: RawGuildChannelData, client?: Client, immediatePatch?: boolean); public defaultAutoArchiveDuration?: ThreadAutoArchiveDuration; public messages: MessageManager; @@ -492,7 +492,7 @@ export type CategoryChannelTypes = ExcludeEnum< >; export class CategoryChannel extends GuildChannel { - public readonly children: Collection; + public readonly children: Collection>; public type: 'GUILD_CATEGORY'; /** @deprecated See [Self-serve Game Selling Deprecation](https://support-dev.discord.com/hc/en-us/articles/4414590563479) for more information */ public createChannel( @@ -507,7 +507,7 @@ export class CategoryChannel extends GuildChannel { public createChannel( name: string, options: CategoryCreateChannelOptions, - ): Promise; + ): Promise>; } export type CategoryChannelResolvable = Snowflake | CategoryChannel; @@ -521,9 +521,9 @@ export abstract class Channel extends Base { public id: Snowflake; public readonly partial: false; public type: keyof typeof ChannelTypes; - public delete(): Promise; - public fetch(force?: boolean): Promise; - public isText(): this is TextBasedChannels; + public delete(): Promise; + public fetch(force?: boolean): Promise; + public isText(): this is TextBasedChannel; public isVoice(): this is BaseGuildVoiceChannel; public isThread(): this is ThreadChannel; public toString(): ChannelMention; @@ -842,7 +842,7 @@ export class DiscordAPIError extends Error { public requestData: HTTPErrorData; } -export class DMChannel extends TextBasedChannel(Channel, ['bulkDelete']) { +export class DMChannel extends TextBasedChannelMixin(Channel, ['bulkDelete']) { private constructor(client: Client, data?: RawDMChannelData); public messages: MessageManager; public recipient: User; @@ -868,7 +868,7 @@ export class Emoji extends Base { export class Guild extends AnonymousGuild { private constructor(client: Client, data: RawGuildData); private _sortedRoles(): Collection; - private _sortedChannels(channel: Channel): Collection; + private _sortedChannels(channel: NonThreadGuildBasedChannel): Collection; public readonly afkChannel: VoiceChannel | null; public afkChannelId: Snowflake | null; @@ -1255,7 +1255,7 @@ export class Interaction extends Base { GuildTextBasedChannel | null, GuildTextBasedChannel | null, GuildTextBasedChannel | null, - TextBasedChannels | null + TextBasedChannel | null >; public channelId: Snowflake | null; public readonly createdAt: Date; @@ -1286,7 +1286,7 @@ export class Interaction extends Base { export class InteractionCollector extends Collector { public constructor(client: Client, options?: InteractionCollectorOptions); private _handleMessageDeletion(message: Message): void; - private _handleChannelDeletion(channel: GuildChannel): void; + private _handleChannelDeletion(channel: NonThreadGuildBasedChannel): void; private _handleGuildDeletion(guild: Guild): void; public channelId: Snowflake | null; @@ -1319,7 +1319,7 @@ export class InteractionWebhook extends PartialWebhookMixin() { export class Invite extends Base { private constructor(client: Client, data: RawInviteData); - public channel: GuildChannel | PartialGroupDMChannel; + public channel: NonThreadGuildBasedChannel | PartialGroupDMChannel; public channelId: Snowflake; public code: string; public readonly deletable: boolean; @@ -1399,7 +1399,6 @@ export type AwaitMessageCollectorOptionsParams< keyof AwaitMessageComponentOptions >; -export type GuildTextBasedChannel = Exclude; export interface StringMappedInteractionTypes { BUTTON: ButtonInteraction; SELECT_MENU: SelectMenuInteraction; @@ -1426,7 +1425,7 @@ export class Message extends Base { public applicationId: Snowflake | null; public attachments: Collection; public author: User; - public readonly channel: If; + public readonly channel: If; public channelId: Snowflake; public readonly cleanContent: string; public components: MessageActionRow[]; @@ -1551,11 +1550,11 @@ export class MessageButton extends BaseMessageComponent { } export class MessageCollector extends Collector { - public constructor(channel: TextBasedChannels, options?: MessageCollectorOptions); - private _handleChannelDeletion(channel: GuildChannel): void; + public constructor(channel: TextBasedChannel, options?: MessageCollectorOptions); + private _handleChannelDeletion(channel: NonThreadGuildBasedChannel): void; private _handleGuildDeletion(guild: Guild): void; - public channel: TextBasedChannels; + public channel: TextBasedChannel; public readonly endReason: string | null; public options: MessageCollectorOptions; public received: number; @@ -1665,11 +1664,11 @@ export class MessageMentions { everyone: boolean, repliedUser?: APIUser | User, ); - private _channels: Collection | null; + private _channels: Collection | null; private readonly _content: string; private _members: Collection | null; - public readonly channels: Collection; + public readonly channels: Collection; public readonly client: Client; public everyone: boolean; public readonly guild: Guild; @@ -1774,9 +1773,9 @@ export class PartialGroupDMChannel extends Channel { } export class PermissionOverwrites extends Base { - private constructor(client: Client, data: RawPermissionOverwriteData, channel: GuildChannel); + private constructor(client: Client, data: RawPermissionOverwriteData, channel: NonThreadGuildBasedChannel); public allow: Readonly; - public readonly channel: GuildChannel; + public readonly channel: NonThreadGuildBasedChannel; public deny: Readonly; public id: Snowflake; public type: OverwriteType; @@ -1818,7 +1817,7 @@ export class Presence extends Base { export class ReactionCollector extends Collector { public constructor(message: Message, options?: ReactionCollectorOptions); - private _handleChannelDeletion(channel: GuildChannel): void; + private _handleChannelDeletion(channel: NonThreadGuildBasedChannel): void; private _handleGuildDeletion(guild: Guild): void; private _handleMessageDeletion(message: Message): void; @@ -1889,7 +1888,7 @@ export class Role extends Base { public edit(data: RoleData, reason?: string): Promise; public equals(role: Role): boolean; public iconURL(options?: StaticImageURLOptions): string | null; - public permissionsIn(channel: GuildChannel | Snowflake, checkAdmin?: boolean): Readonly; + public permissionsIn(channel: NonThreadGuildBasedChannel | Snowflake, checkAdmin?: boolean): Readonly; public setColor(color: ColorResolvable, reason?: string): Promise; public setHoist(hoist?: boolean, reason?: string): Promise; public setMentionable(mentionable?: boolean, reason?: string): Promise; @@ -2226,7 +2225,7 @@ export class TextChannel extends BaseGuildTextChannel { public setRateLimitPerUser(rateLimitPerUser: number, reason?: string): Promise; } -export class ThreadChannel extends TextBasedChannel(Channel) { +export class ThreadChannel extends TextBasedChannelMixin(Channel) { private constructor(guild: Guild, data?: RawThreadChannelData, client?: Client, fromInteraction?: boolean); public archived: boolean | null; public readonly archivedAt: Date | null; @@ -2254,7 +2253,7 @@ export class ThreadChannel extends TextBasedChannel(Channel) { public rateLimitPerUser: number | null; public type: ThreadChannelTypes; public readonly unarchivable: boolean; - public delete(reason?: string): Promise; + public delete(reason?: string): Promise; public edit(data: ThreadEditData, reason?: string): Promise; public join(): Promise; public leave(): Promise; @@ -2295,8 +2294,8 @@ export class ThreadMemberFlags extends BitField { } export class Typing extends Base { - private constructor(channel: TextBasedChannels, user: PartialUser, data?: RawTypingData); - public channel: TextBasedChannels; + private constructor(channel: TextBasedChannel, user: PartialUser, data?: RawTypingData); + public channel: TextBasedChannel; public user: PartialUser; public startedTimestamp: number; public readonly startedAt: Date; @@ -2357,7 +2356,7 @@ export class Util extends null { /** @deprecated When not using with `makeCache` use `Sweepers.archivedThreadSweepFilter` instead */ public static archivedThreadSweepFilter(lifetime?: number): SweepFilter; public static basename(path: string, ext?: string): string; - public static cleanContent(str: string, channel: TextBasedChannels): string; + public static cleanContent(str: string, channel: TextBasedChannel): string; /** @deprecated Use {@link MessageOptions.allowedMentions} to control mentions in a message instead. */ public static removeMentions(str: string): string; public static cloneObject(obj: unknown): unknown; @@ -2383,7 +2382,7 @@ export class Util extends null { public static resolveColor(color: ColorResolvable): number; public static resolvePartialEmoji(emoji: EmojiIdentifierResolvable): Partial | null; public static verifyString(data: string, error?: typeof Error, errorMessage?: string, allowEmpty?: boolean): string; - public static setPosition( + public static setPosition( item: T, position: number, relative: boolean, @@ -2438,7 +2437,7 @@ export class VoiceRegion { export class VoiceState extends Base { private constructor(guild: Guild, data: RawVoiceStateData); - public readonly channel: VoiceChannel | StageChannel | null; + public readonly channel: VoiceBasedChannel | null; public channelId: Snowflake | null; public readonly deaf: boolean | null; public guild: Guild; @@ -2896,9 +2895,9 @@ export class BaseGuildEmojiManager extends CachedManager { +export class ChannelManager extends CachedManager { private constructor(client: Client, iterable: Iterable); - public fetch(id: Snowflake, options?: FetchChannelOptions): Promise; + public fetch(id: Snowflake, options?: FetchChannelOptions): Promise; } export class GuildApplicationCommandManager extends ApplicationCommandManager { @@ -2926,11 +2925,7 @@ export type MappedGuildChannelTypes = EnumValueMapped< export type GuildChannelTypes = CategoryChannelTypes | ChannelTypes.GUILD_CATEGORY | 'GUILD_CATEGORY'; -export class GuildChannelManager extends CachedManager< - Snowflake, - GuildChannel | ThreadChannel, - GuildChannelResolvable -> { +export class GuildChannelManager extends CachedManager { private constructor(guild: Guild, iterable?: Iterable); public readonly channelCountWithoutThreads: number; public guild: Guild; @@ -2940,20 +2935,9 @@ export class GuildChannelManager extends CachedManager< name: string, options: GuildChannelCreateOptions & { type: T }, ): Promise; - public create( - name: string, - options: GuildChannelCreateOptions, - ): Promise; - public fetch( - id: Snowflake, - options?: BaseFetchOptions, - ): Promise; - public fetch( - id?: undefined, - options?: BaseFetchOptions, - ): Promise< - Collection - >; + public create(name: string, options: GuildChannelCreateOptions): Promise; + public fetch(id: Snowflake, options?: BaseFetchOptions): Promise; + public fetch(id?: undefined, options?: BaseFetchOptions): Promise>; public setPositions(channelPositions: readonly ChannelPosition[]): Promise; public fetchActiveThreads(cache?: boolean): Promise; } @@ -3068,8 +3052,8 @@ export class GuildMemberRoleManager extends DataManager { - private constructor(channel: TextBasedChannels, iterable?: Iterable); - public channel: TextBasedChannels; + private constructor(channel: TextBasedChannel, iterable?: Iterable); + public channel: TextBasedChannel; public cache: Collection; public crosspost(message: MessageResolvable): Promise; public delete(message: MessageResolvable): Promise; @@ -3094,24 +3078,24 @@ export class PermissionOverwriteManager extends CachedManager< public set( overwrites: readonly OverwriteResolvable[] | Collection, reason?: string, - ): Promise; + ): Promise; private upsert( userOrRole: RoleResolvable | UserResolvable, options: PermissionOverwriteOptions, overwriteOptions?: GuildChannelOverwriteOptions, existing?: PermissionOverwrites, - ): Promise; + ): Promise; public create( userOrRole: RoleResolvable | UserResolvable, options: PermissionOverwriteOptions, overwriteOptions?: GuildChannelOverwriteOptions, - ): Promise; + ): Promise; public edit( userOrRole: RoleResolvable | UserResolvable, options: PermissionOverwriteOptions, overwriteOptions?: GuildChannelOverwriteOptions, - ): Promise; - public delete(userOrRole: RoleResolvable | UserResolvable, reason?: string): Promise; + ): Promise; + public delete(userOrRole: RoleResolvable | UserResolvable, reason?: string): Promise; } export class PresenceManager extends CachedManager { @@ -3196,7 +3180,7 @@ export class VoiceStateManager extends CachedManager = abstract new (...args: any[]) => T; export function PartialTextBasedChannel(Base?: Constructable): Constructable; -export function TextBasedChannel( +export function TextBasedChannelMixin( Base?: Constructable, ignore?: I[], ): Constructable>; @@ -3789,14 +3773,14 @@ export interface ChannelLogsQueryOptions { export type ChannelMention = `<#${Snowflake}>`; export interface ChannelPosition { - channel: GuildChannel | Snowflake; + channel: NonThreadGuildBasedChannel | Snowflake; lockPermissions?: boolean; parent?: CategoryChannelResolvable | null; position?: number; } export type GuildTextChannelResolvable = TextChannel | NewsChannel | Snowflake; -export type ChannelResolvable = Channel | Snowflake; +export type ChannelResolvable = AnyChannel | Snowflake; export interface ChannelWebhookCreateOptions { avatar?: BufferResolvable | Base64Resolvable | null; @@ -3819,10 +3803,13 @@ export interface ClientEvents extends BaseClientEvents { /** @deprecated See [this issue](https://github.com/discord/discord-api-docs/issues/3690) for more information. */ applicationCommandUpdate: [oldCommand: ApplicationCommand | null, newCommand: ApplicationCommand]; cacheSweep: [message: string]; - channelCreate: [channel: GuildChannel]; - channelDelete: [channel: DMChannel | GuildChannel]; - channelPinsUpdate: [channel: TextBasedChannels, date: Date]; - channelUpdate: [oldChannel: DMChannel | GuildChannel, newChannel: DMChannel | GuildChannel]; + channelCreate: [channel: NonThreadGuildBasedChannel]; + channelDelete: [channel: DMChannel | NonThreadGuildBasedChannel]; + channelPinsUpdate: [channel: TextBasedChannel, date: Date]; + channelUpdate: [ + oldChannel: DMChannel | NonThreadGuildBasedChannel, + newChannel: DMChannel | NonThreadGuildBasedChannel, + ]; warn: [message: string]; emojiCreate: [emoji: GuildEmoji]; emojiDelete: [emoji: GuildEmoji]; @@ -4000,7 +3987,7 @@ export interface CommandInteractionOption options?: CommandInteractionOption[]; user?: User; member?: CacheTypeReducer; - channel?: CacheTypeReducer; + channel?: CacheTypeReducer; role?: CacheTypeReducer; message?: GuildCacheMessage; } @@ -4009,7 +3996,7 @@ export interface CommandInteractionResolvedData; members?: Collection>; roles?: Collection>; - channels?: Collection>; + channels?: Collection>; messages?: Collection>; } @@ -4425,7 +4412,7 @@ export type GuildAuditLogsActionType = GuildAuditLogsTypes[keyof GuildAuditLogsT export interface GuildAuditLogsEntryExtraField { MEMBER_PRUNE: { removed: number; days: number }; - MEMBER_MOVE: { channel: VoiceChannel | StageChannel | { id: Snowflake }; count: number }; + MEMBER_MOVE: { channel: VoiceBasedChannel | { id: Snowflake }; count: number }; MESSAGE_DELETE: { channel: GuildTextBasedChannel | { id: Snowflake }; count: number }; MESSAGE_BULK_DELETE: { channel: GuildTextBasedChannel | { id: Snowflake }; count: number }; MESSAGE_PIN: { channel: GuildTextBasedChannel | { id: Snowflake }; messageId: Snowflake }; @@ -4458,7 +4445,7 @@ export interface GuildAuditLogsEntryTargetField { parent?: CategoryChannelResolvable; @@ -4520,7 +4507,7 @@ export interface GuildCreateOptions { export interface GuildWidgetSettings { enabled: boolean; - channel: GuildChannel | null; + channel: NonThreadGuildBasedChannel | null; } export interface GuildEditData { @@ -4600,7 +4587,7 @@ export interface GuildMemberEditData { export type GuildMemberResolvable = GuildMember | UserResolvable; -export type GuildResolvable = Guild | GuildChannel | GuildMember | GuildEmoji | Invite | Role | Snowflake; +export type GuildResolvable = Guild | NonThreadGuildBasedChannel | GuildMember | GuildEmoji | Invite | Role | Snowflake; export interface GuildPruneMembersOptions { count?: boolean; @@ -4629,7 +4616,7 @@ export interface GuildListMembersOptions { export type GuildTemplateResolvable = string; -export type GuildVoiceChannelResolvable = VoiceChannel | StageChannel | Snowflake; +export type GuildVoiceChannelResolvable = VoiceBasedChannel | Snowflake; export type HexColorString = `#${string}`; @@ -4669,7 +4656,7 @@ export type IntegrationType = 'twitch' | 'youtube' | 'discord'; export interface InteractionCollectorOptions extends CollectorOptions<[T]> { - channel?: TextBasedChannels; + channel?: TextBasedChannel; componentType?: MessageComponentType | MessageComponentTypes; guild?: Guild; interactionType?: InteractionType | InteractionTypes; @@ -5000,7 +4987,7 @@ export interface MessageSelectOptionData { export type MessageTarget = | Interaction | InteractionWebhook - | TextBasedChannels + | TextBasedChannel | User | GuildMember | Webhook @@ -5366,9 +5353,28 @@ export interface LimitedCollectionOptions { sweepInterval?: number; } -export type TextBasedChannels = PartialDMChannel | DMChannel | TextChannel | NewsChannel | ThreadChannel; +export type AnyChannel = + | CategoryChannel + | DMChannel + | PartialDMChannel + | NewsChannel + | StageChannel + | StoreChannel + | TextChannel + | ThreadChannel + | VoiceChannel; + +export type TextBasedChannel = Extract; + +export type TextBasedChannelTypes = TextBasedChannel['type']; + +export type VoiceBasedChannel = Extract; + +export type GuildBasedChannel = Extract; + +export type NonThreadGuildBasedChannel = Exclude; -export type TextBasedChannelTypes = TextBasedChannels['type']; +export type GuildTextBasedChannel = Extract; export type TextChannelResolvable = Snowflake | TextChannel; @@ -5425,7 +5431,7 @@ export interface Vanity { export type VerificationLevel = keyof typeof VerificationLevels; -export type VoiceBasedChannelTypes = 'GUILD_VOICE' | 'GUILD_STAGE_VOICE'; +export type VoiceBasedChannelTypes = VoiceBasedChannel['type']; export type VoiceChannelResolvable = Snowflake | VoiceChannel; diff --git a/typings/index.test-d.ts b/typings/index.test-d.ts index fbfc9e4b90a7..971b12c5d92b 100644 --- a/typings/index.test-d.ts +++ b/typings/index.test-d.ts @@ -37,13 +37,11 @@ import { DMChannel, Guild, GuildApplicationCommandManager, - GuildChannel, GuildChannelManager, GuildEmoji, GuildEmojiManager, GuildMember, GuildResolvable, - GuildTextBasedChannel, Intents, Interaction, InteractionCollector, @@ -72,7 +70,12 @@ import { StageChannel, StoreChannel, TextBasedChannelFields, - TextBasedChannels, + TextBasedChannel, + TextBasedChannelTypes, + VoiceBasedChannel, + GuildBasedChannel, + NonThreadGuildBasedChannel, + GuildTextBasedChannel, TextChannel, ThreadChannel, ThreadMember, @@ -89,6 +92,7 @@ import { Emoji, MessageActionRowComponent, MessageSelectMenu, + PartialDMChannel, } from '.'; import type { ApplicationCommandOptionTypes } from './enums'; import { expectAssignable, expectDeprecated, expectNotAssignable, expectNotType, expectType } from 'tsd'; @@ -542,7 +546,7 @@ client.on('messageCreate', async message => { expectType(message.member); } - expectType(message.channel); + expectType(message.channel); expectNotType(message.channel); // @ts-expect-error @@ -890,7 +894,7 @@ declare const typing: Typing; expectType(typing.user); if (typing.user.partial) expectType(typing.user.username); -expectType(typing.channel); +expectType(typing.channel); if (typing.channel.partial) expectType(typing.channel.lastMessageId); expectType(typing.member); @@ -1076,7 +1080,7 @@ client.on('interactionCreate', async interaction => { expectAssignable(interaction); expectType>>(interaction.reply({ fetchReply: true })); - expectType(interaction.options.getChannel('test', true)); + expectType(interaction.options.getChannel('test', true)); expectType(interaction.options.getRole('test', true)); } else { // @ts-expect-error @@ -1086,9 +1090,7 @@ client.on('interactionCreate', async interaction => { expectType(interaction.options.getMember('test')); expectType(interaction.options.getMember('test', true)); - expectType( - interaction.options.getChannel('test', true), - ); + expectType(interaction.options.getChannel('test', true)); expectType(interaction.options.getRole('test', true)); } @@ -1229,3 +1231,23 @@ expectType>( // @ts-expect-error Invalid audit log ID guild.fetchAuditLogs({ type: 2000 }).then(al => al.entries.first()?.target), ); + +declare const TextBasedChannel: TextBasedChannel; +declare const TextBasedChannelTypes: TextBasedChannelTypes; +declare const VoiceBasedChannel: VoiceBasedChannel; +declare const GuildBasedChannel: GuildBasedChannel; +declare const NonThreadGuildBasedChannel: NonThreadGuildBasedChannel; +declare const GuildTextBasedChannel: GuildTextBasedChannel; + +expectType(TextBasedChannel); +expectType<'DM' | 'GUILD_NEWS' | 'GUILD_TEXT' | 'GUILD_PUBLIC_THREAD' | 'GUILD_PRIVATE_THREAD' | 'GUILD_NEWS_THREAD'>( + TextBasedChannelTypes, +); +expectType(VoiceBasedChannel); +expectType( + GuildBasedChannel, +); +expectType( + NonThreadGuildBasedChannel, +); +expectType(GuildTextBasedChannel);