feat(core): handle request all guild members rate limit (#11251)

* feat(core): handle request all guild members rate limit

BREAKING CHANGE: `Gateway` now requires `off` `once` methods

* fix: weird import update

* refactor: error class

* refactor: error class again

* refactor: requested changes

* chore: fix dep

* fix: suggested changes

---------

Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
This commit is contained in:
Denis-Adrian Cristea
2025-11-11 09:14:57 +02:00
committed by GitHub
parent 37a38883d7
commit d3d6777ef8
6 changed files with 60 additions and 4 deletions

View File

@@ -1,6 +1,6 @@
import { clearTimeout, setTimeout } from 'node:timers';
import type { REST } from '@discordjs/rest';
import { calculateShardId } from '@discordjs/util';
import { calculateShardId, GatewayRateLimitError } from '@discordjs/util';
import { WebSocketShardEvents } from '@discordjs/ws';
import { DiscordSnowflake } from '@sapphire/snowflake';
import { AsyncEventEmitter } from '@vladfrangu/async_event_emitter';
@@ -61,6 +61,7 @@ import {
type GatewayMessageUpdateDispatchData,
type GatewayPresenceUpdateData,
type GatewayPresenceUpdateDispatchData,
type GatewayRateLimitedDispatchData,
type GatewayReadyDispatchData,
type GatewayRequestGuildMembersData,
type GatewayStageInstanceCreateDispatchData,
@@ -164,6 +165,7 @@ export interface MappedEvents {
[GatewayDispatchEvents.MessageReactionRemoveEmoji]: [ToEventProps<GatewayMessageReactionRemoveEmojiDispatchData>];
[GatewayDispatchEvents.MessageUpdate]: [ToEventProps<GatewayMessageUpdateDispatchData>];
[GatewayDispatchEvents.PresenceUpdate]: [ToEventProps<GatewayPresenceUpdateDispatchData>];
[GatewayDispatchEvents.RateLimited]: [ToEventProps<GatewayRateLimitedDispatchData>];
[GatewayDispatchEvents.Ready]: [ToEventProps<GatewayReadyDispatchData>];
[GatewayDispatchEvents.Resumed]: [ToEventProps<never>];
[GatewayDispatchEvents.StageInstanceCreate]: [ToEventProps<GatewayStageInstanceCreateDispatchData>];
@@ -244,6 +246,22 @@ export class Client extends AsyncEventEmitter<MappedEvents> {
let timer: NodeJS.Timeout | undefined = createTimer(controller, timeout);
const onRatelimit = ({ data }: ToEventProps<GatewayRateLimitedDispatchData>) => {
// We could verify meta.guild_id === options.guild_id as well, but really, the nonce check is enough
if (data.meta.nonce === nonce) {
controller.abort(new GatewayRateLimitError(data, options));
}
};
const cleanup = () => {
if (timer) {
clearTimeout(timer);
}
this.off(GatewayDispatchEvents.RateLimited, onRatelimit);
};
this.on(GatewayDispatchEvents.RateLimited, onRatelimit);
await this.gateway.send(shardId, {
op: GatewayOpcodes.RequestGuildMembers,
// eslint-disable-next-line id-length
@@ -275,18 +293,21 @@ export class Client extends AsyncEventEmitter<MappedEvents> {
if (data.chunk_index >= data.chunk_count - 1) break;
// eslint-disable-next-line require-atomic-updates
timer = createTimer(controller, timeout);
}
} catch (error) {
if (error instanceof Error && error.name === 'AbortError') {
if (error.cause instanceof GatewayRateLimitError) {
throw error.cause;
}
throw new Error('Request timed out');
}
throw error;
} finally {
if (timer) {
clearTimeout(timer);
}
cleanup();
}
}

View File

@@ -5,6 +5,8 @@ export * from './util/index.js';
export * from 'discord-api-types/v10';
export { GatewayRateLimitError } from '@discordjs/util';
/**
* The {@link https://github.com/discordjs/discord.js/blob/main/packages/core#readme | @discordjs/core} version
* that you are currently using.