import type { proto } from "@whiskeysockets/baileys"; import { extractMessageContent, getContentType, normalizeMessageContent, } from "@whiskeysockets/baileys"; import { formatLocationText, type NormalizedLocation } from "../../channels/location.js"; import { logVerbose } from "../../globals.js"; import { jidToE164 } from "../../utils.js"; import { parseVcard } from "../vcard.js"; function unwrapMessage(message: proto.IMessage | undefined): proto.IMessage | undefined { const normalized = normalizeMessageContent(message); return normalized; } function extractContextInfo(message: proto.IMessage | undefined): proto.IContextInfo | undefined { if (!message) { return undefined; } const contentType = getContentType(message); const candidate = contentType ? (message as Record)[contentType] : undefined; const contextInfo = candidate && typeof candidate === "object" && "contextInfo" in candidate ? (candidate as { contextInfo?: proto.IContextInfo }).contextInfo : undefined; if (contextInfo) { return contextInfo; } const fallback = message.extendedTextMessage?.contextInfo ?? message.imageMessage?.contextInfo ?? message.videoMessage?.contextInfo ?? message.documentMessage?.contextInfo ?? message.audioMessage?.contextInfo ?? message.stickerMessage?.contextInfo ?? message.buttonsResponseMessage?.contextInfo ?? message.listResponseMessage?.contextInfo ?? message.templateButtonReplyMessage?.contextInfo ?? message.interactiveResponseMessage?.contextInfo ?? message.buttonsMessage?.contextInfo ?? message.listMessage?.contextInfo; if (fallback) { return fallback; } for (const value of Object.values(message)) { if (!value || typeof value !== "object") { continue; } if (!("contextInfo" in value)) { continue; } const candidateContext = (value as { contextInfo?: proto.IContextInfo }).contextInfo; if (candidateContext) { return candidateContext; } } return undefined; } export function extractMentionedJids(rawMessage: proto.IMessage | undefined): string[] | undefined { const message = unwrapMessage(rawMessage); if (!message) { return undefined; } const candidates: Array = [ message.extendedTextMessage?.contextInfo?.mentionedJid, message.extendedTextMessage?.contextInfo?.quotedMessage?.extendedTextMessage?.contextInfo ?.mentionedJid, message.imageMessage?.contextInfo?.mentionedJid, message.videoMessage?.contextInfo?.mentionedJid, message.documentMessage?.contextInfo?.mentionedJid, message.audioMessage?.contextInfo?.mentionedJid, message.stickerMessage?.contextInfo?.mentionedJid, message.buttonsResponseMessage?.contextInfo?.mentionedJid, message.listResponseMessage?.contextInfo?.mentionedJid, ]; const flattened = candidates.flatMap((arr) => arr ?? []).filter(Boolean); if (flattened.length === 0) { return undefined; } return Array.from(new Set(flattened)); } export function extractText(rawMessage: proto.IMessage | undefined): string | undefined { const message = unwrapMessage(rawMessage); if (!message) { return undefined; } const extracted = extractMessageContent(message); const candidates = [message, extracted && extracted !== message ? extracted : undefined]; for (const candidate of candidates) { if (!candidate) { continue; } if (typeof candidate.conversation === "string" && candidate.conversation.trim()) { return candidate.conversation.trim(); } const extended = candidate.extendedTextMessage?.text; if (extended?.trim()) { return extended.trim(); } const caption = candidate.imageMessage?.caption ?? candidate.videoMessage?.caption ?? candidate.documentMessage?.caption; if (caption?.trim()) { return caption.trim(); } } const contactPlaceholder = extractContactPlaceholder(message) ?? (extracted && extracted !== message ? extractContactPlaceholder(extracted as proto.IMessage | undefined) : undefined); if (contactPlaceholder) { return contactPlaceholder; } return undefined; } export function extractMediaPlaceholder( rawMessage: proto.IMessage | undefined, ): string | undefined { const message = unwrapMessage(rawMessage); if (!message) { return undefined; } if (message.imageMessage) { return ""; } if (message.videoMessage) { return ""; } if (message.audioMessage) { return ""; } if (message.documentMessage) { return ""; } if (message.stickerMessage) { return ""; } return undefined; } function extractContactPlaceholder(rawMessage: proto.IMessage | undefined): string | undefined { const message = unwrapMessage(rawMessage); if (!message) { return undefined; } const contact = message.contactMessage ?? undefined; if (contact) { const { name, phones } = describeContact({ displayName: contact.displayName, vcard: contact.vcard, }); return formatContactPlaceholder(name, phones); } const contactsArray = message.contactsArrayMessage?.contacts ?? undefined; if (!contactsArray || contactsArray.length === 0) { return undefined; } const labels = contactsArray .map((entry) => describeContact({ displayName: entry.displayName, vcard: entry.vcard })) .map((entry) => formatContactLabel(entry.name, entry.phones)) .filter((value): value is string => Boolean(value)); return formatContactsPlaceholder(labels, contactsArray.length); } function describeContact(input: { displayName?: string | null; vcard?: string | null }): { name?: string; phones: string[]; } { const displayName = (input.displayName ?? "").trim(); const parsed = parseVcard(input.vcard ?? undefined); const name = displayName || parsed.name; return { name, phones: parsed.phones }; } function formatContactPlaceholder(name?: string, phones?: string[]): string { const label = formatContactLabel(name, phones); if (!label) { return ""; } return ``; } function formatContactsPlaceholder(labels: string[], total: number): string { const cleaned = labels.map((label) => label.trim()).filter(Boolean); if (cleaned.length === 0) { const suffix = total === 1 ? "contact" : "contacts"; return ``; } const remaining = Math.max(total - cleaned.length, 0); const suffix = remaining > 0 ? ` +${remaining} more` : ""; return ``; } 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 { shown, remaining } = summarizeList(cleaned, cleaned.length, 1); const [primary] = shown; if (!primary) { return undefined; } if (remaining === 0) { return primary; } return `${primary} (+${remaining} more)`; } function summarizeList( values: string[], total: number, maxShown: number, ): { shown: string[]; remaining: number } { const shown = values.slice(0, maxShown); const remaining = Math.max(total - shown.length, 0); return { shown, remaining }; } export function extractLocationData( rawMessage: proto.IMessage | undefined, ): NormalizedLocation | null { const message = unwrapMessage(rawMessage); if (!message) { return null; } const live = message.liveLocationMessage ?? undefined; if (live) { const latitudeRaw = live.degreesLatitude; const longitudeRaw = live.degreesLongitude; if (latitudeRaw != null && longitudeRaw != null) { const latitude = Number(latitudeRaw); const longitude = Number(longitudeRaw); if (Number.isFinite(latitude) && Number.isFinite(longitude)) { return { latitude, longitude, accuracy: live.accuracyInMeters ?? undefined, caption: live.caption ?? undefined, source: "live", isLive: true, }; } } } const location = message.locationMessage ?? undefined; if (location) { const latitudeRaw = location.degreesLatitude; const longitudeRaw = location.degreesLongitude; if (latitudeRaw != null && longitudeRaw != null) { const latitude = Number(latitudeRaw); const longitude = Number(longitudeRaw); if (Number.isFinite(latitude) && Number.isFinite(longitude)) { const isLive = Boolean(location.isLive); return { latitude, longitude, accuracy: location.accuracyInMeters ?? undefined, name: location.name ?? undefined, address: location.address ?? undefined, caption: location.comment ?? undefined, source: isLive ? "live" : location.name || location.address ? "place" : "pin", isLive, }; } } } return null; } export function describeReplyContext(rawMessage: proto.IMessage | undefined): { id?: string; body: string; sender: string; senderJid?: string; senderE164?: string; } | null { const message = unwrapMessage(rawMessage); if (!message) { return null; } const contextInfo = extractContextInfo(message); const quoted = normalizeMessageContent(contextInfo?.quotedMessage as proto.IMessage | undefined); if (!quoted) { return null; } const location = extractLocationData(quoted); const locationText = location ? formatLocationText(location) : undefined; const text = extractText(quoted); let body: string | undefined = [text, locationText].filter(Boolean).join("\n").trim(); if (!body) { body = extractMediaPlaceholder(quoted); } if (!body) { const quotedType = quoted ? getContentType(quoted) : undefined; logVerbose( `Quoted message missing extractable body${quotedType ? ` (type ${quotedType})` : ""}`, ); return null; } const senderJid = contextInfo?.participant ?? undefined; const senderE164 = senderJid ? (jidToE164(senderJid) ?? senderJid) : undefined; const sender = senderE164 ?? "unknown sender"; return { id: contextInfo?.stanzaId ? String(contextInfo.stanzaId) : undefined, body, sender, senderJid, senderE164, }; }