fix: harden MEDIA parsing and add tests
This commit is contained in:
@@ -171,8 +171,15 @@ const mediaNote =
|
||||
? `[media attached: ${ctx.MediaPath}${ctx.MediaType ? ` (${ctx.MediaType})` : ""}${ctx.MediaUrl ? ` | ${ctx.MediaUrl}` : ""}]`
|
||||
: undefined;
|
||||
// For command prompts we prepend the media note so Claude et al. see it; text replies stay clean.
|
||||
const mediaReplyHint =
|
||||
mediaNote && reply?.mode === "command"
|
||||
? "To send an image back, add a line like: MEDIA:https://example.com/image.jpg (no spaces). Keep caption in the text body."
|
||||
: undefined;
|
||||
const commandBody = mediaNote
|
||||
? `${mediaNote}\n${prefixedBody ?? ""}`.trim()
|
||||
? [mediaNote, mediaReplyHint, prefixedBody ?? ""]
|
||||
.filter(Boolean)
|
||||
.join("\n")
|
||||
.trim()
|
||||
: prefixedBody;
|
||||
const templatingCtx: TemplateContext = {
|
||||
...sessionCtx,
|
||||
@@ -282,10 +289,36 @@ const mediaNote =
|
||||
const rawStdout = stdout.trim();
|
||||
let trimmed = rawStdout;
|
||||
let mediaFromCommand: string | undefined;
|
||||
const mediaMatch = /MEDIA:\s*(.+)$/im.exec(rawStdout);
|
||||
if (mediaMatch?.[1]) {
|
||||
mediaFromCommand = mediaMatch[1].trim();
|
||||
trimmed = rawStdout.replace(mediaMatch[0], "").trim();
|
||||
const mediaLine = rawStdout
|
||||
.split("\n")
|
||||
.find((line) => /^MEDIA:/i.test(line));
|
||||
if (mediaLine) {
|
||||
const after = mediaLine.replace(/^MEDIA:\s*/i, "");
|
||||
const parts = after.trim().split(/\s+/);
|
||||
if (parts.length === 1 && parts[0]) {
|
||||
mediaFromCommand = parts[0];
|
||||
}
|
||||
trimmed = rawStdout
|
||||
.split("\n")
|
||||
.filter((line) => !/^MEDIA:/i.test(line))
|
||||
.join("\n")
|
||||
.trim();
|
||||
// Basic sanity: accept only URLs or existing file paths without whitespace.
|
||||
const hasWhitespace = mediaFromCommand
|
||||
? /\s/.test(mediaFromCommand)
|
||||
: false;
|
||||
const looksLikeUrl = mediaFromCommand
|
||||
? /^https?:\/\//i.test(mediaFromCommand)
|
||||
: false;
|
||||
if (
|
||||
!mediaFromCommand ||
|
||||
hasWhitespace ||
|
||||
(!looksLikeUrl && mediaFromCommand.length > 1024)
|
||||
) {
|
||||
mediaFromCommand = undefined;
|
||||
}
|
||||
} else {
|
||||
trimmed = rawStdout;
|
||||
}
|
||||
if (stderr?.trim()) {
|
||||
logVerbose(`Command auto-reply stderr: ${stderr.trim()}`);
|
||||
|
||||
@@ -146,6 +146,32 @@ describe("config and templating", () => {
|
||||
expect(result?.mediaUrl).toBe("https://example.com/img.jpg");
|
||||
});
|
||||
|
||||
it("ignores invalid MEDIA lines with whitespace", async () => {
|
||||
const runSpy = vi.spyOn(index, "runCommandWithTimeout").mockResolvedValue({
|
||||
stdout: "hello\nMEDIA: not a url with spaces\nrest\n",
|
||||
stderr: "",
|
||||
code: 0,
|
||||
signal: null,
|
||||
killed: false,
|
||||
});
|
||||
const cfg = {
|
||||
inbound: {
|
||||
reply: {
|
||||
mode: "command" as const,
|
||||
command: ["echo", "{{Body}}"],
|
||||
},
|
||||
},
|
||||
};
|
||||
const result = await index.getReplyFromConfig(
|
||||
{ Body: "hi", From: "+1", To: "+2" },
|
||||
undefined,
|
||||
cfg,
|
||||
runSpy,
|
||||
);
|
||||
expect(result?.text).toBe("hello\nrest");
|
||||
expect(result?.mediaUrl).toBeUndefined();
|
||||
});
|
||||
|
||||
it("getReplyFromConfig runs command and manages session store", async () => {
|
||||
const tmpStore = path.join(os.tmpdir(), `warelay-store-${Date.now()}.json`);
|
||||
vi.spyOn(crypto, "randomUUID").mockReturnValue("session-123");
|
||||
|
||||
Reference in New Issue
Block a user