diff --git a/extensions/discord/src/monitor/native-command-ui.ts b/extensions/discord/src/monitor/native-command-ui.ts index 4d1b0599a83..2706deabba1 100644 --- a/extensions/discord/src/monitor/native-command-ui.ts +++ b/extensions/discord/src/monitor/native-command-ui.ts @@ -1,6 +1,5 @@ import { Button, - ChannelType, Container, Row, StringSelectMenu, @@ -34,12 +33,7 @@ import { normalizeOptionalString, withTimeout, } from "openclaw/plugin-sdk/text-runtime"; -import { - resolveDiscordChannelNameSafe, - resolveDiscordChannelParentIdSafe, -} from "./channel-access.js"; import { resolveDiscordSlashCommandConfig } from "./commands.js"; -import { resolveDiscordChannelInfo } from "./message-utils.js"; import { readDiscordModelPickerRecentModels, recordDiscordModelPickerRecentModel, @@ -56,8 +50,8 @@ import { type DiscordModelPickerCommandContext, } from "./model-picker.js"; import { resolveDiscordNativeInteractionRouteState } from "./native-command-route.js"; +import { resolveDiscordNativeInteractionChannelContext } from "./native-interaction-channel-context.js"; import type { ThreadBindingManager } from "./thread-bindings.js"; -import { resolveDiscordThreadParentInfo } from "./threading.js"; type DiscordConfig = NonNullable["discord"]; @@ -241,33 +235,16 @@ async function resolveDiscordModelPickerRouteState(params: { enforceConfiguredBindingReadiness?: boolean; }) { const { interaction, cfg, accountId } = params; - const channel = interaction.channel; - const channelType = channel?.type; - const isDirectMessage = channelType === ChannelType.DM; - const isGroupDm = channelType === ChannelType.GroupDM; - const isThreadChannel = - channelType === ChannelType.PublicThread || - channelType === ChannelType.PrivateThread || - channelType === ChannelType.AnnouncementThread; - const rawChannelId = channel?.id ?? "unknown"; + const { isDirectMessage, isGroupDm, isThreadChannel, rawChannelId, threadParentId } = + await resolveDiscordNativeInteractionChannelContext({ + channel: interaction.channel, + client: interaction.client, + hasGuild: Boolean(interaction.guild), + channelIdFallback: "unknown", + }); const memberRoleIds = Array.isArray(interaction.rawData.member?.roles) ? interaction.rawData.member.roles.map((roleId: string) => roleId) : []; - let threadParentId: string | undefined; - if (interaction.guild && channel && isThreadChannel && rawChannelId) { - const channelInfo = await resolveDiscordChannelInfo(interaction.client, rawChannelId); - const parentInfo = await resolveDiscordThreadParentInfo({ - client: interaction.client, - threadChannel: { - id: rawChannelId, - name: resolveDiscordChannelNameSafe(channel), - parentId: resolveDiscordChannelParentIdSafe(channel), - parent: undefined, - }, - channelInfo, - }); - threadParentId = parentInfo.id; - } const threadBinding = isThreadChannel ? params.threadBindings.getByThreadId(rawChannelId) diff --git a/extensions/discord/src/monitor/native-command.ts b/extensions/discord/src/monitor/native-command.ts index e8968b1bbc8..de26ec7cbca 100644 --- a/extensions/discord/src/monitor/native-command.ts +++ b/extensions/discord/src/monitor/native-command.ts @@ -1,6 +1,5 @@ import { Button, - ChannelType, Command, StringSelectMenu, type AutocompleteInteraction, @@ -56,7 +55,6 @@ import { resolveDiscordMaxLinesPerMessage } from "../accounts.js"; import { chunkDiscordTextWithMode } from "../chunk.js"; import { normalizeDiscordAllowList, - normalizeDiscordSlug, resolveDiscordAllowListMatch, resolveDiscordChannelConfigWithFallback, resolveDiscordChannelPolicyCommandAuthorizer, @@ -65,14 +63,9 @@ import { resolveDiscordOwnerAccess, resolveGroupDmAllow, } from "./allow-list.js"; -import { - resolveDiscordChannelNameSafe, - resolveDiscordChannelParentIdSafe, - resolveDiscordChannelTopicSafe, -} from "./channel-access.js"; +import { resolveDiscordChannelTopicSafe } from "./channel-access.js"; import { resolveDiscordDmCommandAccess } from "./dm-command-auth.js"; import { handleDiscordDmCommandDecision } from "./dm-command-decision.js"; -import { resolveDiscordChannelInfo } from "./message-utils.js"; import { buildDiscordNativeCommandContext } from "./native-command-context.js"; import { resolveDiscordNativeInteractionRouteState } from "./native-command-route.js"; import { @@ -86,9 +79,9 @@ import { type DiscordCommandArgContext, type DiscordModelPickerContext, } from "./native-command-ui.js"; +import { resolveDiscordNativeInteractionChannelContext } from "./native-interaction-channel-context.js"; import { resolveDiscordSenderIdentity } from "./sender-identity.js"; import type { ThreadBindingManager } from "./thread-bindings.js"; -import { resolveDiscordThreadParentInfo } from "./threading.js"; type DiscordConfig = NonNullable["discord"]; type DiscordCommandArgs = { @@ -412,17 +405,22 @@ async function resolveDiscordNativeAutocompleteAuthorized(params: { return false; } const sender = resolveDiscordSenderIdentity({ author: user, pluralkitInfo: null }); - const channel = interaction.channel; - const channelType = channel?.type; - const isDirectMessage = channelType === ChannelType.DM; - const isGroupDm = channelType === ChannelType.GroupDM; - const isThreadChannel = - channelType === ChannelType.PublicThread || - channelType === ChannelType.PrivateThread || - channelType === ChannelType.AnnouncementThread; - const channelName = resolveDiscordChannelNameSafe(channel); - const channelSlug = channelName ? normalizeDiscordSlug(channelName) : ""; - const rawChannelId = channel?.id ?? ""; + const { + isDirectMessage, + isGroupDm, + isThreadChannel, + channelName, + channelSlug, + rawChannelId, + threadParentId, + threadParentName, + threadParentSlug, + } = await resolveDiscordNativeInteractionChannelContext({ + channel: interaction.channel, + client: interaction.client, + hasGuild: Boolean(interaction.guild), + channelIdFallback: "", + }); const memberRoleIds = Array.isArray(interaction.rawData.member?.roles) ? interaction.rawData.member.roles.map((roleId: string) => roleId) : []; @@ -460,25 +458,6 @@ async function resolveDiscordNativeAutocompleteAuthorized(params: { guildId: interaction.guild?.id ?? undefined, guildEntries: discordConfig?.guilds, }); - let threadParentId: string | undefined; - let threadParentName: string | undefined; - let threadParentSlug = ""; - if (interaction.guild && channel && isThreadChannel && rawChannelId) { - const channelInfo = await resolveDiscordChannelInfo(interaction.client, rawChannelId); - const parentInfo = await resolveDiscordThreadParentInfo({ - client: interaction.client, - threadChannel: { - id: rawChannelId, - name: channelName, - parentId: resolveDiscordChannelParentIdSafe(channel), - parent: undefined, - }, - channelInfo, - }); - threadParentId = parentInfo.id; - threadParentName = parentInfo.name; - threadParentSlug = threadParentName ? normalizeDiscordSlug(threadParentName) : ""; - } const channelConfig = interaction.guild ? resolveDiscordChannelConfigWithFallback({ guildInfo, @@ -806,16 +785,22 @@ async function dispatchDiscordCommandInteraction(params: { } const sender = resolveDiscordSenderIdentity({ author: user, pluralkitInfo: null }); const channel = interaction.channel; - const channelType = channel?.type; - const isDirectMessage = channelType === ChannelType.DM; - const isGroupDm = channelType === ChannelType.GroupDM; - const isThreadChannel = - channelType === ChannelType.PublicThread || - channelType === ChannelType.PrivateThread || - channelType === ChannelType.AnnouncementThread; - const channelName = resolveDiscordChannelNameSafe(channel); - const channelSlug = channelName ? normalizeDiscordSlug(channelName) : ""; - const rawChannelId = channel?.id ?? ""; + const { + isDirectMessage, + isGroupDm, + isThreadChannel, + channelName, + channelSlug, + rawChannelId, + threadParentId, + threadParentName, + threadParentSlug, + } = await resolveDiscordNativeInteractionChannelContext({ + channel, + client: interaction.client, + hasGuild: Boolean(interaction.guild), + channelIdFallback: "", + }); const memberRoleIds = Array.isArray(interaction.rawData.member?.roles) ? interaction.rawData.member.roles.map((roleId: string) => roleId) : []; @@ -852,26 +837,6 @@ async function dispatchDiscordCommandInteraction(params: { guildId: interaction.guild?.id ?? undefined, guildEntries: discordConfig?.guilds, }); - let threadParentId: string | undefined; - let threadParentName: string | undefined; - let threadParentSlug = ""; - if (interaction.guild && channel && isThreadChannel && rawChannelId) { - // Threads inherit parent channel config unless explicitly overridden. - const channelInfo = await resolveDiscordChannelInfo(interaction.client, rawChannelId); - const parentInfo = await resolveDiscordThreadParentInfo({ - client: interaction.client, - threadChannel: { - id: rawChannelId, - name: channelName, - parentId: resolveDiscordChannelParentIdSafe(channel), - parent: undefined, - }, - channelInfo, - }); - threadParentId = parentInfo.id; - threadParentName = parentInfo.name; - threadParentSlug = threadParentName ? normalizeDiscordSlug(threadParentName) : ""; - } const channelConfig = interaction.guild ? resolveDiscordChannelConfigWithFallback({ guildInfo, @@ -1070,10 +1035,6 @@ async function dispatchDiscordCommandInteraction(params: { return; } const channelId = rawChannelId || "unknown"; - const isThreadChannel = - interaction.channel?.type === ChannelType.PublicThread || - interaction.channel?.type === ChannelType.PrivateThread || - interaction.channel?.type === ChannelType.AnnouncementThread; const messageThreadId = !isDirectMessage && isThreadChannel ? channelId : undefined; const pluginThreadParentId = !isDirectMessage && isThreadChannel ? threadParentId : undefined; const { effectiveRoute } = await getNativeRouteState(); diff --git a/extensions/discord/src/monitor/native-interaction-channel-context.ts b/extensions/discord/src/monitor/native-interaction-channel-context.ts new file mode 100644 index 00000000000..5e8eda3142e --- /dev/null +++ b/extensions/discord/src/monitor/native-interaction-channel-context.ts @@ -0,0 +1,78 @@ +import { ChannelType, type Client } from "@buape/carbon"; +import { normalizeDiscordSlug } from "./allow-list.js"; +import { + resolveDiscordChannelNameSafe, + resolveDiscordChannelParentIdSafe, +} from "./channel-access.js"; +import { resolveDiscordChannelInfo } from "./message-utils.js"; +import { resolveDiscordThreadParentInfo } from "./threading.js"; + +type DiscordInteractionChannel = { + id?: string; + type?: ChannelType; +}; + +export type DiscordNativeInteractionChannelContext = { + channelType?: ChannelType; + isDirectMessage: boolean; + isGroupDm: boolean; + isThreadChannel: boolean; + channelName?: string; + channelSlug: string; + rawChannelId: string; + threadParentId?: string; + threadParentName?: string; + threadParentSlug: string; +}; + +export async function resolveDiscordNativeInteractionChannelContext(params: { + channel: DiscordInteractionChannel | null | undefined; + client: Client; + hasGuild: boolean; + channelIdFallback: string; +}): Promise { + const { channel } = params; + const channelType = channel?.type; + const isDirectMessage = channelType === ChannelType.DM; + const isGroupDm = channelType === ChannelType.GroupDM; + const isThreadChannel = + channelType === ChannelType.PublicThread || + channelType === ChannelType.PrivateThread || + channelType === ChannelType.AnnouncementThread; + const channelName = resolveDiscordChannelNameSafe(channel); + const channelSlug = channelName ? normalizeDiscordSlug(channelName) : ""; + const rawChannelId = channel?.id ?? params.channelIdFallback; + + let threadParentId: string | undefined; + let threadParentName: string | undefined; + let threadParentSlug = ""; + if (params.hasGuild && channel && isThreadChannel && rawChannelId) { + const channelInfo = await resolveDiscordChannelInfo(params.client, rawChannelId); + const parentInfo = await resolveDiscordThreadParentInfo({ + client: params.client, + threadChannel: { + id: rawChannelId, + name: channelName, + parentId: resolveDiscordChannelParentIdSafe(channel), + parent: undefined, + }, + channelInfo, + }); + threadParentId = parentInfo.id; + threadParentName = parentInfo.name; + threadParentSlug = threadParentName ? normalizeDiscordSlug(threadParentName) : ""; + } + + return { + channelType, + isDirectMessage, + isGroupDm, + isThreadChannel, + channelName, + channelSlug, + rawChannelId, + threadParentId, + threadParentName, + threadParentSlug, + }; +}