mirror of https://github.com/openclaw/openclaw.git
fix(discord): downgrade text-only component+media to classic message and auto-append file block
This commit is contained in:
parent
36ed66cce2
commit
efbb9a1296
|
|
@ -17,6 +17,7 @@ import {
|
|||
type DiscordComponentMessageSpec,
|
||||
} from "./components.js";
|
||||
import { loadOutboundMediaFromUrl } from "./runtime-api.js";
|
||||
import { sendMessageDiscord } from "./send.outbound.js";
|
||||
import {
|
||||
buildDiscordSendError,
|
||||
createDiscordClient,
|
||||
|
|
@ -41,6 +42,55 @@ function extractComponentAttachmentNames(spec: DiscordComponentMessageSpec): str
|
|||
return names;
|
||||
}
|
||||
|
||||
function hasComponentAttachmentBlock(spec: DiscordComponentMessageSpec): boolean {
|
||||
return (spec.blocks ?? []).some((block) => block.type === "file");
|
||||
}
|
||||
|
||||
function withImplicitComponentAttachmentBlock(
|
||||
spec: DiscordComponentMessageSpec,
|
||||
attachmentName: string | undefined,
|
||||
): DiscordComponentMessageSpec {
|
||||
if (!attachmentName || hasComponentAttachmentBlock(spec)) {
|
||||
return spec;
|
||||
}
|
||||
return {
|
||||
...spec,
|
||||
blocks: [
|
||||
...(spec.blocks ?? []),
|
||||
{
|
||||
type: "file",
|
||||
file: `attachment://${attachmentName}` as `attachment://${string}`,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
function canSendAsClassicDiscordMessage(spec: DiscordComponentMessageSpec): boolean {
|
||||
return (spec.blocks ?? []).every((block) => block.type === "text" || block.type === "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");
|
||||
}
|
||||
|
||||
type DiscordComponentSendOpts = {
|
||||
cfg?: OpenClawConfig;
|
||||
accountId?: string;
|
||||
|
|
@ -79,28 +129,12 @@ async function buildDiscordComponentPayload(params: {
|
|||
body: ReturnType<typeof stripUndefinedFields>;
|
||||
buildResult: ReturnType<typeof buildDiscordComponentMessage>;
|
||||
}> {
|
||||
const buildResult = buildDiscordComponentMessage({
|
||||
spec: params.spec,
|
||||
sessionKey: params.opts.sessionKey,
|
||||
agentId: params.opts.agentId,
|
||||
accountId: params.accountId,
|
||||
});
|
||||
const flags = buildDiscordComponentMessageFlags(buildResult.components);
|
||||
const finalFlags = params.opts.silent
|
||||
? (flags ?? 0) | SUPPRESS_NOTIFICATIONS_FLAG
|
||||
: (flags ?? undefined);
|
||||
const messageReference = params.opts.replyTo
|
||||
? { message_id: params.opts.replyTo, fail_if_not_exists: false }
|
||||
: undefined;
|
||||
|
||||
const attachmentNames = extractComponentAttachmentNames(params.spec);
|
||||
const uniqueAttachmentNames = [...new Set(attachmentNames)];
|
||||
if (uniqueAttachmentNames.length > 1) {
|
||||
throw new Error(
|
||||
"Discord component attachments currently support a single file. Use media-gallery for multiple files.",
|
||||
);
|
||||
}
|
||||
const expectedAttachmentName = uniqueAttachmentNames[0];
|
||||
let spec = params.spec;
|
||||
let resolvedFileName: string | undefined;
|
||||
let files: MessagePayloadFile[] | undefined;
|
||||
if (params.opts.mediaUrl) {
|
||||
const media = await loadOutboundMediaFromUrl(params.opts.mediaUrl, {
|
||||
|
|
@ -109,20 +143,42 @@ async function buildDiscordComponentPayload(params: {
|
|||
mediaReadFile: params.opts.mediaReadFile,
|
||||
});
|
||||
const filenameOverride = params.opts.filename?.trim();
|
||||
const fileName = filenameOverride || media.fileName || "upload";
|
||||
if (expectedAttachmentName && expectedAttachmentName !== fileName) {
|
||||
throw new Error(
|
||||
`Component file block expects attachment "${expectedAttachmentName}", but the uploaded file is "${fileName}". Update components.blocks[].file or provide a matching filename.`,
|
||||
);
|
||||
}
|
||||
resolvedFileName = filenameOverride || media.fileName || "upload";
|
||||
spec = withImplicitComponentAttachmentBlock(spec, resolvedFileName);
|
||||
const fileData = toDiscordFileBlob(media.buffer);
|
||||
files = [{ data: fileData, name: fileName }];
|
||||
} else if (expectedAttachmentName) {
|
||||
files = [{ data: fileData, name: resolvedFileName }];
|
||||
}
|
||||
|
||||
const attachmentNames = extractComponentAttachmentNames(spec);
|
||||
const uniqueAttachmentNames = [...new Set(attachmentNames)];
|
||||
if (uniqueAttachmentNames.length > 1) {
|
||||
throw new Error(
|
||||
"Discord component attachments currently support a single file. Use media-gallery for multiple files.",
|
||||
);
|
||||
}
|
||||
const expectedAttachmentName = uniqueAttachmentNames[0];
|
||||
if (expectedAttachmentName && resolvedFileName && expectedAttachmentName !== resolvedFileName) {
|
||||
throw new 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) {
|
||||
throw new Error(
|
||||
"Discord component file blocks require a media attachment (media/path/filePath).",
|
||||
);
|
||||
}
|
||||
|
||||
const buildResult = buildDiscordComponentMessage({
|
||||
spec,
|
||||
sessionKey: params.opts.sessionKey,
|
||||
agentId: params.opts.agentId,
|
||||
accountId: params.accountId,
|
||||
});
|
||||
const flags = buildDiscordComponentMessageFlags(buildResult.components);
|
||||
const finalFlags = params.opts.silent
|
||||
? (flags ?? 0) | SUPPRESS_NOTIFICATIONS_FLAG
|
||||
: (flags ?? undefined);
|
||||
|
||||
const payload: MessagePayloadObject = {
|
||||
components: buildResult.components,
|
||||
...(finalFlags ? { flags: finalFlags } : {}),
|
||||
|
|
@ -141,6 +197,20 @@ export async function sendDiscordComponentMessage(
|
|||
spec: DiscordComponentMessageSpec,
|
||||
opts: DiscordComponentSendOpts = {},
|
||||
): Promise<DiscordSendResult> {
|
||||
if (opts.mediaUrl && canSendAsClassicDiscordMessage(spec)) {
|
||||
return await sendMessageDiscord(to, collapseClassicComponentText(spec), {
|
||||
cfg: opts.cfg,
|
||||
accountId: opts.accountId,
|
||||
token: opts.token,
|
||||
rest: opts.rest,
|
||||
mediaUrl: opts.mediaUrl,
|
||||
filename: opts.filename,
|
||||
mediaLocalRoots: opts.mediaLocalRoots,
|
||||
replyTo: opts.replyTo,
|
||||
silent: opts.silent,
|
||||
});
|
||||
}
|
||||
|
||||
const cfg = opts.cfg ?? loadConfig();
|
||||
const accountInfo = resolveDiscordAccount({ cfg, accountId: opts.accountId });
|
||||
const { token, rest, request } = createDiscordClient(opts, cfg);
|
||||
|
|
|
|||
Loading…
Reference in New Issue