npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

lidsync

v5.0.4

Published

LID → JID Identity Resolver para Baileys. Cache LRU, índice invertido, resolución batch y auto-aprendizaje desde eventos.

Readme


¿Qué es LidSync?

WhatsApp introdujo los LIDs como identificadores de privacidad que ocultan el número real de un usuario. Cuando un bot opera en grupos modernos de WhatsApp, los remitentes llegan como:

170360431460562@lid

en lugar del número real. LidSync resuelve esos LIDs al JID real correspondiente:

170360431460562@lid  →  [email protected]

LidSync no es un parche externo — sigue de cerca la arquitectura interna de Baileys. Usa el signalRepository.lidMapping que Baileys expone nativamente, se suscribe a los mismos eventos que Baileys emite y escribe de vuelta en el store de señal cuando aprende nuevos pares.


Estado

La librería funciona con la versión actual de Baileys.

LidSync se actualiza cuando Baileys cambia su implementación LID interna. Las actualizaciones normalmente siguen el ritmo de Baileys, aunque si un cambio crítico lo requiere, se lanza un parche de inmediato.


Instalación

npm install lidsync

O via git para siempre tener la última versión:

npm install git+https://github.com/Neykoor/LidSync.git

Requisitos:

  • Node.js >= 18
  • @whiskeysockets/baileys >= 6.7.0 (peer dependency)

Inicio rápido

import { makeWASocket, useMultiFileAuthState } from "@whiskeysockets/baileys";
import { pluginLid } from "lidsync";

async function start() {
  const { state, saveCreds } = await useMultiFileAuthState("auth_session");

  let sock = makeWASocket({ auth: state });

  // Inyectar LidSync — debe ir antes de store.bind()
  sock = pluginLid(sock, { store });

  store.bind(sock.ev);
  sock.ev.on("creds.update", saveCreds);

  sock.ev.on("messages.upsert", async ({ messages }) => {
    const msg = messages[0];
    if (!msg.message || msg.key.fromMe) return;

    const sender = msg.key.participant || msg.key.remoteJid;
    const jid = await sock.lid.resolve(sender);

    console.log(`Mensaje de: ${jid ?? sender}`);
  });
}

start();

pluginLid debe llamarse antes de store.bind() para que el resolver ya esté suscrito cuando lleguen los primeros eventos de contactos.


Ejemplo completo — Welcome con LID resuelto

Este es el caso más común: enviar un mensaje de bienvenida cuando alguien entra a un grupo, resolviendo su LID al número real para mencionarlo correctamente.

import {
  makeWASocket,
  useMultiFileAuthState,
  makeInMemoryStore,
  jidNormalizedUser,
} from "@whiskeysockets/baileys";
import { pluginLid } from "lidsync";
import pino from "pino";

const store = makeInMemoryStore({
  logger: pino().child({ level: "silent", stream: "store" }),
});

store.readFromFile("./baileys_store.json");
setInterval(() => store.writeToFile("./baileys_store.json"), 10_000);

async function start() {
  const { state, saveCreds } = await useMultiFileAuthState("./session");

  let sock = makeWASocket({
    auth: state,
    printQRInTerminal: true,
    logger: pino({ level: "silent" }),
  });

  // 1. Inyectar LidSync antes de store.bind()
  sock = pluginLid(sock, { store });
  store.bind(sock.ev);

  sock.ev.on("creds.update", saveCreds);

  sock.ev.on("connection.update", ({ connection }) => {
    if (connection === "open") {
      console.log("Bot conectado y LidSync activado.");
      // Si el store carga datos desde disco, forzar sincronización aquí
      sock.lid.syncStore();
    }
  });

  // Welcome con resolución de LID
  sock.ev.on("group-participants.update", async ({ id, participants, action }) => {
    if (action !== "add") return;

    // Resolver todos los LIDs del batch de una vez
    const resolved = await sock.lid.resolveParticipants(participants);

    for (const p of participants) {
      const lid = p.lid || (p.id?.endsWith("@lid") ? p.id : null);
      const jid = resolved.get(lid || p.id) || p.id;

      await sock.sendMessage(id, {
        text: `¡Bienvenido @${jid.split("@")[0]}! 👋`,
        mentions: [jid],
      });
    }
  });
}

start();

¿Por qué usar resolveParticipants en el welcome?

Cuando varios usuarios entran al mismo tiempo, resolveParticipants resuelve el lote completo en una sola operación batch. Es más eficiente que llamar resolve() uno a uno y garantiza que la mención llegue con el JID real, no con el LID crudo.

Nota: Si el LID aún no tiene mapeo conocido, resolved.get(...) devuelve undefined y el fallback || p.id usa el LID original. El mensaje se envía igual — WhatsApp lo entrega correctamente, aunque la mención visual puede quedar sin nombre en clientes muy nuevos.


Cómo funciona

LidSync resuelve mediante tres capas en cascada:

| Nivel | Fuente | Detalle | |:---:|---|---| | 1 | Cache LRU | TTL de 24 h con refresco automático en cada lectura. Máx 7 500 entradas. | | 2 | Índice invertido | Map<LID, JID> en memoria, O(1). Se promueve al caché en cada consulta. | | 3 | Signal Repository | Fallback al lidMapping interno de Baileys. |

El aprendizaje es completamente pasivo: cada mensaje, evento de contacto, actualización de grupo o historial que Baileys emite es interceptado y procesado para enriquecer el índice automáticamente.

Nota: Un LID solo puede resolverse si el usuario ya interactuó con el bot o está en la agenda del número vinculado. Esta es una restricción de WhatsApp, no de LidSync.


Vinculación con store personalizado

Si usas un store propio (JSON, SQLite, etc.), pásalo en las opciones para que LidSync pueda aprender de los contactos y chats ya almacenados:

sock = pluginLid(sock, { store: miStore });

El store debe exponer al menos contacts y/o chats como objetos planos. Después de que la conexión abra, fuerza la sincronización si el store carga sus datos desde disco:

sock.ev.on("connection.update", ({ connection }) => {
  if (connection === "open") sock.lid.syncStore();
});

API

sock.lid.resolve(id)

Resuelve un LID a su JID real. Si el input ya es un JID @s.whatsapp.net, lo devuelve normalizado directamente.

const jid = await sock.lid.resolve("170360431460562@lid");
// → "[email protected]" | null

| Retorno | Significado | |---|---| | string | JID limpio y normalizado | | null | LID sin mapeo conocido aún |


sock.lid.resolveBatch(ids, opciones?)

Resuelve múltiples LIDs con concurrencia controlada.

const resultado = await sock.lid.resolveBatch(
  ["id1@lid", "id2@lid", "id3@lid"],
  { concurrency: 5 }
);

for (const [lid, jid] of resultado) {
  console.log(`${lid} → ${jid}`);
}

Retorna: Map<string, string> — solo incluye los LIDs que pudieron resolverse.


sock.lid.resolveParticipants(participants)

Resuelve los LIDs de un array de participantes de grupo en una sola operación batch. Es el método recomendado para el evento group-participants.update.

const resolved = await sock.lid.resolveParticipants(participants);
const jid = resolved.get(p.lid || p.id);

Retorna: Map<string, string>


sock.lid.syncStore(forzar?)

Sincroniza el índice desde el store manualmente.

sock.lid.syncStore();       // Solo si no se sincronizó antes
sock.lid.syncStore(true);   // Fuerza re-sincronización siempre

sock.lid.isResolvable(id)

Comprueba si un LID tiene mapeo conocido sin hacer ninguna consulta externa.

if (sock.lid.isResolvable("170360431460562@lid")) {
  const jid = await sock.lid.resolve("170360431460562@lid");
}

Retorna: boolean


sock.lid.preload(pares)

Pre-carga mapeos desde una fuente externa directamente al caché e índice.

sock.lid.preload([
  ["123456789@lid", "[email protected]"],
  ["987654321@lid", "[email protected]"],
]);

// También acepta Map
sock.lid.preload(new Map([["123@lid", "[email protected]"]]));

sock.lid.getStats()

Devuelve métricas del caché LRU y el índice invertido.

const stats = sock.lid.getStats();
/*
{
  cache: {
    size: 1250,
    maxSize: 7500,
    hits: 4821,
    misses: 302,
    evictions: 0,
    expirations: 14,
    hitRate: "94.10%",
    memoryEstimate: "305.18 KB"
  },
  index: {
    size: 1250,
    maxSize: 50000,
    ttlMs: 21600000
  },
  sincronizado: true
}
*/

sock.lid.destroy()

Limpia el caché, vacía el índice y remueve todos los listeners. Llámalo siempre antes de una reconexión.

sock.lid.destroy();
sock = await reconnect();
sock = pluginLid(sock, { store });

Eventos que LidSync escucha

| Evento | Qué aprende | |---|---| | contacts.upsert / contacts.update | Contactos con LID y número telefónico | | messages.upsert | Pares LID↔JID desde participant y participantAlt | | lid-mapping.update | Mappings directos desde acciones de AppState | | messaging-history.set | Historial con lidPnMappings | | group-participants.update | Participantes con phoneNumber y lid | | group.join-request | authorPn y participantPn | | group.member-tag.update | participantAlt con LID/JID | | groups.upsert / groups.update | Participantes de grupos al sincronizar |


Arquitectura interna

pluginLid(sock, { store })
│
├── LidResolver
│   ├── LidCache          — LRU, TTL 24h, máx 7 500 entradas
│   ├── #reverseIndex     — Map<LID, JID>, O(1), máx 50 000
│   ├── Listeners         — 9 eventos de Baileys
│   └── signalRepository  — lidMapping nativo de Baileys
│
└── sock.lid
    ├── resolve(id)
    ├── resolveBatch(ids, opts?)
    ├── resolveParticipants(participants)
    ├── syncStore(forzar?)
    ├── isResolvable(id)
    ├── preload(pares)
    ├── getStats()
    └── destroy()

Política de actualizaciones

LidSync se actualiza siguiendo el ritmo de Baileys. Cuando Baileys modifica su implementación interna del sistema LID, LidSync se adapta en consecuencia. Las actualizaciones pueden tardar algunos días si el cambio es menor, pero si un breaking change afecta el funcionamiento core, se lanza un parche de inmediato.

Si encontrás que algo dejó de funcionar después de actualizar Baileys, abrí un issue indicando la versión de Baileys y el comportamiento observado.


Compatibilidad

| Entorno | Soporte | |---|---| | Node.js | >= 18 | | Baileys | >= 6.7.0 | | Termux | ✅ | | Render / Railway | ✅ | | Pterodactyl / Pelican | ✅ |


Bot de referencia

Repositorio con implementación completa lista para usar:

🔗 LidSync-CoreBot


Creador


Agradecimientos