fix(whatsapp): allow per-message link preview override\n\nWhatsApp messages default to enabling link previews for URLs. This adds\nsupport for overriding this behavior per-message via the \nparameter (e.g. from tool options), consistent with Telegram.\n\nFix: Updated internal WhatsApp Web API layers to pass option\ndown to Baileys .

This commit is contained in:
Rain
2026-02-16 22:13:05 +08:00
committed by Peter Steinberger
parent 312a7f7880
commit 1bef2fc68b
6 changed files with 80 additions and 6 deletions

View File

@@ -0,0 +1,56 @@
import { describe, it, expect, vi, beforeEach } from "vitest";
import { whatsappPlugin } from "./channel.js";
// Mock runtime
const mockSendMessageWhatsApp = vi.fn().mockResolvedValue({ messageId: "123", toJid: "123@s.whatsapp.net" });
vi.mock("./runtime.js", () => ({
getWhatsAppRuntime: () => ({
channel: {
text: { chunkText: (t: string) => [t] },
whatsapp: {
sendMessageWhatsApp: mockSendMessageWhatsApp,
createLoginTool: vi.fn(),
},
},
logging: { shouldLogVerbose: () => false },
}),
}));
describe("whatsappPlugin.outbound.sendText", () => {
beforeEach(() => {
vi.clearAllMocks();
});
it("passes linkPreview option to sendMessageWhatsApp", async () => {
await whatsappPlugin.outbound.sendText({
to: "1234567890",
text: "http://example.com",
// @ts-expect-error - injecting extra param as per runtime behavior
linkPreview: false,
});
expect(mockSendMessageWhatsApp).toHaveBeenCalledWith(
"1234567890",
"http://example.com",
expect.objectContaining({
linkPreview: false,
})
);
});
it("passes linkPreview=undefined when omitted", async () => {
await whatsappPlugin.outbound.sendText({
to: "1234567890",
text: "hello",
});
expect(mockSendMessageWhatsApp).toHaveBeenCalledWith(
"1234567890",
"hello",
expect.objectContaining({
linkPreview: undefined,
})
);
});
});

View File

@@ -290,12 +290,15 @@ export const whatsappPlugin: ChannelPlugin<ResolvedWhatsAppAccount> = {
pollMaxOptions: 12,
resolveTarget: ({ to, allowFrom, mode }) =>
resolveWhatsAppOutboundTarget({ to, allowFrom, mode }),
sendText: async ({ to, text, accountId, deps, gifPlayback }) => {
sendText: async (params) => {
const { to, text, accountId, deps, gifPlayback } = params;
const linkPreview = (params as { linkPreview?: boolean }).linkPreview;
const send = deps?.sendWhatsApp ?? getWhatsAppRuntime().channel.whatsapp.sendMessageWhatsApp;
const result = await send(to, text, {
verbose: false,
accountId: accountId ?? undefined,
gifPlayback,
linkPreview,
});
return { channel: "whatsapp", ...result };
},

View File

@@ -6,6 +6,7 @@ export type ActiveWebSendOptions = {
gifPlayback?: boolean;
accountId?: string;
fileName?: string;
linkPreview?: boolean;
};
export type ActiveWebListener = {

View File

@@ -366,7 +366,7 @@ export async function monitorWebInbox(options: {
const sendApi = createWebSendApi({
sock: {
sendMessage: (jid: string, content: AnyMessageContent) => sock.sendMessage(jid, content),
sendMessage: (jid, content, options) => sock.sendMessage(jid, content, options),
sendPresenceUpdate: (presence, jid?: string) => sock.sendPresenceUpdate(presence, jid),
},
defaultAccountId: options.accountId,

View File

@@ -1,4 +1,4 @@
import type { AnyMessageContent, WAPresence } from "@whiskeysockets/baileys";
import type { AnyMessageContent, MiscMessageGenerationOptions, WAPresence } from "@whiskeysockets/baileys";
import type { ActiveWebSendOptions } from "../active-listener.js";
import { recordChannelActivity } from "../../infra/channel-activity.js";
import { toWhatsappJid } from "../../utils.js";
@@ -19,7 +19,11 @@ function resolveOutboundMessageId(result: unknown): string {
export function createWebSendApi(params: {
sock: {
sendMessage: (jid: string, content: AnyMessageContent) => Promise<unknown>;
sendMessage: (
jid: string,
content: AnyMessageContent,
options?: MiscMessageGenerationOptions,
) => Promise<unknown>;
sendPresenceUpdate: (presence: WAPresence, jid?: string) => Promise<unknown>;
};
defaultAccountId: string;
@@ -63,7 +67,10 @@ export function createWebSendApi(params: {
} else {
payload = { text };
}
const result = await params.sock.sendMessage(jid, payload);
const miscOptions: MiscMessageGenerationOptions = {
linkPreview: sendOptions?.linkPreview === false ? null : undefined,
};
const result = await params.sock.sendMessage(jid, payload, miscOptions);
const accountId = sendOptions?.accountId ?? params.defaultAccountId;
recordWhatsAppOutbound(accountId);
const messageId = resolveOutboundMessageId(result);

View File

@@ -21,6 +21,7 @@ export async function sendMessageWhatsApp(
mediaLocalRoots?: readonly string[];
gifPlayback?: boolean;
accountId?: string;
linkPreview?: boolean;
},
): Promise<{ messageId: string; toJid: string }> {
let text = body;
@@ -75,10 +76,16 @@ export async function sendMessageWhatsApp(
const hasExplicitAccountId = Boolean(options.accountId?.trim());
const accountId = hasExplicitAccountId ? resolvedAccountId : undefined;
const sendOptions: ActiveWebSendOptions | undefined =
options.gifPlayback || accountId || documentFileName
options.gifPlayback ||
options.accountId ||
options.linkPreview !== undefined ||
documentFileName
? {
...(options.gifPlayback ? { gifPlayback: true } : {}),
...(documentFileName ? { fileName: documentFileName } : {}),
...(options.linkPreview !== undefined
? { linkPreview: options.linkPreview }
: {}),
accountId,
}
: undefined;