mirror of
https://github.com/discordjs/discord.js.git
synced 2026-03-09 16:13:31 +01:00
feat(core): handle request all guild members rate limit (#11251)
* feat(core): handle request all guild members rate limit * 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:
committed by
Jiralite
parent
eeeef2ac50
commit
5c5b545c38
@@ -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';
|
||||
@@ -57,6 +57,7 @@ import {
|
||||
type GatewayMessageUpdateDispatchData,
|
||||
type GatewayPresenceUpdateData,
|
||||
type GatewayPresenceUpdateDispatchData,
|
||||
type GatewayRateLimitedDispatchData,
|
||||
type GatewayReadyDispatchData,
|
||||
type GatewayRequestGuildMembersData,
|
||||
type GatewayStageInstanceCreateDispatchData,
|
||||
@@ -150,6 +151,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>];
|
||||
@@ -182,6 +184,10 @@ export interface RequestGuildMembersResult {
|
||||
presences: NonNullable<GatewayGuildMembersChunkDispatchData['presences']>;
|
||||
}
|
||||
|
||||
function createTimer(controller: AbortController, timeout: number) {
|
||||
return setTimeout(() => controller.abort(), timeout);
|
||||
}
|
||||
|
||||
export class Client extends AsyncEventEmitter<MappedEvents> {
|
||||
public readonly rest: REST;
|
||||
|
||||
@@ -220,13 +226,24 @@ export class Client extends AsyncEventEmitter<MappedEvents> {
|
||||
|
||||
const controller = new AbortController();
|
||||
|
||||
const createTimer = () =>
|
||||
setTimeout(() => {
|
||||
controller.abort();
|
||||
}, timeout);
|
||||
let timer: NodeJS.Timeout | undefined = createTimer(controller, timeout);
|
||||
|
||||
let timer: NodeJS.Timeout | undefined = createTimer();
|
||||
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
|
||||
@@ -256,22 +273,23 @@ export class Client extends AsyncEventEmitter<MappedEvents> {
|
||||
chunkCount: data.chunk_count,
|
||||
};
|
||||
|
||||
if (data.chunk_index >= data.chunk_count - 1) {
|
||||
break;
|
||||
} else {
|
||||
timer = createTimer();
|
||||
}
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -61,6 +61,9 @@
|
||||
},
|
||||
"homepage": "https://discord.js.org",
|
||||
"funding": "https://github.com/discordjs/discord.js?sponsor",
|
||||
"dependencies": {
|
||||
"discord-api-types": "^0.38.33"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@discordjs/api-extractor": "workspace:^",
|
||||
"@discordjs/scripts": "workspace:^",
|
||||
|
||||
25
packages/util/src/gatewayRateLimitError.ts
Normal file
25
packages/util/src/gatewayRateLimitError.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import type { GatewayOpcodeRateLimitMetadataMap, GatewayRateLimitedDispatchData } from 'discord-api-types/v10';
|
||||
|
||||
/**
|
||||
* Represents the error thrown when the gateway emits a `RATE_LIMITED` event after a certain request.
|
||||
*/
|
||||
export class GatewayRateLimitError extends Error {
|
||||
public override readonly name = GatewayRateLimitError.name;
|
||||
|
||||
public constructor(
|
||||
/**
|
||||
* The data associated with the rate limit event
|
||||
*/
|
||||
public readonly data: GatewayRateLimitedDispatchData<keyof GatewayOpcodeRateLimitMetadataMap>,
|
||||
/**
|
||||
* The payload data that lead to this rate limit
|
||||
*
|
||||
* @privateRemarks
|
||||
* Too complicated to type properly here (i.e. extract the ['data']
|
||||
* of event payloads that have t = keyof GatewayOpcodeRateLimitMetadataMap)
|
||||
*/
|
||||
public readonly payload: unknown,
|
||||
) {
|
||||
super(`Request with opcode ${data.opcode} was rate limited. Retry after ${data.retry_after} seconds.`);
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ export * from './types.js';
|
||||
export * from './functions/index.js';
|
||||
export * from './JSONEncodable.js';
|
||||
export * from './Equatable.js';
|
||||
export * from './gatewayRateLimitError.js';
|
||||
|
||||
/**
|
||||
* The {@link https://github.com/discordjs/discord.js/blob/main/packages/util#readme | @discordjs/util} version
|
||||
|
||||
4
pnpm-lock.yaml
generated
4
pnpm-lock.yaml
generated
@@ -1270,6 +1270,10 @@ importers:
|
||||
version: 2.1.9(@edge-runtime/vm@3.2.0)(@types/node@18.19.130)(terser@5.44.1)
|
||||
|
||||
packages/util:
|
||||
dependencies:
|
||||
discord-api-types:
|
||||
specifier: ^0.38.33
|
||||
version: 0.38.33
|
||||
devDependencies:
|
||||
'@discordjs/api-extractor':
|
||||
specifier: workspace:^
|
||||
|
||||
Reference in New Issue
Block a user