feat(providers): normalize location parsing

This commit is contained in:
Peter Steinberger
2026-01-06 06:30:12 +01:00
parent 255e77f530
commit b759cb6f37
10 changed files with 421 additions and 66 deletions

View File

@@ -39,6 +39,7 @@ import { emitHeartbeatEvent } from "../infra/heartbeat-events.js";
import { enqueueSystemEvent } from "../infra/system-events.js";
import { registerUnhandledRejectionHandler } from "../infra/unhandled-rejections.js";
import { createSubsystemLogger, getChildLogger } from "../logging.js";
import { toLocationContext } from "../providers/location.js";
import { defaultRuntime, type RuntimeEnv } from "../runtime.js";
import { isSelfChatMode, jidToE164, normalizeE164 } from "../utils.js";
import { setActiveWebListener } from "./active-listener.js";
@@ -1216,6 +1217,7 @@ export async function monitorWebProvider(
SenderName: msg.senderName,
SenderE164: msg.senderE164,
WasMentioned: msg.wasMentioned,
...(msg.location ? toLocationContext(msg.location) : {}),
Surface: "whatsapp",
},
cfg,

View File

@@ -1,6 +1,10 @@
import { describe, expect, it } from "vitest";
import { extractMediaPlaceholder, extractText } from "./inbound.js";
import {
extractLocationData,
extractMediaPlaceholder,
extractText,
} from "./inbound.js";
describe("web inbound helpers", () => {
it("prefers the main conversation body", () => {
@@ -45,4 +49,46 @@ describe("web inbound helpers", () => {
} as unknown as import("@whiskeysockets/baileys").proto.IMessage),
).toBe("<media:audio>");
});
it("extracts WhatsApp location messages", () => {
const location = extractLocationData({
locationMessage: {
degreesLatitude: 48.858844,
degreesLongitude: 2.294351,
name: "Eiffel Tower",
address: "Champ de Mars, Paris",
accuracyInMeters: 12,
comment: "Meet here",
},
} as unknown as import("@whiskeysockets/baileys").proto.IMessage);
expect(location).toEqual({
latitude: 48.858844,
longitude: 2.294351,
accuracy: 12,
name: "Eiffel Tower",
address: "Champ de Mars, Paris",
caption: "Meet here",
source: "place",
isLive: false,
});
});
it("extracts WhatsApp live location messages", () => {
const location = extractLocationData({
liveLocationMessage: {
degreesLatitude: 37.819929,
degreesLongitude: -122.478255,
accuracyInMeters: 20,
caption: "On the move",
},
} as unknown as import("@whiskeysockets/baileys").proto.IMessage);
expect(location).toEqual({
latitude: 37.819929,
longitude: -122.478255,
accuracy: 20,
caption: "On the move",
source: "live",
isLive: true,
});
});
});

View File

@@ -16,6 +16,10 @@ import { loadConfig } from "../config/config.js";
import { logVerbose, shouldLogVerbose } from "../globals.js";
import { createSubsystemLogger, getChildLogger } from "../logging.js";
import { saveMediaBuffer } from "../media/store.js";
import {
formatLocationText,
type NormalizedLocation,
} from "../providers/location.js";
import {
isSelfChatMode,
jidToE164,
@@ -56,6 +60,7 @@ export type WebInboundMessage = {
mentionedJids?: string[];
selfJid?: string | null;
selfE164?: string | null;
location?: NormalizedLocation;
sendComposing: () => Promise<void>;
reply: (text: string) => Promise<void>;
sendMedia: (payload: AnyMessageContent) => Promise<void>;
@@ -241,7 +246,12 @@ export async function monitorWebInbox(options: {
// but we skip triggering the auto-reply logic to avoid spamming old context.
if (upsert.type === "append") continue;
const location = extractLocationData(msg.message ?? undefined);
const locationText = location ? formatLocationText(location) : undefined;
let body = extractText(msg.message ?? undefined);
if (locationText) {
body = [body, locationText].filter(Boolean).join("\n").trim();
}
if (!body) {
body = extractMediaPlaceholder(msg.message ?? undefined);
if (!body) continue;
@@ -319,6 +329,7 @@ export async function monitorWebInbox(options: {
mentionedJids: mentionedJids ?? undefined,
selfJid,
selfE164,
location: location ?? undefined,
sendComposing,
reply,
sendMedia,
@@ -598,6 +609,62 @@ export function extractMediaPlaceholder(
return undefined;
}
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;
}
function describeReplyContext(rawMessage: proto.IMessage | undefined): {
id?: string;
body: string;
@@ -610,7 +677,11 @@ function describeReplyContext(rawMessage: proto.IMessage | undefined): {
contextInfo?.quotedMessage as proto.IMessage | undefined,
) as proto.IMessage | undefined;
if (!quoted) return null;
const body = extractText(quoted) ?? extractMediaPlaceholder(quoted);
const location = extractLocationData(quoted);
const locationText = location ? formatLocationText(location) : undefined;
const text = extractText(quoted);
let body = [text, locationText].filter(Boolean).join("\n").trim();
if (!body) body = extractMediaPlaceholder(quoted);
if (!body) {
const quotedType = quoted ? getContentType(quoted) : undefined;
logVerbose(