Skip to content
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
'use strict';

const process = require('node:process');
const { GatewayOpcodes } = require('discord-api-types/v10');

const emittedFor = new Set();

module.exports = (client, { d: data }) => {
switch (data.opcode) {
case GatewayOpcodes.RequestGuildMembers: {
break;
}

default: {
if (!emittedFor.has(data.opcode)) {
process.emitWarning(
`Hit a gateway rate limit on opcode ${data.opcode} (${GatewayOpcodes[data.opcode]}). If the discord.js version you're using is up-to-date, please open an issue on GitHub.`,
);

emittedFor.add(data.opcode);
}
}
}
};
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ const PacketHandlers = Object.fromEntries([
['MESSAGE_REACTION_REMOVE_EMOJI', require('./MESSAGE_REACTION_REMOVE_EMOJI.js')],
['MESSAGE_UPDATE', require('./MESSAGE_UPDATE.js')],
['PRESENCE_UPDATE', require('./PRESENCE_UPDATE.js')],
['RATE_LIMITED', require('./RATE_LIMITED.js')],
['READY', require('./READY.js')],
['SOUNDBOARD_SOUNDS', require('./SOUNDBOARD_SOUNDS.js')],
['STAGE_INSTANCE_CREATE', require('./STAGE_INSTANCE_CREATE.js')],
Expand Down
67 changes: 44 additions & 23 deletions packages/discord.js/src/managers/GuildMemberManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@
const { setTimeout, clearTimeout } = require('node:timers');
const { Collection } = require('@discordjs/collection');
const { makeURLSearchParams } = require('@discordjs/rest');
const { GatewayRateLimitError } = require('@discordjs/util');
const { WebSocketShardEvents } = require('@discordjs/ws');
const { DiscordSnowflake } = require('@sapphire/snowflake');
const { Routes, GatewayOpcodes } = require('discord-api-types/v10');
const { Routes, GatewayOpcodes, GatewayDispatchEvents } = require('discord-api-types/v10');
const { DiscordjsError, DiscordjsTypeError, DiscordjsRangeError, ErrorCodes } = require('../errors/index.js');
const { BaseGuildVoiceChannel } = require('../structures/BaseGuildVoiceChannel.js');
const { GuildMember } = require('../structures/GuildMember.js');
Expand Down Expand Up @@ -246,46 +248,65 @@ class GuildMemberManager extends CachedManager {
const query = initialQuery ?? (users ? undefined : '');

return new Promise((resolve, reject) => {
this.guild.client.ws.send(this.guild.shardId, {
op: GatewayOpcodes.RequestGuildMembers,
// eslint-disable-next-line id-length
d: {
guild_id: this.guild.id,
presences,
user_ids: users,
query,
nonce,
limit,
},
});
const fetchedMembers = new Collection();
let index = 0;

const cleanup = () => {
/* eslint-disable no-use-before-define */
clearTimeout(timeout);

this.client.ws.removeListener(WebSocketShardEvents.Dispatch, rateLimitHandler);
this.client.removeListener(Events.GuildMembersChunk, handler);
this.client.decrementMaxListeners();
/* eslint-enable no-use-before-define */
};

const timeout = setTimeout(() => {
cleanup();
reject(new DiscordjsError(ErrorCodes.GuildMembersTimeout));
}, time).unref();

const handler = (members, _, chunk) => {
if (chunk.nonce !== nonce) return;

// eslint-disable-next-line no-use-before-define
timeout.refresh();
index++;
for (const member of members.values()) {
fetchedMembers.set(member.id, member);
}

if (members.size < 1_000 || (limit && fetchedMembers.size >= limit) || index === chunk.count) {
// eslint-disable-next-line no-use-before-define
clearTimeout(timeout);
this.client.removeListener(Events.GuildMembersChunk, handler);
this.client.decrementMaxListeners();
cleanup();
resolve(users && !Array.isArray(users) && fetchedMembers.size ? fetchedMembers.first() : fetchedMembers);
}
};

const timeout = setTimeout(() => {
this.client.removeListener(Events.GuildMembersChunk, handler);
this.client.decrementMaxListeners();
reject(new DiscordjsError(ErrorCodes.GuildMembersTimeout));
}, time).unref();
const requestData = {
guild_id: this.guild.id,
presences,
user_ids: users,
query,
nonce,
limit,
};

const rateLimitHandler = payload => {
if (payload.t === GatewayDispatchEvents.RateLimited && payload.d.meta.nonce === nonce) {
cleanup();
reject(new GatewayRateLimitError(payload.d, requestData));
}
};

this.client.ws.on(WebSocketShardEvents.Dispatch, rateLimitHandler);

this.client.incrementMaxListeners();
this.client.on(Events.GuildMembersChunk, handler);

this.guild.client.ws.send(this.guild.shardId, {
op: GatewayOpcodes.RequestGuildMembers,
// eslint-disable-next-line id-length
d: requestData,
});
});
}

Expand Down