fix(whatsapp): resolve lid mappings for inbound

This commit is contained in:
Peter Steinberger
2026-01-09 21:46:11 +01:00
parent 5fa26bfec7
commit a5065b354e
5 changed files with 208 additions and 38 deletions

View File

@@ -1,6 +1,7 @@
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { resolveOAuthDir } from "./config/paths.js";
import { logVerbose, shouldLogVerbose } from "./globals.js";
export async function ensureDir(dir: string) {
@@ -61,36 +62,93 @@ export function toWhatsappJid(number: string): string {
return `${digits}@s.whatsapp.net`;
}
export function jidToE164(jid: string): string | null {
export type JidToE164Options = {
authDir?: string;
lidMappingDirs?: string[];
logMissing?: boolean;
};
type LidLookup = {
getPNForLID?: (jid: string) => Promise<string | null>;
};
function resolveLidMappingDirs(opts?: JidToE164Options): string[] {
const dirs = new Set<string>();
const addDir = (dir?: string | null) => {
if (!dir) return;
dirs.add(resolveUserPath(dir));
};
addDir(opts?.authDir);
for (const dir of opts?.lidMappingDirs ?? []) addDir(dir);
addDir(resolveOAuthDir());
addDir(path.join(CONFIG_DIR, "credentials"));
return [...dirs];
}
function readLidReverseMapping(
lid: string,
opts?: JidToE164Options,
): string | null {
const mappingFilename = `lid-mapping-${lid}_reverse.json`;
const mappingDirs = resolveLidMappingDirs(opts);
for (const dir of mappingDirs) {
const mappingPath = path.join(dir, mappingFilename);
try {
const data = fs.readFileSync(mappingPath, "utf8");
const phone = JSON.parse(data) as string | number | null;
if (phone === null || phone === undefined) continue;
return normalizeE164(String(phone));
} catch {
// Try the next location.
}
}
return null;
}
export function jidToE164(jid: string, opts?: JidToE164Options): string | null {
// Convert a WhatsApp JID (with optional device suffix, e.g. 1234:1@s.whatsapp.net) back to +1234.
const match = jid.match(/^(\d+)(?::\d+)?@s\.whatsapp\.net$/);
const match = jid.match(/^(\d+)(?::\d+)?@(s\.whatsapp\.net|hosted)$/);
if (match) {
const digits = match[1];
return `+${digits}`;
}
// Support @lid format (WhatsApp Linked ID) - look up reverse mapping
const lidMatch = jid.match(/^(\d+)(?::\d+)?@lid$/);
const lidMatch = jid.match(/^(\d+)(?::\d+)?@(lid|hosted\.lid)$/);
if (lidMatch) {
const lid = lidMatch[1];
try {
const mappingPath = `${CONFIG_DIR}/credentials/lid-mapping-${lid}_reverse.json`;
const data = fs.readFileSync(mappingPath, "utf8");
const phone = JSON.parse(data);
if (phone) return `+${phone}`;
} catch {
if (shouldLogVerbose()) {
logVerbose(
`LID mapping not found for ${lid}; skipping inbound message`,
);
}
// Mapping not found, fall through
const phone = readLidReverseMapping(lid, opts);
if (phone) return phone;
const shouldLog = opts?.logMissing ?? shouldLogVerbose();
if (shouldLog) {
logVerbose(`LID mapping not found for ${lid}; skipping inbound message`);
}
}
return null;
}
export async function resolveJidToE164(
jid: string | null | undefined,
opts?: JidToE164Options & { lidLookup?: LidLookup },
): Promise<string | null> {
if (!jid) return null;
const direct = jidToE164(jid, opts);
if (direct) return direct;
if (!/(@lid|@hosted\.lid)$/.test(jid)) return null;
if (!opts?.lidLookup?.getPNForLID) return null;
try {
const pnJid = await opts.lidLookup.getPNForLID(jid);
if (!pnJid) return null;
return jidToE164(pnJid, opts);
} catch (err) {
if (shouldLogVerbose()) {
logVerbose(`LID mapping lookup failed for ${jid}: ${String(err)}`);
}
return null;
}
}
export function sleep(ms: number) {
return new Promise((resolve) => setTimeout(resolve, ms));
}