fix: prefer FN for WhatsApp contact cards (#622) (thanks @mahmoudashraf93)

This commit is contained in:
Peter Steinberger
2026-01-10 00:08:30 +01:00
parent c9c5a71d8b
commit 1277f6e27b
3 changed files with 30 additions and 13 deletions

View File

@@ -41,7 +41,7 @@
- WhatsApp: improve "no active web listener" errors (include account + relink hint). (#612) — thanks @YuriNachos - WhatsApp: improve "no active web listener" errors (include account + relink hint). (#612) — thanks @YuriNachos
- WhatsApp: add broadcast groups for multi-agent replies. (#547) — thanks @pasogott - WhatsApp: add broadcast groups for multi-agent replies. (#547) — thanks @pasogott
- WhatsApp: resolve @lid inbound senders via auth-dir mapping fallback + shared resolver. (#365) - WhatsApp: resolve @lid inbound senders via auth-dir mapping fallback + shared resolver. (#365)
- WhatsApp: treat shared contact cards as inbound messages. (#622) — thanks @mahmoudashraf93 - WhatsApp: treat shared contact cards as inbound messages (prefer vCard FN). (#622) — thanks @mahmoudashraf93
- iMessage: isolate group-ish threads by chat_id. (#535) — thanks @mdahmann - iMessage: isolate group-ish threads by chat_id. (#535) — thanks @mdahmann
- Models: add OAuth expiry checks in doctor, expanded `models status` auth output (missing auth + `--check` exit codes). (#538) — thanks @latitudeki5223 - Models: add OAuth expiry checks in doctor, expanded `models status` auth output (missing auth + `--check` exit codes). (#538) — thanks @latitudeki5223
- Deps: bump Pi to 0.40.0 and drop pi-ai patch (upstream 429 fix). (#543) — thanks @mcinteerj - Deps: bump Pi to 0.40.0 and drop pi-ai patch (upstream 429 fix). (#543) — thanks @mcinteerj

View File

@@ -44,6 +44,22 @@ describe("web inbound helpers", () => {
expect(body).toBe("<contact: Ada Lovelace, +15555550123>"); expect(body).toBe("<contact: Ada Lovelace, +15555550123>");
}); });
it("prefers FN over N in WhatsApp vcards", () => {
const body = extractText({
contactMessage: {
vcard: [
"BEGIN:VCARD",
"VERSION:3.0",
"N:Lovelace;Ada;;;",
"FN:Ada Lovelace",
"TEL;TYPE=CELL:+15555550123",
"END:VCARD",
].join("\n"),
},
} as unknown as import("@whiskeysockets/baileys").proto.IMessage);
expect(body).toBe("<contact: Ada Lovelace, +15555550123>");
});
it("extracts multiple WhatsApp contact cards", () => { it("extracts multiple WhatsApp contact cards", () => {
const body = extractText({ const body = extractText({
contactsArrayMessage: { contactsArrayMessage: {

View File

@@ -735,9 +735,7 @@ export function extractText(
const contactPlaceholder = const contactPlaceholder =
extractContactPlaceholder(message) ?? extractContactPlaceholder(message) ??
(extracted && extracted !== message (extracted && extracted !== message
? extractContactPlaceholder( ? extractContactPlaceholder(extracted as proto.IMessage | undefined)
extracted as proto.IMessage | undefined,
)
: undefined); : undefined);
if (contactPlaceholder) return contactPlaceholder; if (contactPlaceholder) return contactPlaceholder;
return undefined; return undefined;
@@ -791,12 +789,11 @@ function describeContact(input: {
return { name, phone }; return { name, phone };
} }
function parseVcard( function parseVcard(vcard?: string): { name?: string; phones: string[] } {
vcard?: string,
): { name?: string; phones: string[] } {
if (!vcard) return { phones: [] }; if (!vcard) return { phones: [] };
const lines = vcard.split(/\r?\n/); const lines = vcard.split(/\r?\n/);
let name: string | undefined; let nameFromN: string | undefined;
let nameFromFn: string | undefined;
const phones: string[] = []; const phones: string[] = [];
for (const rawLine of lines) { for (const rawLine of lines) {
const line = rawLine.trim(); const line = rawLine.trim();
@@ -808,15 +805,19 @@ function parseVcard(
if (!rawValue) continue; if (!rawValue) continue;
const value = cleanVcardValue(rawValue); const value = cleanVcardValue(rawValue);
if (!value) continue; if (!value) continue;
if ((key === "FN" || key === "N") && !name) { if (key === "FN" && !nameFromFn) {
name = normalizeVcardName(value); nameFromFn = normalizeVcardName(value);
continue;
}
if (key === "N" && !nameFromN) {
nameFromN = normalizeVcardName(value);
continue; continue;
} }
if (key.startsWith("TEL") || key.includes(".TEL")) { if (key.startsWith("TEL") || key.includes(".TEL")) {
phones.push(value); phones.push(value);
} }
} }
return { name, phones }; return { name: nameFromFn ?? nameFromN, phones };
} }
function cleanVcardValue(value: string): string { function cleanVcardValue(value: string): string {
@@ -832,8 +833,8 @@ function normalizeVcardName(value: string): string {
} }
function formatContactPlaceholder(name?: string, phone?: string): string { function formatContactPlaceholder(name?: string, phone?: string): string {
const parts = [name, phone].filter( const parts = [name, phone].filter((value): value is string =>
(value): value is string => Boolean(value), Boolean(value),
); );
if (parts.length === 0) return "<contact>"; if (parts.length === 0) return "<contact>";
return `<contact: ${parts.join(", ")}>`; return `<contact: ${parts.join(", ")}>`;