mirror of
https://github.com/discordjs/discord.js.git
synced 2026-03-16 11:33:30 +01:00
refactor(GuildMemberManager): Tidy up fetching guild members (#8972)
* refactor(GuildMemberManager): tidy up fetching guild members * refactor: no destructure * fix: throw `nonce` error correctly * refactor: simplify `resolve()` with ternary * refactor: prioritise `nonce` check * fix: allow single user * refactor: do not use `null` This is not documented to request over the gateway. * refactor: better name * fix: extract correct property
This commit is contained in:
@@ -159,20 +159,18 @@ class GuildMemberManager extends CachedManager {
|
|||||||
/**
|
/**
|
||||||
* Options used to fetch multiple members from a guild.
|
* Options used to fetch multiple members from a guild.
|
||||||
* @typedef {Object} FetchMembersOptions
|
* @typedef {Object} FetchMembersOptions
|
||||||
* @property {UserResolvable|UserResolvable[]} user The user(s) to fetch
|
* @property {UserResolvable|UserResolvable[]} [user] The user(s) to fetch
|
||||||
* @property {?string} query Limit fetch to members with similar usernames
|
* @property {?string} [query] Limit fetch to members with similar usernames
|
||||||
* @property {number} [limit=0] Maximum number of members to request
|
* @property {number} [limit=0] Maximum number of members to request
|
||||||
* @property {boolean} [withPresences=false] Whether or not to include the presences
|
* @property {boolean} [withPresences=false] Whether to include the presences
|
||||||
* @property {number} [time=120e3] Timeout for receipt of members
|
* @property {number} [time=120e3] Timeout for receipt of members
|
||||||
* @property {?string} nonce Nonce for this request (32 characters max - default to base 16 now timestamp)
|
* @property {?string} [nonce] Nonce for this request (32 characters max - default to base 16 now timestamp)
|
||||||
* @property {boolean} [force=false] Whether to skip the cache check and request the API
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetches member(s) from Discord, even if they're offline.
|
* Fetches member(s) from a guild.
|
||||||
* @param {UserResolvable|FetchMemberOptions|FetchMembersOptions} [options] If a UserResolvable, the user to fetch.
|
* @param {UserResolvable|FetchMemberOptions|FetchMembersOptions} [options] Options for fetching member(s).
|
||||||
* If undefined, fetches all members.
|
* Omitting the parameter or providing `undefined` will fetch all members.
|
||||||
* If a query, it limits the results to users with similar usernames.
|
|
||||||
* @returns {Promise<GuildMember|Collection<Snowflake, GuildMember>>}
|
* @returns {Promise<GuildMember|Collection<Snowflake, GuildMember>>}
|
||||||
* @example
|
* @example
|
||||||
* // Fetch all members from a guild
|
* // Fetch all members from a guild
|
||||||
@@ -207,18 +205,70 @@ class GuildMemberManager extends CachedManager {
|
|||||||
*/
|
*/
|
||||||
fetch(options) {
|
fetch(options) {
|
||||||
if (!options) return this._fetchMany();
|
if (!options) return this._fetchMany();
|
||||||
const user = this.client.users.resolveId(options);
|
const { user: users, limit, withPresences, cache, force } = options;
|
||||||
if (user) return this._fetchSingle({ user, cache: true });
|
const resolvedUser = this.client.users.resolveId(users ?? options);
|
||||||
if (options.user) {
|
if (resolvedUser && !limit && !withPresences) return this._fetchSingle({ user: resolvedUser, cache, force });
|
||||||
if (Array.isArray(options.user)) {
|
const resolvedUsers = users?.map?.(user => this.client.users.resolveId(user)) ?? resolvedUser ?? undefined;
|
||||||
options.user = options.user.map(u => this.client.users.resolveId(u));
|
return this._fetchMany({ ...options, users: resolvedUsers });
|
||||||
return this._fetchMany(options);
|
}
|
||||||
} else {
|
|
||||||
options.user = this.client.users.resolveId(options.user);
|
async _fetchSingle({ user, cache, force = false }) {
|
||||||
}
|
if (!force) {
|
||||||
if (!options.limit && !options.withPresences) return this._fetchSingle(options);
|
const existing = this.cache.get(user);
|
||||||
|
if (existing && !existing.partial) return existing;
|
||||||
}
|
}
|
||||||
return this._fetchMany(options);
|
|
||||||
|
const data = await this.client.rest.get(Routes.guildMember(this.guild.id, user));
|
||||||
|
return this._add(data, cache);
|
||||||
|
}
|
||||||
|
|
||||||
|
_fetchMany({
|
||||||
|
limit = 0,
|
||||||
|
withPresences: presences,
|
||||||
|
users,
|
||||||
|
query,
|
||||||
|
time = 120e3,
|
||||||
|
nonce = DiscordSnowflake.generate().toString(),
|
||||||
|
} = {}) {
|
||||||
|
if (nonce.length > 32) return Promise.reject(new DiscordjsRangeError(ErrorCodes.MemberFetchNonceLength));
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
if (!query && !users) query = '';
|
||||||
|
this.guild.shard.send({
|
||||||
|
op: GatewayOpcodes.RequestGuildMembers,
|
||||||
|
d: {
|
||||||
|
guild_id: this.guild.id,
|
||||||
|
presences,
|
||||||
|
user_ids: users,
|
||||||
|
query,
|
||||||
|
nonce,
|
||||||
|
limit,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const fetchedMembers = new Collection();
|
||||||
|
let i = 0;
|
||||||
|
const handler = (members, _, chunk) => {
|
||||||
|
if (chunk.nonce !== nonce) return;
|
||||||
|
timeout.refresh();
|
||||||
|
i++;
|
||||||
|
for (const member of members.values()) {
|
||||||
|
fetchedMembers.set(member.id, member);
|
||||||
|
}
|
||||||
|
if (members.size < 1_000 || (limit && fetchedMembers.size >= limit) || i === chunk.count) {
|
||||||
|
clearTimeout(timeout);
|
||||||
|
this.client.removeListener(Events.GuildMembersChunk, handler);
|
||||||
|
this.client.decrementMaxListeners();
|
||||||
|
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();
|
||||||
|
this.client.incrementMaxListeners();
|
||||||
|
this.client.on(Events.GuildMembersChunk, handler);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -485,66 +535,6 @@ class GuildMemberManager extends CachedManager {
|
|||||||
|
|
||||||
return this.resolve(user) ?? this.client.users.resolve(user) ?? userId;
|
return this.resolve(user) ?? this.client.users.resolve(user) ?? userId;
|
||||||
}
|
}
|
||||||
|
|
||||||
async _fetchSingle({ user, cache, force = false }) {
|
|
||||||
if (!force) {
|
|
||||||
const existing = this.cache.get(user);
|
|
||||||
if (existing && !existing.partial) return existing;
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = await this.client.rest.get(Routes.guildMember(this.guild.id, user));
|
|
||||||
return this._add(data, cache);
|
|
||||||
}
|
|
||||||
|
|
||||||
_fetchMany({
|
|
||||||
limit = 0,
|
|
||||||
withPresences: presences = false,
|
|
||||||
user: user_ids,
|
|
||||||
query,
|
|
||||||
time = 120e3,
|
|
||||||
nonce = DiscordSnowflake.generate().toString(),
|
|
||||||
} = {}) {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
if (!query && !user_ids) query = '';
|
|
||||||
if (nonce.length > 32) throw new DiscordjsRangeError(ErrorCodes.MemberFetchNonceLength);
|
|
||||||
this.guild.shard.send({
|
|
||||||
op: GatewayOpcodes.RequestGuildMembers,
|
|
||||||
d: {
|
|
||||||
guild_id: this.guild.id,
|
|
||||||
presences,
|
|
||||||
user_ids,
|
|
||||||
query,
|
|
||||||
nonce,
|
|
||||||
limit,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
const fetchedMembers = new Collection();
|
|
||||||
let i = 0;
|
|
||||||
const handler = (members, _, chunk) => {
|
|
||||||
timeout.refresh();
|
|
||||||
if (chunk.nonce !== nonce) return;
|
|
||||||
i++;
|
|
||||||
for (const member of members.values()) {
|
|
||||||
fetchedMembers.set(member.id, member);
|
|
||||||
}
|
|
||||||
if (members.size < 1_000 || (limit && fetchedMembers.size >= limit) || i === chunk.count) {
|
|
||||||
clearTimeout(timeout);
|
|
||||||
this.client.removeListener(Events.GuildMembersChunk, handler);
|
|
||||||
this.client.decrementMaxListeners();
|
|
||||||
let fetched = fetchedMembers;
|
|
||||||
if (user_ids && !Array.isArray(user_ids) && fetched.size) fetched = fetched.first();
|
|
||||||
resolve(fetched);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const timeout = setTimeout(() => {
|
|
||||||
this.client.removeListener(Events.GuildMembersChunk, handler);
|
|
||||||
this.client.decrementMaxListeners();
|
|
||||||
reject(new DiscordjsError(ErrorCodes.GuildMembersTimeout));
|
|
||||||
}, time).unref();
|
|
||||||
this.client.incrementMaxListeners();
|
|
||||||
this.client.on(Events.GuildMembersChunk, handler);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = GuildMemberManager;
|
module.exports = GuildMemberManager;
|
||||||
|
|||||||
1
packages/discord.js/typings/index.d.ts
vendored
1
packages/discord.js/typings/index.d.ts
vendored
@@ -5167,7 +5167,6 @@ export interface FetchMembersOptions {
|
|||||||
withPresences?: boolean;
|
withPresences?: boolean;
|
||||||
time?: number;
|
time?: number;
|
||||||
nonce?: string;
|
nonce?: string;
|
||||||
force?: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface FetchMessageOptions extends BaseFetchOptions {
|
export interface FetchMessageOptions extends BaseFetchOptions {
|
||||||
|
|||||||
@@ -158,6 +158,7 @@ import {
|
|||||||
AutoModerationRuleManager,
|
AutoModerationRuleManager,
|
||||||
PrivateThreadChannel,
|
PrivateThreadChannel,
|
||||||
PublicThreadChannel,
|
PublicThreadChannel,
|
||||||
|
GuildMemberManager,
|
||||||
GuildMemberFlagsBitField,
|
GuildMemberFlagsBitField,
|
||||||
} from '.';
|
} from '.';
|
||||||
import { expectAssignable, expectNotAssignable, expectNotType, expectType } from 'tsd';
|
import { expectAssignable, expectNotAssignable, expectNotType, expectType } from 'tsd';
|
||||||
@@ -1486,6 +1487,28 @@ declare const guildTextThreadManager: GuildTextThreadManager<
|
|||||||
>;
|
>;
|
||||||
expectType<TextChannel | NewsChannel>(guildTextThreadManager.channel);
|
expectType<TextChannel | NewsChannel>(guildTextThreadManager.channel);
|
||||||
|
|
||||||
|
declare const guildMemberManager: GuildMemberManager;
|
||||||
|
{
|
||||||
|
expectType<Promise<GuildMember>>(guildMemberManager.fetch('12345678901234567'));
|
||||||
|
expectType<Promise<GuildMember>>(guildMemberManager.fetch({ user: '12345678901234567' }));
|
||||||
|
expectType<Promise<GuildMember>>(guildMemberManager.fetch({ user: '12345678901234567', cache: true, force: false }));
|
||||||
|
expectType<Promise<GuildMember>>(guildMemberManager.fetch({ user: '12345678901234567', cache: true, force: false }));
|
||||||
|
expectType<Promise<Collection<Snowflake, GuildMember>>>(guildMemberManager.fetch());
|
||||||
|
expectType<Promise<Collection<Snowflake, GuildMember>>>(guildMemberManager.fetch({}));
|
||||||
|
expectType<Promise<Collection<Snowflake, GuildMember>>>(guildMemberManager.fetch({ user: ['12345678901234567'] }));
|
||||||
|
expectType<Promise<Collection<Snowflake, GuildMember>>>(guildMemberManager.fetch({ withPresences: false }));
|
||||||
|
expectType<Promise<GuildMember>>(guildMemberManager.fetch({ user: '12345678901234567', withPresences: true }));
|
||||||
|
|
||||||
|
expectType<Promise<Collection<Snowflake, GuildMember>>>(
|
||||||
|
guildMemberManager.fetch({ query: 'test', user: ['12345678901234567'], nonce: 'test' }),
|
||||||
|
);
|
||||||
|
|
||||||
|
// @ts-expect-error The cache & force options have no effect here.
|
||||||
|
guildMemberManager.fetch({ cache: true, force: false });
|
||||||
|
// @ts-expect-error The force option has no effect here.
|
||||||
|
guildMemberManager.fetch({ user: ['12345678901234567'], cache: true, force: false });
|
||||||
|
}
|
||||||
|
|
||||||
declare const messageManager: MessageManager;
|
declare const messageManager: MessageManager;
|
||||||
{
|
{
|
||||||
expectType<Promise<Message>>(messageManager.fetch('1234567890'));
|
expectType<Promise<Message>>(messageManager.fetch('1234567890'));
|
||||||
|
|||||||
Reference in New Issue
Block a user