fix(discord): resolve user ID from DM interactions for approval clicks
Discord puts the clicking user at interaction.member.user for guild interactions but interaction.user for DM interactions. The Gateway handler only checked interaction.member, so DM button clicks resolved to an empty user ID and were silently rejected as unauthorized. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -105,7 +105,7 @@ export function createChatSdkBridge(config: ChatSdkBridgeConfig): ChannelAdapter
|
|||||||
let setupConfig: ChannelSetup;
|
let setupConfig: ChannelSetup;
|
||||||
let gatewayAbort: AbortController | null = null;
|
let gatewayAbort: AbortController | null = null;
|
||||||
|
|
||||||
async function messageToInbound(message: ChatMessage, isMention: boolean): Promise<InboundMessage> {
|
async function messageToInbound(message: ChatMessage, isMention: boolean, isGroup?: boolean): Promise<InboundMessage> {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
const serialized = message.toJSON() as Record<string, any>;
|
const serialized = message.toJSON() as Record<string, any>;
|
||||||
|
|
||||||
@@ -162,6 +162,7 @@ export function createChatSdkBridge(config: ChatSdkBridgeConfig): ChannelAdapter
|
|||||||
content: serialized,
|
content: serialized,
|
||||||
timestamp: message.metadata.dateSent.toISOString(),
|
timestamp: message.metadata.dateSent.toISOString(),
|
||||||
isMention,
|
isMention,
|
||||||
|
isGroup,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -195,13 +196,13 @@ export function createChatSdkBridge(config: ChatSdkBridgeConfig): ChannelAdapter
|
|||||||
// wirings still fire on in-thread mentions.
|
// wirings still fire on in-thread mentions.
|
||||||
chat.onSubscribedMessage(async (thread, message) => {
|
chat.onSubscribedMessage(async (thread, message) => {
|
||||||
const channelId = adapter.channelIdFromThreadId(thread.id);
|
const channelId = adapter.channelIdFromThreadId(thread.id);
|
||||||
await setupConfig.onInbound(channelId, thread.id, await messageToInbound(message, message.isMention === true));
|
await setupConfig.onInbound(channelId, thread.id, await messageToInbound(message, message.isMention === true, true));
|
||||||
});
|
});
|
||||||
|
|
||||||
// @mention in an unsubscribed thread — SDK-confirmed bot mention.
|
// @mention in an unsubscribed thread — SDK-confirmed bot mention.
|
||||||
chat.onNewMention(async (thread, message) => {
|
chat.onNewMention(async (thread, message) => {
|
||||||
const channelId = adapter.channelIdFromThreadId(thread.id);
|
const channelId = adapter.channelIdFromThreadId(thread.id);
|
||||||
await setupConfig.onInbound(channelId, thread.id, await messageToInbound(message, true));
|
await setupConfig.onInbound(channelId, thread.id, await messageToInbound(message, true, true));
|
||||||
});
|
});
|
||||||
|
|
||||||
// DMs — by definition addressed to the bot. Thread id flows through
|
// DMs — by definition addressed to the bot. Thread id flows through
|
||||||
@@ -216,7 +217,7 @@ export function createChatSdkBridge(config: ChatSdkBridgeConfig): ChannelAdapter
|
|||||||
sender: (message.author as any)?.fullName ?? (message.author as any)?.userId ?? 'unknown',
|
sender: (message.author as any)?.fullName ?? (message.author as any)?.userId ?? 'unknown',
|
||||||
threadId: thread.id,
|
threadId: thread.id,
|
||||||
});
|
});
|
||||||
await setupConfig.onInbound(channelId, thread.id, await messageToInbound(message, true));
|
await setupConfig.onInbound(channelId, thread.id, await messageToInbound(message, true, false));
|
||||||
});
|
});
|
||||||
|
|
||||||
// Plain messages in unsubscribed threads.
|
// Plain messages in unsubscribed threads.
|
||||||
@@ -231,7 +232,7 @@ export function createChatSdkBridge(config: ChatSdkBridgeConfig): ChannelAdapter
|
|||||||
// flood gate.
|
// flood gate.
|
||||||
chat.onNewMessage(/./, async (thread, message) => {
|
chat.onNewMessage(/./, async (thread, message) => {
|
||||||
const channelId = adapter.channelIdFromThreadId(thread.id);
|
const channelId = adapter.channelIdFromThreadId(thread.id);
|
||||||
await setupConfig.onInbound(channelId, thread.id, await messageToInbound(message, false));
|
await setupConfig.onInbound(channelId, thread.id, await messageToInbound(message, false, true));
|
||||||
});
|
});
|
||||||
|
|
||||||
// Handle button clicks (ask_user_question)
|
// Handle button clicks (ask_user_question)
|
||||||
@@ -501,7 +502,10 @@ async function handleForwardedEvent(
|
|||||||
// type 3 = MessageComponent (button/select)
|
// type 3 = MessageComponent (button/select)
|
||||||
if (interaction.type === 3) {
|
if (interaction.type === 3) {
|
||||||
const customId = (interaction.data as Record<string, unknown>)?.custom_id as string;
|
const customId = (interaction.data as Record<string, unknown>)?.custom_id as string;
|
||||||
const user = (interaction.member as Record<string, unknown>)?.user as Record<string, string> | undefined;
|
// In guilds the clicker is at interaction.member.user; in DMs it's interaction.user directly.
|
||||||
|
const user =
|
||||||
|
((interaction.member as Record<string, unknown>)?.user as Record<string, string> | undefined) ??
|
||||||
|
(interaction.user as Record<string, string> | undefined);
|
||||||
const interactionId = interaction.id as string;
|
const interactionId = interaction.id as string;
|
||||||
const interactionToken = interaction.token as string;
|
const interactionToken = interaction.token as string;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user