refactor!: use AsyncEventEmitter instead of EventEmitter (#10710)

BREAKING CHANGE: The `BaseClient`, `Shard`, `ShardingManager`, and `Collector` classes now extend `AsyncEventEmitter` instead of `EventEmitter`.
This commit is contained in:
Almeida
2025-01-23 09:20:05 +00:00
committed by GitHub
parent aa90f00d11
commit 687e2ae672
9 changed files with 78 additions and 132 deletions

View File

@@ -72,6 +72,7 @@
"@discordjs/util": "workspace:^", "@discordjs/util": "workspace:^",
"@discordjs/ws": "workspace:^", "@discordjs/ws": "workspace:^",
"@sapphire/snowflake": "3.5.5", "@sapphire/snowflake": "3.5.5",
"@vladfrangu/async_event_emitter": "^2.4.6",
"discord-api-types": "^0.37.114", "discord-api-types": "^0.37.114",
"fast-deep-equal": "3.1.3", "fast-deep-equal": "3.1.3",
"lodash.snakecase": "4.1.1", "lodash.snakecase": "4.1.1",

View File

@@ -1,7 +1,7 @@
'use strict'; 'use strict';
const EventEmitter = require('node:events');
const { REST } = require('@discordjs/rest'); const { REST } = require('@discordjs/rest');
const { AsyncEventEmitter } = require('@vladfrangu/async_event_emitter');
const { Routes } = require('discord-api-types/v10'); const { Routes } = require('discord-api-types/v10');
const { DiscordjsTypeError, ErrorCodes } = require('../errors'); const { DiscordjsTypeError, ErrorCodes } = require('../errors');
const { Options } = require('../util/Options'); const { Options } = require('../util/Options');
@@ -9,11 +9,11 @@ const { flatten } = require('../util/Util');
/** /**
* The base class for all clients. * The base class for all clients.
* @extends {EventEmitter} * @extends {AsyncEventEmitter}
*/ */
class BaseClient extends EventEmitter { class BaseClient extends AsyncEventEmitter {
constructor(options = {}) { constructor(options = {}) {
super({ captureRejections: true }); super();
if (typeof options !== 'object' || options === null) { if (typeof options !== 'object' || options === null) {
throw new DiscordjsTypeError(ErrorCodes.InvalidType, 'options', 'object', true); throw new DiscordjsTypeError(ErrorCodes.InvalidType, 'options', 'object', true);

View File

@@ -1,11 +1,11 @@
'use strict'; 'use strict';
const EventEmitter = require('node:events');
const path = require('node:path'); const path = require('node:path');
const process = require('node:process'); const process = require('node:process');
const { setTimeout, clearTimeout } = require('node:timers'); const { setTimeout, clearTimeout } = require('node:timers');
const { setTimeout: sleep } = require('node:timers/promises'); const { setTimeout: sleep } = require('node:timers/promises');
const { SHARE_ENV } = require('node:worker_threads'); const { SHARE_ENV } = require('node:worker_threads');
const { AsyncEventEmitter } = require('@vladfrangu/async_event_emitter');
const { DiscordjsError, ErrorCodes } = require('../errors'); const { DiscordjsError, ErrorCodes } = require('../errors');
const { ShardEvents } = require('../util/ShardEvents'); const { ShardEvents } = require('../util/ShardEvents');
const { makeError, makePlainError } = require('../util/Util'); const { makeError, makePlainError } = require('../util/Util');
@@ -17,9 +17,9 @@ let Worker = null;
* A self-contained shard created by the {@link ShardingManager}. Each one has a {@link ChildProcess} that contains * A self-contained shard created by the {@link ShardingManager}. Each one has a {@link ChildProcess} that contains
* an instance of the bot and its {@link Client}. When its child process/worker exits for any reason, the shard will * an instance of the bot and its {@link Client}. When its child process/worker exits for any reason, the shard will
* spawn a new one to replace it as necessary. * spawn a new one to replace it as necessary.
* @extends {EventEmitter} * @extends {AsyncEventEmitter}
*/ */
class Shard extends EventEmitter { class Shard extends AsyncEventEmitter {
constructor(manager, id) { constructor(manager, id) {
super(); super();
@@ -445,7 +445,7 @@ class Shard extends EventEmitter {
/** /**
* Increments max listeners by one for a given emitter, if they are not zero. * Increments max listeners by one for a given emitter, if they are not zero.
* @param {EventEmitter|process} emitter The emitter that emits the events. * @param {Worker|ChildProcess} emitter The emitter that emits the events.
* @private * @private
*/ */
incrementMaxListeners(emitter) { incrementMaxListeners(emitter) {
@@ -457,7 +457,7 @@ class Shard extends EventEmitter {
/** /**
* Decrements max listeners by one for a given emitter, if they are not zero. * Decrements max listeners by one for a given emitter, if they are not zero.
* @param {EventEmitter|process} emitter The emitter that emits the events. * @param {Worker|ChildProcess} emitter The emitter that emits the events.
* @private * @private
*/ */
decrementMaxListeners(emitter) { decrementMaxListeners(emitter) {

View File

@@ -242,7 +242,7 @@ class ShardClientUtil {
/** /**
* Increments max listeners by one for a given emitter, if they are not zero. * Increments max listeners by one for a given emitter, if they are not zero.
* @param {EventEmitter|process} emitter The emitter that emits the events. * @param {Worker|ChildProcess} emitter The emitter that emits the events.
* @private * @private
*/ */
incrementMaxListeners(emitter) { incrementMaxListeners(emitter) {
@@ -254,7 +254,7 @@ class ShardClientUtil {
/** /**
* Decrements max listeners by one for a given emitter, if they are not zero. * Decrements max listeners by one for a given emitter, if they are not zero.
* @param {EventEmitter|process} emitter The emitter that emits the events. * @param {Worker|ChildProcess} emitter The emitter that emits the events.
* @private * @private
*/ */
decrementMaxListeners(emitter) { decrementMaxListeners(emitter) {

View File

@@ -1,11 +1,11 @@
'use strict'; 'use strict';
const EventEmitter = require('node:events');
const fs = require('node:fs'); const fs = require('node:fs');
const path = require('node:path'); const path = require('node:path');
const process = require('node:process'); const process = require('node:process');
const { setTimeout: sleep } = require('node:timers/promises'); const { setTimeout: sleep } = require('node:timers/promises');
const { Collection } = require('@discordjs/collection'); const { Collection } = require('@discordjs/collection');
const { AsyncEventEmitter } = require('@vladfrangu/async_event_emitter');
const { Shard } = require('./Shard'); const { Shard } = require('./Shard');
const { DiscordjsError, DiscordjsTypeError, DiscordjsRangeError, ErrorCodes } = require('../errors'); const { DiscordjsError, DiscordjsTypeError, DiscordjsRangeError, ErrorCodes } = require('../errors');
const { fetchRecommendedShardCount } = require('../util/Util'); const { fetchRecommendedShardCount } = require('../util/Util');
@@ -17,9 +17,9 @@ const { fetchRecommendedShardCount } = require('../util/Util');
* process, and there are several useful methods that utilize it in order to simplify tasks that are normally difficult * process, and there are several useful methods that utilize it in order to simplify tasks that are normally difficult
* with sharding. It can spawn a specific number of shards or the amount that Discord suggests for the bot, and takes a * with sharding. It can spawn a specific number of shards or the amount that Discord suggests for the bot, and takes a
* path to your main bot script to launch for each one. * path to your main bot script to launch for each one.
* @extends {EventEmitter} * @extends {AsyncEventEmitter}
*/ */
class ShardingManager extends EventEmitter { class ShardingManager extends AsyncEventEmitter {
/** /**
* The mode to spawn shards with for a {@link ShardingManager}. Can be either one of: * The mode to spawn shards with for a {@link ShardingManager}. Can be either one of:
* * 'process' to use child processes * * 'process' to use child processes

View File

@@ -1,8 +1,8 @@
'use strict'; 'use strict';
const EventEmitter = require('node:events');
const { setTimeout, clearTimeout } = require('node:timers'); const { setTimeout, clearTimeout } = require('node:timers');
const { Collection } = require('@discordjs/collection'); const { Collection } = require('@discordjs/collection');
const { AsyncEventEmitter } = require('@vladfrangu/async_event_emitter');
const { DiscordjsTypeError, ErrorCodes } = require('../../errors'); const { DiscordjsTypeError, ErrorCodes } = require('../../errors');
const { flatten } = require('../../util/Util'); const { flatten } = require('../../util/Util');
@@ -25,10 +25,10 @@ const { flatten } = require('../../util/Util');
/** /**
* Abstract class for defining a new Collector. * Abstract class for defining a new Collector.
* @extends {EventEmitter} * @extends {AsyncEventEmitter}
* @abstract * @abstract
*/ */
class Collector extends EventEmitter { class Collector extends AsyncEventEmitter {
constructor(client, options = {}) { constructor(client, options = {}) {
super(); super();

View File

@@ -20,6 +20,7 @@ import { Awaitable, JSONEncodable } from '@discordjs/util';
import { Collection, ReadonlyCollection } from '@discordjs/collection'; import { Collection, ReadonlyCollection } from '@discordjs/collection';
import { BaseImageURLOptions, ImageURLOptions, RawFile, REST, RESTOptions } from '@discordjs/rest'; import { BaseImageURLOptions, ImageURLOptions, RawFile, REST, RESTOptions } from '@discordjs/rest';
import { WebSocketManager, WebSocketManagerOptions } from '@discordjs/ws'; import { WebSocketManager, WebSocketManagerOptions } from '@discordjs/ws';
import { AsyncEventEmitter } from '@vladfrangu/async_event_emitter';
import { import {
APIActionRowComponent, APIActionRowComponent,
APIApplicationCommandInteractionData, APIApplicationCommandInteractionData,
@@ -177,7 +178,6 @@ import {
GatewayVoiceChannelEffectSendDispatchData, GatewayVoiceChannelEffectSendDispatchData,
} from 'discord-api-types/v10'; } from 'discord-api-types/v10';
import { ChildProcess } from 'node:child_process'; import { ChildProcess } from 'node:child_process';
import { EventEmitter } from 'node:events';
import { Stream } from 'node:stream'; import { Stream } from 'node:stream';
import { MessagePort, Worker } from 'node:worker_threads'; import { MessagePort, Worker } from 'node:worker_threads';
import { import {
@@ -515,7 +515,7 @@ export abstract class Base {
public valueOf(): string; public valueOf(): string;
} }
export class BaseClient extends EventEmitter implements AsyncDisposable { export class BaseClient<Events extends {}> extends AsyncEventEmitter<Events> implements AsyncDisposable {
public constructor(options?: ClientOptions | WebhookClientOptions); public constructor(options?: ClientOptions | WebhookClientOptions);
private decrementMaxListeners(): void; private decrementMaxListeners(): void;
private incrementMaxListeners(): void; private incrementMaxListeners(): void;
@@ -959,7 +959,7 @@ export type If<Value extends boolean, TrueResult, FalseResult = null> = Value ex
? FalseResult ? FalseResult
: TrueResult | FalseResult; : TrueResult | FalseResult;
export class Client<Ready extends boolean = boolean> extends BaseClient { export class Client<Ready extends boolean = boolean> extends BaseClient<ClientEventTypes> {
public constructor(options: ClientOptions); public constructor(options: ClientOptions);
private actions: unknown; private actions: unknown;
private expectedGuilds: Set<Snowflake>; private expectedGuilds: Set<Snowflake>;
@@ -977,18 +977,6 @@ export class Client<Ready extends boolean = boolean> extends BaseClient {
// This a technique used to brand the ready state. Or else we'll get `never` errors on typeguard checks. // This a technique used to brand the ready state. Or else we'll get `never` errors on typeguard checks.
private readonly _ready: Ready; private readonly _ready: Ready;
// Override inherited static EventEmitter methods, with added type checks for Client events.
public static once<Emitter extends EventEmitter, Event extends keyof ClientEvents>(
eventEmitter: Emitter,
eventName: Emitter extends Client ? Event : string | symbol,
options?: { signal?: AbortSignal | undefined },
): Promise<Emitter extends Client ? ClientEvents[Event] : any[]>;
public static on<Emitter extends EventEmitter, Event extends keyof ClientEvents>(
eventEmitter: Emitter,
eventName: Emitter extends Client ? Event : string | symbol,
options?: { signal?: AbortSignal | undefined },
): AsyncIterableIterator<Emitter extends Client ? ClientEvents[Event] : any[]>;
public application: If<Ready, ClientApplication>; public application: If<Ready, ClientApplication>;
public channels: ChannelManager; public channels: ChannelManager;
public get emojis(): BaseGuildEmojiManager; public get emojis(): BaseGuildEmojiManager;
@@ -1023,30 +1011,6 @@ export class Client<Ready extends boolean = boolean> extends BaseClient {
public login(token?: string): Promise<string>; public login(token?: string): Promise<string>;
public isReady(): this is Client<true>; public isReady(): this is Client<true>;
public toJSON(): unknown; public toJSON(): unknown;
public on<Event extends keyof ClientEvents>(event: Event, listener: (...args: ClientEvents[Event]) => void): this;
public on<Event extends string | symbol>(
event: Exclude<Event, keyof ClientEvents>,
listener: (...args: any[]) => void,
): this;
public once<Event extends keyof ClientEvents>(event: Event, listener: (...args: ClientEvents[Event]) => void): this;
public once<Event extends string | symbol>(
event: Exclude<Event, keyof ClientEvents>,
listener: (...args: any[]) => void,
): this;
public emit<Event extends keyof ClientEvents>(event: Event, ...args: ClientEvents[Event]): boolean;
public emit<Event extends string | symbol>(event: Exclude<Event, keyof ClientEvents>, ...args: unknown[]): boolean;
public off<Event extends keyof ClientEvents>(event: Event, listener: (...args: ClientEvents[Event]) => void): this;
public off<Event extends string | symbol>(
event: Exclude<Event, keyof ClientEvents>,
listener: (...args: any[]) => void,
): this;
public removeAllListeners<Event extends keyof ClientEvents>(event?: Event): this;
public removeAllListeners<Event extends string | symbol>(event?: Exclude<Event, keyof ClientEvents>): this;
} }
export interface StickerPackFetchOptions { export interface StickerPackFetchOptions {
@@ -1134,7 +1098,12 @@ export interface CollectorEventTypes<Key, Value, Extras extends unknown[] = []>
end: [collected: ReadonlyCollection<Key, Value>, reason: string]; end: [collected: ReadonlyCollection<Key, Value>, reason: string];
} }
export abstract class Collector<Key, Value, Extras extends unknown[] = []> extends EventEmitter { export abstract class Collector<
Key,
Value,
Extras extends unknown[] = [],
EventTypes extends {} = CollectorEventTypes<Key, Value, Extras>,
> extends AsyncEventEmitter<EventTypes> {
protected constructor(client: Client<true>, options?: CollectorOptions<[Value, ...Extras]>); protected constructor(client: Client<true>, options?: CollectorOptions<[Value, ...Extras]>);
private _timeout: NodeJS.Timeout | null; private _timeout: NodeJS.Timeout | null;
private _idletimeout: NodeJS.Timeout | null; private _idletimeout: NodeJS.Timeout | null;
@@ -1160,16 +1129,6 @@ export abstract class Collector<Key, Value, Extras extends unknown[] = []> exten
protected listener: (...args: any[]) => void; protected listener: (...args: any[]) => void;
public abstract collect(...args: unknown[]): Awaitable<Key | null>; public abstract collect(...args: unknown[]): Awaitable<Key | null>;
public abstract dispose(...args: unknown[]): Key | null; public abstract dispose(...args: unknown[]): Key | null;
public on<EventKey extends keyof CollectorEventTypes<Key, Value, Extras>>(
event: EventKey,
listener: (...args: CollectorEventTypes<Key, Value, Extras>[EventKey]) => void,
): this;
public once<EventKey extends keyof CollectorEventTypes<Key, Value, Extras>>(
event: EventKey,
listener: (...args: CollectorEventTypes<Key, Value, Extras>[EventKey]) => void,
): this;
} }
export class ChatInputCommandInteraction<Cached extends CacheType = CacheType> extends CommandInteraction<Cached> { export class ChatInputCommandInteraction<Cached extends CacheType = CacheType> extends CommandInteraction<Cached> {
@@ -2009,11 +1968,7 @@ export class InteractionCallbackResource {
public type: InteractionResponseType; public type: InteractionResponseType;
} }
export class InteractionCollector<Interaction extends CollectedInteraction> extends Collector< export class InteractionCollector<Interaction extends CollectedInteraction> extends Collector<Snowflake, Interaction> {
Snowflake,
Interaction,
[Collection<Snowflake, Interaction>]
> {
public constructor(client: Client<true>, options?: InteractionCollectorOptions<Interaction>); public constructor(client: Client<true>, options?: InteractionCollectorOptions<Interaction>);
private _handleMessageDeletion(message: Message): void; private _handleMessageDeletion(message: Message): void;
private _handleChannelDeletion(channel: NonThreadGuildBasedChannel): void; private _handleChannelDeletion(channel: NonThreadGuildBasedChannel): void;
@@ -2031,19 +1986,6 @@ export class InteractionCollector<Interaction extends CollectedInteraction> exte
public collect(interaction: Interaction): Snowflake; public collect(interaction: Interaction): Snowflake;
public empty(): void; public empty(): void;
public dispose(interaction: Interaction): Snowflake; public dispose(interaction: Interaction): Snowflake;
public on(event: 'collect' | 'dispose' | 'ignore', listener: (interaction: Interaction) => void): this;
public on(
event: 'end',
listener: (collected: ReadonlyCollection<Snowflake, Interaction>, reason: string) => void,
): this;
public on(event: string, listener: (...args: any[]) => void): this;
public once(event: 'collect' | 'dispose' | 'ignore', listener: (interaction: Interaction) => void): this;
public once(
event: 'end',
listener: (collected: ReadonlyCollection<Snowflake, Interaction>, reason: string) => void,
): this;
public once(event: string, listener: (...args: any[]) => void): this;
} }
// tslint:disable-next-line no-empty-interface // tslint:disable-next-line no-empty-interface
@@ -2767,7 +2709,16 @@ export class PollAnswer extends Base {
public fetchVoters(options?: BaseFetchPollAnswerVotersOptions): Promise<Collection<Snowflake, User>>; public fetchVoters(options?: BaseFetchPollAnswerVotersOptions): Promise<Collection<Snowflake, User>>;
} }
export class ReactionCollector extends Collector<Snowflake | string, MessageReaction, [User]> { export interface ReactionCollectorEventTypes extends CollectorEventTypes<Snowflake | string, MessageReaction, [User]> {
remove: [reaction: MessageReaction, user: User];
}
export class ReactionCollector extends Collector<
Snowflake | string,
MessageReaction,
[User],
ReactionCollectorEventTypes
> {
public constructor(message: Message, options?: ReactionCollectorOptions); public constructor(message: Message, options?: ReactionCollectorOptions);
private _handleChannelDeletion(channel: NonThreadGuildBasedChannel): void; private _handleChannelDeletion(channel: NonThreadGuildBasedChannel): void;
private _handleGuildDeletion(guild: Guild): void; private _handleGuildDeletion(guild: Guild): void;
@@ -2783,26 +2734,6 @@ export class ReactionCollector extends Collector<Snowflake | string, MessageReac
public collect(reaction: MessageReaction, user: User): Snowflake | string | null; public collect(reaction: MessageReaction, user: User): Snowflake | string | null;
public dispose(reaction: MessageReaction, user: User): Snowflake | string | null; public dispose(reaction: MessageReaction, user: User): Snowflake | string | null;
public empty(): void; public empty(): void;
public on(
event: 'collect' | 'dispose' | 'remove' | 'ignore',
listener: (reaction: MessageReaction, user: User) => void,
): this;
public on(
event: 'end',
listener: (collected: ReadonlyCollection<Snowflake, MessageReaction>, reason: string) => void,
): this;
public on(event: string, listener: (...args: any[]) => void): this;
public once(
event: 'collect' | 'dispose' | 'remove' | 'ignore',
listener: (reaction: MessageReaction, user: User) => void,
): this;
public once(
event: 'end',
listener: (collected: ReadonlyCollection<Snowflake, MessageReaction>, reason: string) => void,
): this;
public once(event: string, listener: (...args: any[]) => void): this;
} }
export class ReactionEmoji extends Emoji { export class ReactionEmoji extends Emoji {
@@ -2997,15 +2928,15 @@ export interface ShardEventTypes {
spawn: [process: ChildProcess | Worker]; spawn: [process: ChildProcess | Worker];
} }
export class Shard extends EventEmitter { export class Shard extends AsyncEventEmitter<ShardEventTypes> {
private constructor(manager: ShardingManager, id: number); private constructor(manager: ShardingManager, id: number);
private _evals: Map<string, Promise<unknown>>; private _evals: Map<string, Promise<unknown>>;
private _exitListener: (...args: any[]) => void; private _exitListener: (...args: any[]) => void;
private _fetches: Map<string, Promise<unknown>>; private _fetches: Map<string, Promise<unknown>>;
private _handleExit(respawn?: boolean, timeout?: number): void; private _handleExit(respawn?: boolean, timeout?: number): void;
private _handleMessage(message: unknown): void; private _handleMessage(message: unknown): void;
private incrementMaxListeners(emitter: EventEmitter | ChildProcess): void; private incrementMaxListeners(emitter: Worker | ChildProcess): void;
private decrementMaxListeners(emitter: EventEmitter | ChildProcess): void; private decrementMaxListeners(emitter: Worker | ChildProcess): void;
public args: string[]; public args: string[];
public execArgv: string[]; public execArgv: string[];
@@ -3027,24 +2958,14 @@ export class Shard extends EventEmitter {
public respawn(options?: { delay?: number; timeout?: number }): Promise<ChildProcess>; public respawn(options?: { delay?: number; timeout?: number }): Promise<ChildProcess>;
public send(message: unknown): Promise<Shard>; public send(message: unknown): Promise<Shard>;
public spawn(timeout?: number): Promise<ChildProcess>; public spawn(timeout?: number): Promise<ChildProcess>;
public on<Event extends keyof ShardEventTypes>(
event: Event,
listener: (...args: ShardEventTypes[Event]) => void,
): this;
public once<Event extends keyof ShardEventTypes>(
event: Event,
listener: (...args: ShardEventTypes[Event]) => void,
): this;
} }
export class ShardClientUtil { export class ShardClientUtil {
private constructor(client: Client<true>, mode: ShardingManagerMode); private constructor(client: Client<true>, mode: ShardingManagerMode);
private _handleMessage(message: unknown): void; private _handleMessage(message: unknown): void;
private _respond(type: string, message: unknown): void; private _respond(type: string, message: unknown): void;
private incrementMaxListeners(emitter: EventEmitter | ChildProcess): void; private incrementMaxListeners(emitter: Worker | ChildProcess): void;
private decrementMaxListeners(emitter: EventEmitter | ChildProcess): void; private decrementMaxListeners(emitter: Worker | ChildProcess): void;
public client: Client; public client: Client;
public get count(): number; public get count(): number;
@@ -3073,7 +2994,11 @@ export class ShardClientUtil {
public static shardIdForGuildId(guildId: Snowflake, shardCount: number): number; public static shardIdForGuildId(guildId: Snowflake, shardCount: number): number;
} }
export class ShardingManager extends EventEmitter { export interface ShardingManagerEventTypes {
shardCreate: [shard: Shard];
}
export class ShardingManager extends AsyncEventEmitter<ShardingManagerEventTypes> {
public constructor(file: string, options?: ShardingManagerOptions); public constructor(file: string, options?: ShardingManagerOptions);
private _performOnShards(method: string, args: readonly unknown[]): Promise<unknown[]>; private _performOnShards(method: string, args: readonly unknown[]): Promise<unknown[]>;
private _performOnShards(method: string, args: readonly unknown[], shard: number): Promise<unknown>; private _performOnShards(method: string, args: readonly unknown[], shard: number): Promise<unknown>;
@@ -3105,10 +3030,6 @@ export class ShardingManager extends EventEmitter {
public fetchClientValues(prop: string, shard: number): Promise<unknown>; public fetchClientValues(prop: string, shard: number): Promise<unknown>;
public respawnAll(options?: MultipleShardRespawnOptions): Promise<Collection<number, Shard>>; public respawnAll(options?: MultipleShardRespawnOptions): Promise<Collection<number, Shard>>;
public spawn(options?: MultipleShardSpawnOptions): Promise<Collection<number, Shard>>; public spawn(options?: MultipleShardSpawnOptions): Promise<Collection<number, Shard>>;
public on(event: 'shardCreate', listener: (shard: Shard) => void): this;
public once(event: 'shardCreate', listener: (shard: Shard) => void): this;
} }
export interface FetchRecommendedShardCountOptions { export interface FetchRecommendedShardCountOptions {
@@ -3692,8 +3613,8 @@ export class Webhook<Type extends WebhookType = WebhookType> {
} }
// tslint:disable-next-line no-empty-interface // tslint:disable-next-line no-empty-interface
export interface WebhookClient extends WebhookFields, BaseClient {} export interface WebhookClient extends WebhookFields, BaseClient<{}> {}
export class WebhookClient extends BaseClient { export class WebhookClient extends BaseClient<{}> {
public constructor(data: WebhookClientData, options?: WebhookClientOptions); public constructor(data: WebhookClientData, options?: WebhookClientOptions);
public readonly client: this; public readonly client: this;
public options: WebhookClientOptions; public options: WebhookClientOptions;
@@ -5158,7 +5079,7 @@ export type OmitPartialGroupDMChannel<Structure extends { channel: Channel }> =
channel: Exclude<Structure['channel'], PartialGroupDMChannel>; channel: Exclude<Structure['channel'], PartialGroupDMChannel>;
}; };
export interface ClientEvents { export interface ClientEventTypes {
applicationCommandPermissionsUpdate: [data: ApplicationCommandPermissionsUpdateData]; applicationCommandPermissionsUpdate: [data: ApplicationCommandPermissionsUpdateData];
autoModerationActionExecution: [autoModerationActionExecution: AutoModerationActionExecution]; autoModerationActionExecution: [autoModerationActionExecution: AutoModerationActionExecution];
autoModerationRuleCreate: [autoModerationRule: AutoModerationRule]; autoModerationRuleCreate: [autoModerationRule: AutoModerationRule];
@@ -6172,7 +6093,7 @@ export type CollectedInteraction<Cached extends CacheType = CacheType> =
export interface InteractionCollectorOptions< export interface InteractionCollectorOptions<
Interaction extends CollectedInteraction, Interaction extends CollectedInteraction,
Cached extends CacheType = CacheType, Cached extends CacheType = CacheType,
> extends CollectorOptions<[Interaction, Collection<Snowflake, Interaction>]> { > extends CollectorOptions<[Interaction]> {
channel?: TextBasedChannelResolvable; channel?: TextBasedChannelResolvable;
componentType?: ComponentType; componentType?: ComponentType;
guild?: GuildResolvable; guild?: GuildResolvable;

View File

@@ -473,6 +473,10 @@ client.on('messageCreate', async message => {
expectAssignable<Promise<ButtonInteraction>>(channel.awaitMessageComponent({ componentType: ComponentType.Button })); expectAssignable<Promise<ButtonInteraction>>(channel.awaitMessageComponent({ componentType: ComponentType.Button }));
expectAssignable<InteractionCollector<ButtonInteraction>>(buttonCollector); expectAssignable<InteractionCollector<ButtonInteraction>>(buttonCollector);
buttonCollector.on('collect', (...args) => expectType<[ButtonInteraction]>(args));
buttonCollector.on('dispose', (...args) => expectType<[ButtonInteraction]>(args));
buttonCollector.on('end', (...args) => expectType<[ReadonlyCollection<Snowflake, ButtonInteraction>, string]>(args));
// Verify that select menus interaction are inferred. // Verify that select menus interaction are inferred.
const selectMenuCollector = message.createMessageComponentCollector({ componentType: ComponentType.StringSelect }); const selectMenuCollector = message.createMessageComponentCollector({ componentType: ComponentType.StringSelect });
expectAssignable<Promise<StringSelectMenuInteraction>>( expectAssignable<Promise<StringSelectMenuInteraction>>(
@@ -483,12 +487,24 @@ client.on('messageCreate', async message => {
); );
expectAssignable<InteractionCollector<StringSelectMenuInteraction>>(selectMenuCollector); expectAssignable<InteractionCollector<StringSelectMenuInteraction>>(selectMenuCollector);
selectMenuCollector.on('collect', (...args) => expectType<[SelectMenuInteraction]>(args));
selectMenuCollector.on('dispose', (...args) => expectType<[SelectMenuInteraction]>(args));
selectMenuCollector.on('end', (...args) =>
expectType<[ReadonlyCollection<Snowflake, SelectMenuInteraction>, string]>(args),
);
// Verify that message component interactions are default collected types. // Verify that message component interactions are default collected types.
const defaultCollector = message.createMessageComponentCollector(); const defaultCollector = message.createMessageComponentCollector();
expectAssignable<Promise<MessageComponentInteraction>>(message.awaitMessageComponent()); expectAssignable<Promise<MessageComponentInteraction>>(message.awaitMessageComponent());
expectAssignable<Promise<MessageComponentInteraction>>(channel.awaitMessageComponent()); expectAssignable<Promise<MessageComponentInteraction>>(channel.awaitMessageComponent());
expectAssignable<InteractionCollector<CollectedMessageInteraction>>(defaultCollector); expectAssignable<InteractionCollector<CollectedMessageInteraction>>(defaultCollector);
defaultCollector.on('collect', (...args) => expectType<[MessageComponentInteraction]>(args));
defaultCollector.on('dispose', (...args) => expectType<[MessageComponentInteraction]>(args));
defaultCollector.on('end', (...args) =>
expectType<[ReadonlyCollection<Snowflake, MessageComponentInteraction>, string]>(args),
);
// Verify that additional options don't affect default collector types. // Verify that additional options don't affect default collector types.
const semiDefaultCollector = message.createMessageComponentCollector({ time: 10000 }); const semiDefaultCollector = message.createMessageComponentCollector({ time: 10000 });
expectType<InteractionCollector<CollectedMessageInteraction>>(semiDefaultCollector); expectType<InteractionCollector<CollectedMessageInteraction>>(semiDefaultCollector);
@@ -1306,9 +1322,9 @@ client.on('guildCreate', async g => {
); );
}); });
// EventEmitter static method overrides // Event emitter static method overrides
expectType<Promise<[Client<true>]>>(Client.once(client, 'clientReady')); expectType<Promise<[Client<true>]>>(Client.once(client, 'clientReady'));
expectType<AsyncIterableIterator<[Client<true>]>>(Client.on(client, 'clientReady')); expectAssignable<AsyncIterableIterator<[Client<true>]>>(Client.on(client, 'clientReady'));
client.login('absolutely-valid-token'); client.login('absolutely-valid-token');
@@ -1419,6 +1435,11 @@ reactionCollector.on('dispose', (...args) => {
expectType<[MessageReaction, User]>(args); expectType<[MessageReaction, User]>(args);
}); });
reactionCollector.on('collect', (...args) => expectType<[MessageReaction, User]>(args));
reactionCollector.on('dispose', (...args) => expectType<[MessageReaction, User]>(args));
reactionCollector.on('remove', (...args) => expectType<[MessageReaction, User]>(args));
reactionCollector.on('end', (...args) => expectType<[ReadonlyCollection<string, MessageReaction>, string]>(args));
(async () => { (async () => {
for await (const value of reactionCollector) { for await (const value of reactionCollector) {
expectType<[MessageReaction, User]>(value); expectType<[MessageReaction, User]>(value);

3
pnpm-lock.yaml generated
View File

@@ -940,6 +940,9 @@ importers:
'@sapphire/snowflake': '@sapphire/snowflake':
specifier: 3.5.5 specifier: 3.5.5
version: 3.5.5 version: 3.5.5
'@vladfrangu/async_event_emitter':
specifier: ^2.4.6
version: 2.4.6
discord-api-types: discord-api-types:
specifier: ^0.37.114 specifier: ^0.37.114
version: 0.37.114 version: 0.37.114