refactor(discord): share native interaction channel context

This commit is contained in:
Peter Steinberger
2026-04-22 04:47:22 +01:00
parent 00cdab99bf
commit f027d8faa7
3 changed files with 120 additions and 104 deletions

View File

@@ -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<OpenClawConfig["channels"]>["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)

View File

@@ -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<OpenClawConfig["channels"]>["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();

View File

@@ -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<DiscordNativeInteractionChannelContext> {
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,
};
}