darkfire514's picture
Upload 2526 files
fb4d8fe verified
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<string, unknown>)[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<string[] | null | undefined> = [
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 "<media:image>";
}
if (message.videoMessage) {
return "<media:video>";
}
if (message.audioMessage) {
return "<media:audio>";
}
if (message.documentMessage) {
return "<media:document>";
}
if (message.stickerMessage) {
return "<media:sticker>";
}
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 "<contact>";
}
return `<contact: ${label}>`;
}
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 `<contacts: ${total} ${suffix}>`;
}
const remaining = Math.max(total - cleaned.length, 0);
const suffix = remaining > 0 ? ` +${remaining} more` : "";
return `<contacts: ${cleaned.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 { 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,
};
}