diff --git a/extensions/bluebubbles/src/send.test.ts b/extensions/bluebubbles/src/send.test.ts index 0b8b77a1f..6509ec3bf 100644 --- a/extensions/bluebubbles/src/send.test.ts +++ b/extensions/bluebubbles/src/send.test.ts @@ -187,6 +187,47 @@ describe("send", () => { expect(result).toBe("iMessage;-;+15551234567"); }); + it("returns null when handle only exists in group chat (not DM)", async () => { + // This is the critical fix: if a phone number only exists as a participant in a group chat + // (no direct DM chat), we should NOT send to that group. Return null instead. + mockFetch + .mockResolvedValueOnce({ + ok: true, + json: () => + Promise.resolve({ + data: [ + { + guid: "iMessage;+;group-the-council", + participants: [ + { address: "+12622102921" }, + { address: "+15550001111" }, + { address: "+15550002222" }, + ], + }, + ], + }), + }) + // Empty second page to stop pagination + .mockResolvedValueOnce({ + ok: true, + json: () => Promise.resolve({ data: [] }), + }); + + const target: BlueBubblesSendTarget = { + kind: "handle", + address: "+12622102921", + service: "imessage", + }; + const result = await resolveChatGuidForTarget({ + baseUrl: "http://localhost:1234", + password: "test", + target, + }); + + // Should return null, NOT the group chat GUID + expect(result).toBeNull(); + }); + it("returns null when chat not found", async () => { mockFetch.mockResolvedValueOnce({ ok: true, diff --git a/extensions/bluebubbles/src/send.ts b/extensions/bluebubbles/src/send.ts index 675063d6d..a85ccc4c4 100644 --- a/extensions/bluebubbles/src/send.ts +++ b/extensions/bluebubbles/src/send.ts @@ -257,11 +257,17 @@ export async function resolveChatGuidForTarget(params: { return guid; } if (!participantMatch && guid) { - const participants = extractParticipantAddresses(chat).map((entry) => - normalizeBlueBubblesHandle(entry), - ); - if (participants.includes(normalizedHandle)) { - participantMatch = guid; + // Only consider DM chats (`;-;` separator) as participant matches. + // Group chats (`;+;` separator) should never match when searching by handle/phone. + // This prevents routing "send to +1234567890" to a group chat that contains that number. + const isDmChat = guid.includes(";-;"); + if (isDmChat) { + const participants = extractParticipantAddresses(chat).map((entry) => + normalizeBlueBubblesHandle(entry), + ); + if (participants.includes(normalizedHandle)) { + participantMatch = guid; + } } } }