From 765d5a3b2d5529c3a2a4b29512f6932264443ed1 Mon Sep 17 00:00:00 2001 From: Jiralite <33201955+Jiralite@users.noreply.github.com> Date: Fri, 17 Feb 2023 22:14:36 +0000 Subject: [PATCH] feat(ThreadMemberManager): Support pagination fetching (#9035) * feat: initial commit * fix: `query` * fix: `GuildMember` for `member` * types(ThreadMember): constructor types * fix: send API properties correctly Co-authored-by: Almeida * docs(FetchThreadMembersOptions): update `cache` description * fix: retrieve from option Co-authored-by: Almeida * fix: fix fetch many * types(FetchThreadMembersOptions): add `withMember` * types: stricter types * types: infer possible guild member * chore: reference member in getter * types: remove `` * style: remove line * chore: markdown * types: remove `?` * docs: remove irrelevant part * fix: prevent crash * refactor: make `member` `@private` It will be confusing to have `guildMember` and `member`, specially since the former accounts for the latter. --------- Co-authored-by: Almeida Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- .../src/managers/ThreadMemberManager.js | 47 +++++++++++++++---- .../discord.js/src/structures/ThreadMember.js | 19 ++++++-- packages/discord.js/typings/index.d.ts | 34 ++++++++++++-- packages/discord.js/typings/index.test-d.ts | 16 ++++++- 4 files changed, 95 insertions(+), 21 deletions(-) diff --git a/packages/discord.js/src/managers/ThreadMemberManager.js b/packages/discord.js/src/managers/ThreadMemberManager.js index a756bdd1ac47..c138aa32854b 100644 --- a/packages/discord.js/src/managers/ThreadMemberManager.js +++ b/packages/discord.js/src/managers/ThreadMemberManager.js @@ -1,6 +1,7 @@ 'use strict'; const { Collection } = require('@discordjs/collection'); +const { makeURLSearchParams } = require('@discordjs/rest'); const { Routes } = require('discord-api-types/v10'); const CachedManager = require('./CachedManager'); const { DiscordjsTypeError, ErrorCodes } = require('../errors'); @@ -29,10 +30,10 @@ class ThreadMemberManager extends CachedManager { _add(data, cache = true) { const existing = this.cache.get(data.user_id); - if (cache) existing?._patch(data); + if (cache) existing?._patch(data, { cache }); if (existing) return existing; - const member = new ThreadMember(this.thread, data); + const member = new ThreadMember(this.thread, data, { cache }); if (cache) this.cache.set(data.user_id, member); return member; } @@ -112,15 +113,35 @@ class ThreadMemberManager extends CachedManager { } /** + * Options used to fetch a thread member. * @typedef {BaseFetchOptions} FetchThreadMemberOptions * @property {ThreadMemberResolvable} member The thread member to fetch + * @property {boolean} [withMember] Whether to also return the guild member associated with this thread member */ /** - * @typedef {Object} FetchThreadMembersOptions + * Options used to fetch multiple thread members with guild member data. + * With `withMember` set to `true`, pagination is enabled. + * @typedef {Object} FetchThreadMembersWithGuildMemberDataOptions + * @property {true} withMember Whether to also return the guild member data + * @property {Snowflake} [after] Consider only thread members after this id + * @property {number} [limit] The maximum number of thread members to return + * @property {boolean} [cache] Whether to cache the fetched thread members and guild members + */ + + /** + * Options used to fetch multiple thread members without guild member data. + * @typedef {Object} FetchThreadMembersWithoutGuildMemberDataOptions + * @property {false} [withMember] Whether to also return the guild member data * @property {boolean} [cache] Whether to cache the fetched thread members */ + /** + * Options used to fetch multiple thread members. + * @typedef {FetchThreadMembersWithGuildMemberDataOptions| + * FetchThreadMembersWithoutGuildMemberDataOptions} FetchThreadMembersOptions + */ + /** * Fetches thread member(s) from Discord. * This method requires the {@link GatewayIntentBits.GuildMembers} privileged gateway intent. @@ -130,25 +151,31 @@ class ThreadMemberManager extends CachedManager { */ fetch(options) { if (!options) return this._fetchMany(); - const { member, cache, force } = options; + const { member, withMember, cache, force } = options; const resolvedMember = this.resolveId(member ?? options); - if (resolvedMember) return this._fetchSingle({ member: resolvedMember, cache, force }); + if (resolvedMember) return this._fetchSingle({ member: resolvedMember, withMember, cache, force }); return this._fetchMany(options); } - async _fetchSingle({ member, cache, force = false }) { + async _fetchSingle({ member, withMember, cache, force = false }) { if (!force) { const existing = this.cache.get(member); if (existing) return existing; } - const data = await this.client.rest.get(Routes.threadMembers(this.thread.id, member)); + const data = await this.client.rest.get(Routes.threadMembers(this.thread.id, member), { + query: makeURLSearchParams({ with_member: withMember }), + }); + return this._add(data, cache); } - async _fetchMany(options = {}) { - const data = await this.client.rest.get(Routes.threadMembers(this.thread.id)); - return data.reduce((col, member) => col.set(member.user_id, this._add(member, options.cache)), new Collection()); + async _fetchMany({ withMember, after, limit, cache } = {}) { + const data = await this.client.rest.get(Routes.threadMembers(this.thread.id), { + query: makeURLSearchParams({ with_member: withMember, after, limit }), + }); + + return data.reduce((col, member) => col.set(member.user_id, this._add(member, cache)), new Collection()); } } diff --git a/packages/discord.js/src/structures/ThreadMember.js b/packages/discord.js/src/structures/ThreadMember.js index 2b6743a93554..fc79dd0916f7 100644 --- a/packages/discord.js/src/structures/ThreadMember.js +++ b/packages/discord.js/src/structures/ThreadMember.js @@ -8,7 +8,7 @@ const ThreadMemberFlagsBitField = require('../util/ThreadMemberFlagsBitField'); * @extends {Base} */ class ThreadMember extends Base { - constructor(thread, data) { + constructor(thread, data, extra = {}) { super(thread.client); /** @@ -35,12 +35,23 @@ class ThreadMember extends Base { */ this.id = data.user_id; - this._patch(data); + this._patch(data, extra); } - _patch(data) { + _patch(data, extra = {}) { if ('join_timestamp' in data) this.joinedTimestamp = Date.parse(data.join_timestamp); if ('flags' in data) this.flags = new ThreadMemberFlagsBitField(data.flags).freeze(); + + if ('member' in data) { + /** + * The guild member associated with this thread member. + * @type {?GuildMember} + * @private + */ + this.member = this.thread.guild.members._add(data.member, extra.cache); + } else { + this.member ??= null; + } } /** @@ -58,7 +69,7 @@ class ThreadMember extends Base { * @readonly */ get guildMember() { - return this.thread.guild.members.resolve(this.id); + return this.member ?? this.thread.guild.members.resolve(this.id); } /** diff --git a/packages/discord.js/typings/index.d.ts b/packages/discord.js/typings/index.d.ts index 10a272f3c610..58584e28404d 100644 --- a/packages/discord.js/typings/index.d.ts +++ b/packages/discord.js/typings/index.d.ts @@ -2981,10 +2981,11 @@ export class ThreadChannel extends TextBasedCha public toString(): ChannelMention; } -export class ThreadMember extends Base { - private constructor(thread: ThreadChannel, data?: RawThreadMemberData); +export class ThreadMember extends Base { + private constructor(thread: ThreadChannel, data: RawThreadMemberData, extra?: unknown); public flags: ThreadMemberFlagsBitField; - public get guildMember(): GuildMember | null; + private member: If; + public get guildMember(): HasMemberData extends true ? GuildMember : GuildMember | null; public id: Snowflake; public get joinedAt(): Date | null; public joinedTimestamp: number | null; @@ -4097,8 +4098,18 @@ export class ThreadMemberManager extends CachedManager; + + public fetch( + options: ThreadMember | ((FetchThreadMemberOptions & { withMember: true }) | { member: ThreadMember }), + ): Promise>; + public fetch(options: ThreadMemberResolvable | FetchThreadMemberOptions): Promise; - public fetch(options?: FetchThreadMembersOptions): Promise>; + + public fetch( + options: FetchThreadMembersWithGuildMemberDataOptions, + ): Promise>>; + + public fetch(options?: FetchThreadMembersWithoutGuildMemberDataOptions): Promise>; public fetchMe(options?: BaseFetchOptions): Promise; public remove(id: Snowflake | '@me', reason?: string): Promise; } @@ -5195,12 +5206,25 @@ export interface FetchReactionUsersOptions { export interface FetchThreadMemberOptions extends BaseFetchOptions { member: ThreadMemberResolvable; + withMember?: boolean; } -export interface FetchThreadMembersOptions { +export interface FetchThreadMembersWithGuildMemberDataOptions { + withMember: true; + after?: Snowflake; + limit?: number; + cache?: boolean; +} + +export interface FetchThreadMembersWithoutGuildMemberDataOptions { + withMember?: false; cache?: boolean; } +export type FetchThreadMembersOptions = + | FetchThreadMembersWithGuildMemberDataOptions + | FetchThreadMembersWithoutGuildMemberDataOptions; + export interface FetchThreadsOptions { archived?: FetchArchivedThreadOptions; active?: boolean; diff --git a/packages/discord.js/typings/index.test-d.ts b/packages/discord.js/typings/index.test-d.ts index 0432fa6d8910..4283772568b9 100644 --- a/packages/discord.js/typings/index.test-d.ts +++ b/packages/discord.js/typings/index.test-d.ts @@ -1526,17 +1526,29 @@ declare const guildBanManager: GuildBanManager; guildBanManager.fetch({ user: '1234567890', after: '1234567890', cache: true, force: false }); } +declare const threadMemberWithGuildMember: ThreadMember; declare const threadMemberManager: ThreadMemberManager; { expectType>(threadMemberManager.fetch('12345678')); expectType>(threadMemberManager.fetch({ member: '12345678', cache: false })); expectType>(threadMemberManager.fetch({ member: '12345678', force: true })); - expectType>(threadMemberManager.fetch({ member: '12345678', cache: false, force: true })); + expectType>>(threadMemberManager.fetch({ member: threadMemberWithGuildMember })); + expectType>>(threadMemberManager.fetch({ member: '12345678901234567', withMember: true })); expectType>>(threadMemberManager.fetch()); expectType>>(threadMemberManager.fetch({})); - expectType>>(threadMemberManager.fetch({ cache: true })); + + expectType>>>( + threadMemberManager.fetch({ cache: true, limit: 50, withMember: true, after: '12345678901234567' }), + ); + + expectType>>( + threadMemberManager.fetch({ cache: true, withMember: false }), + ); + // @ts-expect-error The `force` option cannot be used alongside fetching all thread members. threadMemberManager.fetch({ cache: true, force: false }); + // @ts-expect-error `withMember` needs to be `true` to receive paginated results. + threadMemberManager.fetch({ withMember: false, limit: 5, after: '12345678901234567' }); } declare const typing: Typing;