From a8d9d630bc6a3b8697305653c0259e00ab9681ad Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sat, 17 Jan 2026 17:27:12 +0000 Subject: [PATCH] fix: handle legacy matrix polls (#1088) (thanks @sibbl) --- CHANGELOG.md | 3 +++ .../matrix/src/matrix/poll-types.test.ts | 22 +++++++++++++++++++ extensions/matrix/src/matrix/poll-types.ts | 17 ++++++++++++-- extensions/matrix/src/matrix/send.test.ts | 9 ++++++-- 4 files changed, 47 insertions(+), 4 deletions(-) create mode 100644 extensions/matrix/src/matrix/poll-types.test.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 4baf10ea1..59ddcd117 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,9 @@ Docs: https://docs.clawd.bot ### Changes - macOS: strip prerelease/build suffixes when parsing gateway semver patches. (#1110) — thanks @zerone0x. +### Fixes +- Matrix: send voice/image-specific media payloads and keep legacy poll parsing. (#1088) — thanks @sibbl. + ## 2026.1.16-2 ### Changes diff --git a/extensions/matrix/src/matrix/poll-types.test.ts b/extensions/matrix/src/matrix/poll-types.test.ts new file mode 100644 index 000000000..f2d885622 --- /dev/null +++ b/extensions/matrix/src/matrix/poll-types.test.ts @@ -0,0 +1,22 @@ +import { describe, expect, it } from "vitest"; + +import { parsePollStartContent } from "./poll-types.js"; + +describe("parsePollStartContent", () => { + it("parses legacy m.poll payloads", () => { + const summary = parsePollStartContent({ + "m.poll": { + question: { "m.text": "Lunch?" }, + kind: "m.poll.disclosed", + max_selections: 1, + answers: [ + { id: "answer1", "m.text": "Yes" }, + { id: "answer2", "m.text": "No" }, + ], + }, + }); + + expect(summary?.question).toBe("Lunch?"); + expect(summary?.answers).toEqual(["Yes", "No"]); + }); +}); diff --git a/extensions/matrix/src/matrix/poll-types.ts b/extensions/matrix/src/matrix/poll-types.ts index 96b29352b..d25c4e686 100644 --- a/extensions/matrix/src/matrix/poll-types.ts +++ b/extensions/matrix/src/matrix/poll-types.ts @@ -42,7 +42,18 @@ export type PollAnswer = { id: string; } & TextContent; -export type PollStartContent = TimelineEvents[typeof M_POLL_START]; +export type PollStartSubtype = { + question: TextContent; + kind?: PollKind; + max_selections?: number; + answers: PollAnswer[]; +}; + +export type LegacyPollStartContent = { + "m.poll"?: PollStartSubtype; +}; + +export type PollStartContent = TimelineEvents[typeof M_POLL_START] | LegacyPollStartContent; export type PollSummary = { eventId: string; @@ -65,7 +76,9 @@ export function getTextContent(text?: TextContent): string { } export function parsePollStartContent(content: PollStartContent): PollSummary | null { - const poll = content[M_POLL_START] ?? content[ORG_POLL_START]; + const poll = (content as Record)[M_POLL_START] + ?? (content as Record)[ORG_POLL_START] + ?? (content as Record)["m.poll"]; if (!poll) return null; const question = getTextContent(poll.question); diff --git a/extensions/matrix/src/matrix/send.test.ts b/extensions/matrix/src/matrix/send.test.ts index 1d9c746e4..71ba3c79f 100644 --- a/extensions/matrix/src/matrix/send.test.ts +++ b/extensions/matrix/src/matrix/send.test.ts @@ -31,6 +31,11 @@ vi.mock("../../../../src/web/media.js", () => ({ }), })); +vi.mock("../../../../src/media/image-ops.js", () => ({ + getImageMetadata: vi.fn().mockResolvedValue(null), + resizeToJpeg: vi.fn(), +})); + let sendMessageMatrix: typeof import("./send.js").sendMessageMatrix; const makeClient = () => { @@ -65,13 +70,13 @@ describe("sendMessageMatrix media", () => { const uploadArg = uploadContent.mock.calls[0]?.[0]; expect(Buffer.isBuffer(uploadArg)).toBe(true); - const content = sendMessage.mock.calls[0]?.[2] as { + const content = sendMessage.mock.calls[0]?.[1] as { url?: string; msgtype?: string; format?: string; formatted_body?: string; }; - expect(content.msgtype).toBe("m.file"); + expect(content.msgtype).toBe("m.image"); expect(content.format).toBe("org.matrix.custom.html"); expect(content.formatted_body).toContain("caption"); expect(content.url).toBe("mxc://example/file");