Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(ThreadMemberManager): Support pagination fetching #9035

Merged
merged 24 commits into from
Feb 17, 2023
Merged
Show file tree
Hide file tree
Changes from 23 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
ed9458c
feat: initial commit
Jiralite Jan 9, 2023
48dc043
fix: `query`
Jiralite Jan 9, 2023
ee1679b
fix: `GuildMember` for `member`
Jiralite Jan 9, 2023
f9153e3
types(ThreadMember): constructor types
Jiralite Jan 9, 2023
25c3aec
fix: send API properties correctly
Jiralite Jan 9, 2023
b71f4c5
docs(FetchThreadMembersOptions): update `cache` description
Jiralite Jan 9, 2023
f2f09aa
fix: retrieve from option
Jiralite Jan 9, 2023
0b258e0
Merge branch 'feat/thread-member-pagination' of https://github.com/Ji…
Jiralite Jan 9, 2023
e03f79f
fix: fix fetch many
Jiralite Jan 9, 2023
5e8c12d
types(FetchThreadMembersOptions): add `withMember`
Jiralite Jan 9, 2023
867c24f
types: stricter types
Jiralite Jan 9, 2023
aaacbbe
types: infer possible guild member
Jiralite Jan 9, 2023
a3560b9
chore: reference member in getter
Jiralite Jan 9, 2023
cda695f
types: remove `<false>`
Jiralite Jan 9, 2023
5fc1fa2
style: remove line
Jiralite Jan 9, 2023
7d5c327
chore: markdown
Jiralite Jan 9, 2023
e88f05c
types: remove `?`
Jiralite Jan 9, 2023
f1090ea
docs: remove irrelevant part
Jiralite Jan 9, 2023
978c25b
fix: prevent crash
Jiralite Jan 9, 2023
7d0629f
refactor: make `member` `@private`
Jiralite Jan 11, 2023
0c0973f
Merge branch 'main' into feat/thread-member-pagination
Jiralite Jan 13, 2023
5419804
Merge branch 'main' into feat/thread-member-pagination
Jiralite Jan 18, 2023
135cdfa
Merge branch 'main' into feat/thread-member-pagination
Jiralite Feb 17, 2023
de25cea
Merge branch 'main' into feat/thread-member-pagination
kodiakhq[bot] Feb 17, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 37 additions & 10 deletions packages/discord.js/src/managers/ThreadMemberManager.js
Original file line number Diff line number Diff line change
@@ -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');
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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.
* <info>With `withMember` set to `true`, pagination is enabled.</info>
* @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.
* <info>This method requires the {@link GatewayIntentBits.GuildMembers} privileged gateway intent.</info>
Expand All @@ -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());
}
}

Expand Down
19 changes: 15 additions & 4 deletions packages/discord.js/src/structures/ThreadMember.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);

/**
Expand All @@ -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.
Jiralite marked this conversation as resolved.
Show resolved Hide resolved
* @type {?GuildMember}
* @private
*/
this.member = this.thread.guild.members._add(data.member, extra.cache);
} else {
this.member ??= null;
}
}

/**
Expand All @@ -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);
}

/**
Expand Down
34 changes: 29 additions & 5 deletions packages/discord.js/typings/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2962,10 +2962,11 @@ export class ThreadChannel<Forum extends boolean = boolean> extends TextBasedCha
public toString(): ChannelMention;
}

export class ThreadMember extends Base {
private constructor(thread: ThreadChannel, data?: RawThreadMemberData);
export class ThreadMember<HasMemberData extends boolean = boolean> extends Base {
private constructor(thread: ThreadChannel, data: RawThreadMemberData, extra?: unknown);
public flags: ThreadMemberFlagsBitField;
public get guildMember(): GuildMember | null;
private member: If<HasMemberData, GuildMember>;
public get guildMember(): HasMemberData extends true ? GuildMember : GuildMember | null;
public id: Snowflake;
public get joinedAt(): Date | null;
public joinedTimestamp: number | null;
Expand Down Expand Up @@ -4078,8 +4079,18 @@ export class ThreadMemberManager extends CachedManager<Snowflake, ThreadMember,
public thread: AnyThreadChannel;
public get me(): ThreadMember | null;
public add(member: UserResolvable | '@me', reason?: string): Promise<Snowflake>;

public fetch(
options: ThreadMember<true> | ((FetchThreadMemberOptions & { withMember: true }) | { member: ThreadMember<true> }),
): Promise<ThreadMember<true>>;

public fetch(options: ThreadMemberResolvable | FetchThreadMemberOptions): Promise<ThreadMember>;
public fetch(options?: FetchThreadMembersOptions): Promise<Collection<Snowflake, ThreadMember>>;

public fetch(
options: FetchThreadMembersWithGuildMemberDataOptions,
): Promise<Collection<Snowflake, ThreadMember<true>>>;

public fetch(options?: FetchThreadMembersWithoutGuildMemberDataOptions): Promise<Collection<Snowflake, ThreadMember>>;
public fetchMe(options?: BaseFetchOptions): Promise<ThreadMember>;
public remove(id: Snowflake | '@me', reason?: string): Promise<Snowflake>;
}
Expand Down Expand Up @@ -5174,12 +5185,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;
Expand Down
16 changes: 14 additions & 2 deletions packages/discord.js/typings/index.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1525,17 +1525,29 @@ declare const guildBanManager: GuildBanManager;
guildBanManager.fetch({ user: '1234567890', after: '1234567890', cache: true, force: false });
}

declare const threadMemberWithGuildMember: ThreadMember<true>;
declare const threadMemberManager: ThreadMemberManager;
{
expectType<Promise<ThreadMember>>(threadMemberManager.fetch('12345678'));
expectType<Promise<ThreadMember>>(threadMemberManager.fetch({ member: '12345678', cache: false }));
expectType<Promise<ThreadMember>>(threadMemberManager.fetch({ member: '12345678', force: true }));
expectType<Promise<ThreadMember>>(threadMemberManager.fetch({ member: '12345678', cache: false, force: true }));
expectType<Promise<ThreadMember<true>>>(threadMemberManager.fetch({ member: threadMemberWithGuildMember }));
expectType<Promise<ThreadMember<true>>>(threadMemberManager.fetch({ member: '12345678901234567', withMember: true }));
expectType<Promise<Collection<Snowflake, ThreadMember>>>(threadMemberManager.fetch());
expectType<Promise<Collection<Snowflake, ThreadMember>>>(threadMemberManager.fetch({}));
expectType<Promise<Collection<Snowflake, ThreadMember>>>(threadMemberManager.fetch({ cache: true }));

expectType<Promise<Collection<Snowflake, ThreadMember<true>>>>(
threadMemberManager.fetch({ cache: true, limit: 50, withMember: true, after: '12345678901234567' }),
);

expectType<Promise<Collection<Snowflake, ThreadMember>>>(
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;
Expand Down