From 3a547878b64fdcb851a5d7025a76aedfd8179fdb Mon Sep 17 00:00:00 2001 From: RedStar Date: Mon, 2 Nov 2026 14:11:33 +0100 Subject: [PATCH] fix(plugin-gateway): read the optional ids of a message as null like discord.js --- .changeset/gateway-message-null-ids.md | 5 +++ .../src/structures/messages/Message.ts | 34 ++++++++++++++++++- .../plugin-gateway/tests/structures.test.ts | 26 ++++++++++++++ 3 files changed, 64 insertions(+), 1 deletion(-) create mode 100644 .changeset/gateway-message-null-ids.md diff --git a/.changeset/gateway-message-null-ids.md b/.changeset/gateway-message-null-ids.md new file mode 100644 index 00000000..f270a597 --- /dev/null +++ b/.changeset/gateway-message-null-ids.md @@ -0,0 +1,5 @@ +--- +"@wolfstar/plugin-gateway": patch +--- + +`Message#webhookId`, `applicationId`, `nonce` and `position` are `null` when the message has none, like discord.js, instead of the `undefined` they inherited from `@discordjs/structures`, so a check such as `message.webhookId !== null` no longer takes every message for the one of a webhook. diff --git a/packages/plugin-gateway/src/structures/messages/Message.ts b/packages/plugin-gateway/src/structures/messages/Message.ts index 79ac36b8..e72f2c92 100644 --- a/packages/plugin-gateway/src/structures/messages/Message.ts +++ b/packages/plugin-gateway/src/structures/messages/Message.ts @@ -178,7 +178,11 @@ export type MessageSnapshot = Partialize< * the checks answer a promise read from the cache, typed as one once `GatewayCacheConfig` declares the cache * asynchronous. The `fetch*able()` twins also ask the API for what is not cached. */ -export class Message extends BaseMessage<""> { +// The IDs `@discordjs/structures` reads as `undefined` when the message has none, where discord.js gives `null`: omitted +// from its data type so `Message` can override them. +type NullableMessageKeys = "webhook_id" | "application_id" | "nonce" | "position"; + +export class Message extends BaseMessage { /** * Keeps the raw `timestamp` and `edited_timestamp`, which `@discordjs/structures` strips and re-serializes in its own * format, so that {@link Message.toJSON} returns the message as received. @@ -490,6 +494,34 @@ export class Message extends BaseMessage<""> return this.optimizedTimestamp("edited_timestamp"); } + /** + * The ID of the webhook that sent the message, `null` when no webhook did, like discord.js's `Message#webhookId`. + */ + public override get webhookId(): string | null { + return this[kData].webhook_id ?? null; + } + + /** + * The ID of the application of an interaction or of a Rich Presence-related chat embed, `null` without one. + */ + public override get applicationId(): string | null { + return this[kData].application_id ?? null; + } + + /** + * The nonce the message was sent with, to check that it was sent, `null` without one. + */ + public override get nonce(): string | number | null { + return this[kData].nonce ?? null; + } + + /** + * The approximate position of the message in a thread, `null` outside of one. + */ + public override get position(): number | null { + return this[kData].position ?? null; + } + public get editedAt(): Date | null { const { editedTimestamp } = this; return editedTimestamp === null ? null : new Date(editedTimestamp); diff --git a/packages/plugin-gateway/tests/structures.test.ts b/packages/plugin-gateway/tests/structures.test.ts index a5338560..cb08d670 100644 --- a/packages/plugin-gateway/tests/structures.test.ts +++ b/packages/plugin-gateway/tests/structures.test.ts @@ -342,6 +342,32 @@ describe("optimized structures", () => { expect(clone.toJSON().joined_at).toBe("2026-01-01T00:00:00.000Z"); }); + test("GIVEN a message without a webhook, an application, a nonce or a position THEN they are null like discord.js", () => { + const message = new Message({ id: "1", channel_id: "2", author: data } as never); + + expect(message.webhookId).toBeNull(); + expect(message.applicationId).toBeNull(); + expect(message.nonce).toBeNull(); + expect(message.position).toBeNull(); + }); + + test("GIVEN a message with a webhook, an application, a nonce and a position THEN they are read from it", () => { + const message = new Message({ + id: "1", + channel_id: "2", + author: data, + webhook_id: "3", + application_id: "4", + nonce: "5", + position: 0, + } as never); + + expect(message.webhookId).toBe("3"); + expect(message.applicationId).toBe("4"); + expect(message.nonce).toBe("5"); + expect(message.position).toBe(0); + }); + test("GIVEN a message edit and permission overwrite THEN optimized values follow patches", () => { const message = new Message({ id: "1",