import {
serializePayload,
type MessagePayloadFile,
type MessagePayloadObject,
type RequestClient,
} from "@buape/carbon"; import { ChannelType, Routes } from "discord-api-types/v10"; import {
requireRuntimeConfig,
type MarkdownTableMode,
type OpenClawConfig,
} from "openclaw/plugin-sdk/config-runtime"; import { recordChannelActivity } from "openclaw/plugin-sdk/infra-runtime"; import type { ChunkMode } from "openclaw/plugin-sdk/reply-chunking"; import { resolveDiscordAccount } from "./accounts.js"; import { registerDiscordComponentEntries } from "./components-registry.js"; import {
buildDiscordComponentMessage,
buildDiscordComponentMessageFlags,
resolveDiscordComponentAttachmentName,
type DiscordComponentBuildResult,
type DiscordComponentMessageSpec,
} from "./components.js"; import { parseAndResolveRecipient } from "./recipient-resolution.js"; import { loadOutboundMediaFromUrl } from "./runtime-api.js"; import { sendMessageDiscord } from "./send.outbound.js"; import {
buildDiscordSendError,
createDiscordClient,
resolveChannelId,
resolveDiscordChannelType,
toDiscordFileBlob,
stripUndefinedFields,
SUPPRESS_NOTIFICATIONS_FLAG,
} from "./send.shared.js"; import type { DiscordSendResult } from "./send.types.js";
const DISCORD_FORUM_LIKE_TYPES = new Set<number>([ChannelType.GuildForum, ChannelType.GuildMedia]);
function extractComponentAttachmentNames(spec: DiscordComponentMessageSpec): string[] { const names: string[] = []; for (const block of spec.blocks ?? []) { if (block.type === "file") {
names.push(resolveDiscordComponentAttachmentName(block.file));
}
} return names;
}
function withImplicitComponentAttachmentBlock(
spec: DiscordComponentMessageSpec,
attachmentName: string | undefined,
): DiscordComponentMessageSpec { if (!attachmentName || hasComponentAttachmentBlock(spec)) { return spec;
} // Discord File components must point at the uploaded attachment name. Add the // matching file block automatically so callers do not have to duplicate it. return {
...spec,
blocks: [
...(spec.blocks ?? []),
{
type: "file",
file: `attachment://${attachmentName}`,
},
],
};
}
/** * Keep the downgrade rules explicit because this path is only safe when the * spec means exactly what a plain Discord message can represent.
*/ function getClassicDiscordMessageDecision(
spec: DiscordComponentMessageSpec,
): ClassicDiscordMessageDecision { if (hasUnsupportedClassicFeatures(spec)) { return { mode: "components", reason: "unsupported-feature" };
} if (!hasClassicOnlyBlocks(spec)) { return { mode: "components", reason: "unsupported-block" };
} if (!hasAtMostOneNonSpoilerFile(spec)) { return { mode: "components", reason: "multiple-or-spoiler-files" };
} return { mode: "classic", reason: "plain-text-single-file" };
}
function collapseClassicComponentText(spec: DiscordComponentMessageSpec): string { const parts: string[] = []; const addPart = (value: string | undefined) => { if (typeof value !== "string") { return;
} const trimmed = value.trim(); if (!trimmed || parts.includes(trimmed)) { return;
}
parts.push(trimmed);
};
addPart(spec.text); for (const block of spec.blocks ?? []) { if (block.type === "text") {
addPart(block.text);
}
} return parts.join("\n\n");
}
const attachmentNames = extractComponentAttachmentNames(spec); const uniqueAttachmentNames = [...new Set(attachmentNames)]; if (uniqueAttachmentNames.length > 1) { thrownew Error( "Discord component attachments currently support a single file. Use media-gallery for multiple files.",
);
} const expectedAttachmentName = uniqueAttachmentNames[0]; if (expectedAttachmentName && resolvedFileName && expectedAttachmentName !== resolvedFileName) { thrownew Error(
`Component file block expects attachment "${expectedAttachmentName}", but the uploaded file is "${resolvedFileName}". Update components.blocks[].file or provide a matching filename.`,
);
} if (!params.opts.mediaUrl && expectedAttachmentName) { thrownew Error( "Discord component file blocks require a media attachment (media/path/filePath).",
);
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.