fix: suppress whatsapp pairing in self-phone mode

This commit is contained in:
Peter Steinberger
2026-01-07 20:40:24 +01:00
parent 8c48220a60
commit ef644b8369
9 changed files with 187 additions and 32 deletions

View File

@@ -12,6 +12,7 @@ export type ResolvedWhatsAppAccount = {
enabled: boolean;
authDir: string;
isLegacyAuthDir: boolean;
selfChatMode?: boolean;
allowFrom?: string[];
groupAllowFrom?: string[];
groupPolicy?: GroupPolicy;
@@ -103,6 +104,7 @@ export function resolveWhatsAppAccount(params: {
enabled,
authDir,
isLegacyAuthDir: isLegacy,
selfChatMode: accountCfg?.selfChatMode ?? params.cfg.whatsapp?.selfChatMode,
allowFrom: accountCfg?.allowFrom ?? params.cfg.whatsapp?.allowFrom,
groupAllowFrom:
accountCfg?.groupAllowFrom ?? params.cfg.whatsapp?.groupAllowFrom,

View File

@@ -202,6 +202,9 @@ export async function monitorWebInbox(options: {
: undefined);
const isSamePhone = from === selfE164;
const isSelfChat = isSelfChatMode(selfE164, configuredAllowFrom);
const isFromMe = Boolean(msg.key?.fromMe);
const selfChatMode = account.selfChatMode ?? false;
const selfPhoneMode = selfChatMode || isSelfChat;
// Pre-compute normalized allowlists for filtering
const dmHasWildcard = allowFrom?.includes("*") ?? false;
@@ -246,6 +249,12 @@ export async function monitorWebInbox(options: {
// DM access control (secure defaults): "pairing" (default) / "allowlist" / "open" / "disabled"
if (!group) {
if (isFromMe && !isSamePhone && selfPhoneMode) {
logVerbose(
"Skipping outbound self-phone DM (fromMe); no pairing reply needed.",
);
continue;
}
if (dmPolicy === "disabled") {
logVerbose("Blocked dm (dmPolicy: disabled)");
continue;

View File

@@ -1099,6 +1099,110 @@ describe("web monitor inbox", () => {
await listener.close();
});
it("skips pairing replies for outbound DMs in same-phone mode", async () => {
mockLoadConfig.mockReturnValue({
whatsapp: {
dmPolicy: "pairing",
selfChatMode: true,
},
messages: {
messagePrefix: undefined,
responsePrefix: undefined,
},
});
const onMessage = vi.fn();
const listener = await monitorWebInbox({ verbose: false, onMessage });
const sock = await createWaSocket();
const upsert = {
type: "notify",
messages: [
{
key: {
id: "fromme-1",
fromMe: true,
remoteJid: "999@s.whatsapp.net",
},
message: { conversation: "hello" },
messageTimestamp: 1_700_000_000,
},
],
};
sock.ev.emit("messages.upsert", upsert);
await new Promise((resolve) => setImmediate(resolve));
expect(onMessage).not.toHaveBeenCalled();
expect(upsertPairingRequestMock).not.toHaveBeenCalled();
expect(sock.sendMessage).not.toHaveBeenCalled();
mockLoadConfig.mockReturnValue({
whatsapp: {
allowFrom: ["*"],
},
messages: {
messagePrefix: undefined,
responsePrefix: undefined,
},
});
await listener.close();
});
it("still pairs outbound DMs when same-phone mode is disabled", async () => {
mockLoadConfig.mockReturnValue({
whatsapp: {
dmPolicy: "pairing",
selfChatMode: false,
},
messages: {
messagePrefix: undefined,
responsePrefix: undefined,
},
});
const onMessage = vi.fn();
const listener = await monitorWebInbox({ verbose: false, onMessage });
const sock = await createWaSocket();
const upsert = {
type: "notify",
messages: [
{
key: {
id: "fromme-2",
fromMe: true,
remoteJid: "999@s.whatsapp.net",
},
message: { conversation: "hello again" },
messageTimestamp: 1_700_000_000,
},
],
};
sock.ev.emit("messages.upsert", upsert);
await new Promise((resolve) => setImmediate(resolve));
expect(onMessage).not.toHaveBeenCalled();
expect(upsertPairingRequestMock).toHaveBeenCalledTimes(1);
expect(sock.sendMessage).toHaveBeenCalledWith("999@s.whatsapp.net", {
text: expect.stringContaining("Pairing code: PAIRCODE"),
});
mockLoadConfig.mockReturnValue({
whatsapp: {
allowFrom: ["*"],
},
messages: {
messagePrefix: undefined,
responsePrefix: undefined,
},
});
await listener.close();
});
it("handles append messages by marking them read but skipping auto-reply", async () => {
const onMessage = vi.fn();
const listener = await monitorWebInbox({ verbose: false, onMessage });