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
This commit is contained in:
Qjuh
2026-03-27 14:34:33 +01:00
committed by GitHub
parent d20e10305b
commit 45bd430c0d
5 changed files with 40 additions and 14 deletions

View File

@@ -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;

View File

@@ -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;

View File

@@ -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
);
}

View File

@@ -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.
* <info>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.</info>
* @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}

View File

@@ -1514,7 +1514,8 @@ export interface DMChannel extends Omit<
export class DMChannel extends BaseChannel {
private constructor(client: Client<true>, data?: RawDMChannelData);
public flags: Readonly<ChannelFlagsBitField>;
public recipientId: Snowflake;
public get recipientId(): Snowflake;
public recipientIds: Snowflake[];
public get recipient(): User | null;
public type: ChannelType.DM;
public fetch(force?: boolean): Promise<this>;