Merge pull request #625 from mahmoudashraf93/fix/whatsapp-contact-cards-multi
fix: include numbers for WhatsApp contact arrays
This commit is contained in:
@@ -3,6 +3,7 @@
|
|||||||
## Unreleased
|
## Unreleased
|
||||||
|
|
||||||
- WhatsApp: refactor vCard parsing helper and improve empty contact card summaries. (#624) — thanks @steipete
|
- WhatsApp: refactor vCard parsing helper and improve empty contact card summaries. (#624) — thanks @steipete
|
||||||
|
- WhatsApp: include phone numbers when multiple contacts are shared. (#625) — thanks @mahmoudashraf93
|
||||||
- Pairing: cap pending DM pairing requests at 3 per provider and avoid pairing replies for outbound DMs. — thanks @steipete
|
- Pairing: cap pending DM pairing requests at 3 per provider and avoid pairing replies for outbound DMs. — thanks @steipete
|
||||||
- macOS: replace relay smoke test with version check in packaging script. (#615) — thanks @YuriNachos
|
- macOS: replace relay smoke test with version check in packaging script. (#615) — thanks @YuriNachos
|
||||||
- macOS: avoid clearing Launch at Login during app initialization. (#607) — thanks @wes-davis
|
- macOS: avoid clearing Launch at Login during app initialization. (#607) — thanks @wes-davis
|
||||||
|
|||||||
@@ -60,18 +60,94 @@ describe("web inbound helpers", () => {
|
|||||||
expect(body).toBe("<contact: Ada Lovelace, +15555550123>");
|
expect(body).toBe("<contact: Ada Lovelace, +15555550123>");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("normalizes tel: prefixes in WhatsApp vcards", () => {
|
||||||
|
const body = extractText({
|
||||||
|
contactMessage: {
|
||||||
|
vcard: [
|
||||||
|
"BEGIN:VCARD",
|
||||||
|
"VERSION:3.0",
|
||||||
|
"FN:Ada Lovelace",
|
||||||
|
"TEL;TYPE=CELL:tel:+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: {
|
||||||
contacts: [
|
contacts: [
|
||||||
{ displayName: "Alice" },
|
{
|
||||||
{ displayName: "Bob" },
|
displayName: "Alice",
|
||||||
{ displayName: "Charlie" },
|
vcard: [
|
||||||
{ displayName: "Dana" },
|
"BEGIN:VCARD",
|
||||||
|
"VERSION:3.0",
|
||||||
|
"FN:Alice",
|
||||||
|
"TEL;TYPE=CELL:+15555550101",
|
||||||
|
"END:VCARD",
|
||||||
|
].join("\n"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: "Bob",
|
||||||
|
vcard: [
|
||||||
|
"BEGIN:VCARD",
|
||||||
|
"VERSION:3.0",
|
||||||
|
"FN:Bob",
|
||||||
|
"TEL;TYPE=CELL:+15555550102",
|
||||||
|
"END:VCARD",
|
||||||
|
].join("\n"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: "Charlie",
|
||||||
|
vcard: [
|
||||||
|
"BEGIN:VCARD",
|
||||||
|
"VERSION:3.0",
|
||||||
|
"FN:Charlie",
|
||||||
|
"TEL;TYPE=CELL:+15555550103",
|
||||||
|
"TEL;TYPE=HOME:+15555550104",
|
||||||
|
"END:VCARD",
|
||||||
|
].join("\n"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: "Dana",
|
||||||
|
vcard: [
|
||||||
|
"BEGIN:VCARD",
|
||||||
|
"VERSION:3.0",
|
||||||
|
"FN:Dana",
|
||||||
|
"TEL;TYPE=CELL:+15555550105",
|
||||||
|
"END:VCARD",
|
||||||
|
].join("\n"),
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
} as unknown as import("@whiskeysockets/baileys").proto.IMessage);
|
} as unknown as import("@whiskeysockets/baileys").proto.IMessage);
|
||||||
expect(body).toBe("<contacts: Alice, Bob, Charlie +1 more>");
|
expect(body).toBe(
|
||||||
|
"<contacts: Alice, +15555550101, Bob, +15555550102, Charlie, +15555550103 (+1 more) +1 more>",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("counts empty WhatsApp contact cards in array summaries", () => {
|
||||||
|
const body = extractText({
|
||||||
|
contactsArrayMessage: {
|
||||||
|
contacts: [
|
||||||
|
{
|
||||||
|
displayName: "Alice",
|
||||||
|
vcard: [
|
||||||
|
"BEGIN:VCARD",
|
||||||
|
"VERSION:3.0",
|
||||||
|
"FN:Alice",
|
||||||
|
"TEL;TYPE=CELL:+15555550101",
|
||||||
|
"END:VCARD",
|
||||||
|
].join("\n"),
|
||||||
|
},
|
||||||
|
{},
|
||||||
|
{},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
} as unknown as import("@whiskeysockets/baileys").proto.IMessage);
|
||||||
|
expect(body).toBe("<contacts: Alice, +15555550101 +2 more>");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("summarizes empty WhatsApp contact cards with a count", () => {
|
it("summarizes empty WhatsApp contact cards with a count", () => {
|
||||||
|
|||||||
@@ -762,11 +762,11 @@ function extractContactPlaceholder(
|
|||||||
if (!message) return undefined;
|
if (!message) return undefined;
|
||||||
const contact = message.contactMessage ?? undefined;
|
const contact = message.contactMessage ?? undefined;
|
||||||
if (contact) {
|
if (contact) {
|
||||||
const { name, phone } = describeContact({
|
const { name, phones } = describeContact({
|
||||||
displayName: contact.displayName,
|
displayName: contact.displayName,
|
||||||
vcard: contact.vcard,
|
vcard: contact.vcard,
|
||||||
});
|
});
|
||||||
return formatContactPlaceholder(name, phone);
|
return formatContactPlaceholder(name, phones);
|
||||||
}
|
}
|
||||||
const contactsArray = message.contactsArrayMessage?.contacts ?? undefined;
|
const contactsArray = message.contactsArrayMessage?.contacts ?? undefined;
|
||||||
if (!contactsArray || contactsArray.length === 0) return undefined;
|
if (!contactsArray || contactsArray.length === 0) return undefined;
|
||||||
@@ -774,7 +774,7 @@ function extractContactPlaceholder(
|
|||||||
.map((entry) =>
|
.map((entry) =>
|
||||||
describeContact({ displayName: entry.displayName, vcard: entry.vcard }),
|
describeContact({ displayName: entry.displayName, vcard: entry.vcard }),
|
||||||
)
|
)
|
||||||
.map((entry) => entry.name ?? entry.phone)
|
.map((entry) => formatContactLabel(entry.name, entry.phones))
|
||||||
.filter((value): value is string => Boolean(value));
|
.filter((value): value is string => Boolean(value));
|
||||||
return formatContactsPlaceholder(labels, contactsArray.length);
|
return formatContactsPlaceholder(labels, contactsArray.length);
|
||||||
}
|
}
|
||||||
@@ -782,20 +782,17 @@ function extractContactPlaceholder(
|
|||||||
function describeContact(input: {
|
function describeContact(input: {
|
||||||
displayName?: string | null;
|
displayName?: string | null;
|
||||||
vcard?: string | null;
|
vcard?: string | null;
|
||||||
}): { name?: string; phone?: string } {
|
}): { name?: string; phones: string[] } {
|
||||||
const displayName = (input.displayName ?? "").trim();
|
const displayName = (input.displayName ?? "").trim();
|
||||||
const parsed = parseVcard(input.vcard ?? undefined);
|
const parsed = parseVcard(input.vcard ?? undefined);
|
||||||
const name = displayName || parsed.name;
|
const name = displayName || parsed.name;
|
||||||
const phone = parsed.phones[0];
|
return { name, phones: parsed.phones };
|
||||||
return { name, phone };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatContactPlaceholder(name?: string, phone?: string): string {
|
function formatContactPlaceholder(name?: string, phones?: string[]): string {
|
||||||
const parts = [name, phone].filter((value): value is string =>
|
const label = formatContactLabel(name, phones);
|
||||||
Boolean(value),
|
if (!label) return "<contact>";
|
||||||
);
|
return `<contact: ${label}>`;
|
||||||
if (parts.length === 0) return "<contact>";
|
|
||||||
return `<contact: ${parts.join(", ")}>`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatContactsPlaceholder(labels: string[], total: number): string {
|
function formatContactsPlaceholder(labels: string[], total: number): string {
|
||||||
@@ -810,6 +807,27 @@ function formatContactsPlaceholder(labels: string[], total: number): string {
|
|||||||
return `<contacts: ${shown.join(", ")}${suffix}>`;
|
return `<contacts: ${shown.join(", ")}${suffix}>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function formatContactLabel(
|
||||||
|
name?: string,
|
||||||
|
phones?: string[],
|
||||||
|
): string | undefined {
|
||||||
|
const phoneLabel = formatPhoneList(phones);
|
||||||
|
const parts = [name, phoneLabel].filter((value): value is string =>
|
||||||
|
Boolean(value),
|
||||||
|
);
|
||||||
|
if (parts.length === 0) return undefined;
|
||||||
|
return parts.join(", ");
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatPhoneList(phones?: string[]): string | undefined {
|
||||||
|
const cleaned = phones?.map((phone) => phone.trim()).filter(Boolean) ?? [];
|
||||||
|
if (cleaned.length === 0) return undefined;
|
||||||
|
const [primary, ...rest] = cleaned;
|
||||||
|
if (!primary) return undefined;
|
||||||
|
if (rest.length === 0) return primary;
|
||||||
|
return `${primary} (+${rest.length} more)`;
|
||||||
|
}
|
||||||
|
|
||||||
export function extractLocationData(
|
export function extractLocationData(
|
||||||
rawMessage: proto.IMessage | undefined,
|
rawMessage: proto.IMessage | undefined,
|
||||||
): NormalizedLocation | null {
|
): NormalizedLocation | null {
|
||||||
|
|||||||
@@ -32,7 +32,8 @@ export function parseVcard(vcard?: string): ParsedVcard {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (baseKey === "TEL") {
|
if (baseKey === "TEL") {
|
||||||
phones.push(value);
|
const phone = normalizeVcardPhone(value);
|
||||||
|
if (phone) phones.push(phone);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return { name: nameFromFn ?? nameFromN, phones };
|
return { name: nameFromFn ?? nameFromN, phones };
|
||||||
@@ -56,3 +57,12 @@ function cleanVcardValue(value: string): string {
|
|||||||
function normalizeVcardName(value: string): string {
|
function normalizeVcardName(value: string): string {
|
||||||
return value.replace(/;/g, " ").replace(/\s+/g, " ").trim();
|
return value.replace(/;/g, " ").replace(/\s+/g, " ").trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function normalizeVcardPhone(value: string): string {
|
||||||
|
const trimmed = value.trim();
|
||||||
|
if (!trimmed) return "";
|
||||||
|
if (trimmed.toLowerCase().startsWith("tel:")) {
|
||||||
|
return trimmed.slice(4).trim();
|
||||||
|
}
|
||||||
|
return trimmed;
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user