From 45bd430c0d55ddb98380ea320fab9dc56211e07a Mon Sep 17 00:00:00 2001 From: Qjuh <76154676+Qjuh@users.noreply.github.com> Date: Fri, 27 Mar 2026 14:34:33 +0100 Subject: [PATCH] feat: allow partial DMChannel without client user (#11462) * feat(DMChannel): allow partial DMChannel without client user * chore: apply code review comments * chore: apply code review suggestion --- .../discord.js/src/client/actions/Action.js | 12 ++++++-- .../src/client/actions/InteractionCreate.js | 4 ++- .../discord.js/src/managers/UserManager.js | 7 +++-- .../discord.js/src/structures/DMChannel.js | 28 ++++++++++++++----- packages/discord.js/typings/index.d.ts | 3 +- 5 files changed, 40 insertions(+), 14 deletions(-) diff --git a/packages/discord.js/src/client/actions/Action.js b/packages/discord.js/src/client/actions/Action.js index 8d4e30d82..41c4febea 100644 --- a/packages/discord.js/src/client/actions/Action.js +++ b/packages/discord.js/src/client/actions/Action.js @@ -33,10 +33,16 @@ class GenericAction { const payloadData = {}; const id = data.channel_id ?? data.id; - if (!('recipients' in data)) { - // Try to resolve the recipient, but do not add the client user. + if ('recipients' in data) { + // Try to resolve the recipient, but do not add if already existing in recipients. const recipient = data.author ?? data.user ?? { id: data.user_id }; - if (recipient.id !== this.client.user.id) payloadData.recipients = [recipient]; + if (!data.recipients.some(existingRecipient => recipient.id === existingRecipient.id)) { + payloadData.recipients = [...data.recipients, recipient]; + } + } else { + // Try to resolve the recipient. + const recipient = data.author ?? data.user ?? { id: data.user_id }; + payloadData.recipients = [recipient]; } if (id !== undefined) payloadData.id = id; diff --git a/packages/discord.js/src/client/actions/InteractionCreate.js b/packages/discord.js/src/client/actions/InteractionCreate.js index ee04fa20b..4ea5ac86b 100644 --- a/packages/discord.js/src/client/actions/InteractionCreate.js +++ b/packages/discord.js/src/client/actions/InteractionCreate.js @@ -21,7 +21,9 @@ class InteractionCreateAction extends Action { const client = this.client; // Resolve and cache partial channels for Interaction#channel getter - const channel = data.channel && this.getChannel(data.channel); + const channel = + data.channel && + this.getChannel({ ...data.channel, ...('recipients' in data.channel ? { user: data.user } : undefined) }); // Do not emit this for interactions that cache messages that are non-text-based. let InteractionClass; diff --git a/packages/discord.js/src/managers/UserManager.js b/packages/discord.js/src/managers/UserManager.js index d8927a0b2..0842e50e9 100644 --- a/packages/discord.js/src/managers/UserManager.js +++ b/packages/discord.js/src/managers/UserManager.js @@ -41,9 +41,12 @@ class UserManager extends CachedManager { * @private */ dmChannel(userId) { + const expectedRecipientIds = [userId, this.client.user.id]; return ( - this.client.channels.cache.find(channel => channel.type === ChannelType.DM && channel.recipientId === userId) ?? - null + this.client.channels.cache.find( + channel => + channel.type === ChannelType.DM && channel.recipientIds.every(id => expectedRecipientIds.includes(id)), + ) ?? null ); } diff --git a/packages/discord.js/src/structures/DMChannel.js b/packages/discord.js/src/structures/DMChannel.js index ee466442a..83c14b06a 100644 --- a/packages/discord.js/src/structures/DMChannel.js +++ b/packages/discord.js/src/structures/DMChannel.js @@ -30,16 +30,18 @@ class DMChannel extends BaseChannel { super._patch(data); if (data.recipients) { - const recipient = data.recipients[0]; - /** - * The recipient's id - * @type {Snowflake} + * The recipients' ids + * @type {Snowflake[]} */ - this.recipientId = recipient.id; + this.recipientIds = [ + ...new Set([...(this.recipientIds ?? []), ...data.recipients.map(recipient => recipient.id)]), + ]; - if ('username' in recipient || this.client.options.partials.includes(Partials.User)) { - this.client.users._add(recipient); + for (const recipient of data.recipients) { + if ('username' in recipient || this.client.options.partials.includes(Partials.User)) { + this.client.users._add(recipient); + } } } @@ -71,6 +73,18 @@ class DMChannel extends BaseChannel { return this.lastMessageId === undefined; } + /** + * The recipient's id. + * For DMChannel the client user is not a part of this might return a wrong id. + * This will return `null` in the next major version. + * @type {Snowflake} + * @readonly + */ + get recipientId() { + // To not be a breaking change this returns the arbitrary first id if this is not a DMChannel with the client user + return this.recipientIds.find(recipientId => recipientId !== this.client.user.id) ?? this.recipientIds[0]; + } + /** * The recipient on the other end of the DM * @type {?User} diff --git a/packages/discord.js/typings/index.d.ts b/packages/discord.js/typings/index.d.ts index f14e53f40..08a357b9c 100644 --- a/packages/discord.js/typings/index.d.ts +++ b/packages/discord.js/typings/index.d.ts @@ -1514,7 +1514,8 @@ export interface DMChannel extends Omit< export class DMChannel extends BaseChannel { private constructor(client: Client, data?: RawDMChannelData); public flags: Readonly; - public recipientId: Snowflake; + public get recipientId(): Snowflake; + public recipientIds: Snowflake[]; public get recipient(): User | null; public type: ChannelType.DM; public fetch(force?: boolean): Promise;