feat: unify poll support

Co-authored-by: DBH <5251425+dbhurley@users.noreply.github.com>
This commit is contained in:
Peter Steinberger
2026-01-06 04:43:35 +00:00
parent 1f4d9e83ff
commit 0b27964693
19 changed files with 360 additions and 118 deletions

View File

@@ -1,13 +1,9 @@
import type { PollInput } from "../polls.js";
export type ActiveWebSendOptions = {
gifPlayback?: boolean;
};
export type PollOptions = {
question: string;
options: string[];
selectableCount?: number;
};
export type ActiveWebListener = {
sendMessage: (
to: string,
@@ -16,7 +12,7 @@ export type ActiveWebListener = {
mediaType?: string,
options?: ActiveWebSendOptions,
) => Promise<{ messageId: string }>;
sendPoll: (to: string, poll: PollOptions) => Promise<{ messageId: string }>;
sendPoll: (to: string, poll: PollInput) => Promise<{ messageId: string }>;
sendComposingTo: (to: string) => Promise<void>;
close?: () => Promise<void>;
};

View File

@@ -456,6 +456,20 @@ export async function monitorWebInbox(options: {
const result = await sock.sendMessage(jid, payload);
return { messageId: result?.key?.id ?? "unknown" };
},
sendPoll: async (
to: string,
poll: { question: string; options: string[]; maxSelections?: number },
): Promise<{ messageId: string }> => {
const jid = toWhatsappJid(to);
const result = await sock.sendMessage(jid, {
poll: {
name: poll.question,
values: poll.options,
selectableCount: poll.maxSelections ?? 1,
},
});
return { messageId: result?.key?.id ?? "unknown" };
},
/**
* Send typing indicator ("composing") to a chat.
* Used after IPC send to show more messages are coming.

View File

@@ -8,15 +8,16 @@ vi.mock("./media.js", () => ({
loadWebMedia: (...args: unknown[]) => loadWebMediaMock(...args),
}));
import { sendMessageWhatsApp } from "./outbound.js";
import { sendMessageWhatsApp, sendPollWhatsApp } from "./outbound.js";
describe("web outbound", () => {
const sendComposingTo = vi.fn(async () => {});
const sendMessage = vi.fn(async () => ({ messageId: "msg123" }));
const sendPoll = vi.fn(async () => ({ messageId: "poll123" }));
beforeEach(() => {
vi.clearAllMocks();
setActiveWebListener({ sendComposingTo, sendMessage });
setActiveWebListener({ sendComposingTo, sendMessage, sendPoll });
});
afterEach(() => {
@@ -137,4 +138,22 @@ describe("web outbound", () => {
"application/pdf",
);
});
it("sends polls via active listener", async () => {
const result = await sendPollWhatsApp(
"+1555",
{ question: "Lunch?", options: ["Pizza", "Sushi"], maxSelections: 2 },
{ verbose: false },
);
expect(result).toEqual({
messageId: "poll123",
toJid: "1555@s.whatsapp.net",
});
expect(sendPoll).toHaveBeenCalledWith("+1555", {
question: "Lunch?",
options: ["Pizza", "Sushi"],
maxSelections: 2,
durationHours: undefined,
});
});
});

View File

@@ -1,10 +1,10 @@
import { randomUUID } from "node:crypto";
import { createSubsystemLogger, getChildLogger } from "../logging.js";
import { normalizePollInput, type PollInput } from "../polls.js";
import { toWhatsappJid } from "../utils.js";
import {
type ActiveWebSendOptions,
type PollOptions,
getActiveWebListener,
} from "./active-listener.js";
import { loadWebMedia } from "./media.js";
@@ -86,11 +86,10 @@ export async function sendMessageWhatsApp(
throw err;
}
}
export async function sendPollWhatsApp(
to: string,
poll: PollOptions,
options: { verbose: boolean },
poll: PollInput,
_options: { verbose: boolean },
): Promise<{ messageId: string; toJid: string }> {
const correlationId = randomUUID();
const startedAt = Date.now();
@@ -107,12 +106,18 @@ export async function sendPollWhatsApp(
});
try {
const jid = toWhatsappJid(to);
outboundLog.info(`Sending poll -> ${jid}: "${poll.question}"`);
const normalized = normalizePollInput(poll, { maxOptions: 12 });
outboundLog.info(`Sending poll -> ${jid}: "${normalized.question}"`);
logger.info(
{ jid, question: poll.question, optionCount: poll.options.length },
{
jid,
question: normalized.question,
optionCount: normalized.options.length,
maxSelections: normalized.maxSelections,
},
"sending poll",
);
const result = await active.sendPoll(to, poll);
const result = await active.sendPoll(to, normalized);
const messageId =
(result as { messageId?: string })?.messageId ?? "unknown";
const durationMs = Date.now() - startedAt;