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

zap-wa

v1.0.0

Published

Simulador de WhatsApp para desarrollo y testing. API propia con protección anti-ban integrada.

Readme

zap-wa

Simulador de WhatsApp para desarrollo y testing. API propia (no un clon de Baileys) con protección anti-ban avanzada, prompts interactivos, flujos de conversación, webhooks y mucho más. Zero dependencies.


Características

| Categoría | Feature | |---|---| | Anti-ban | Typing simulado, rate limit, pausas humanas, jitter, lectura previa | | Prompts | ask() con opciones numeradas, validación estilo Zod, reintentos | | Flujos | ZapFlow — conversaciones multi-paso con ramificación dinámica | | Cola | ZapQueue — envío secuencial automático (evita ráfagas) | | Webhooks | Despacho de eventos por POST a URL configurada | | Broadcast | Difusión masiva con delay y jitter entre envíos | | Programación | sendAt() para mensajes diferidos con cancelScheduled() | | Auto-respuesta | Mensaje automático fuera de horario comercial configurable | | Media | downloadMedia() — descarga de adjuntos como Uint8Array | | Metadata | setMetadata/getMetadata/clearMetadata por chat | | Sesiones | Serialización JSON para persistir en DB (PostgreSQL, Redis, etc.) | | Multi-instancia | Cientos de cuentas por proceso (I/O Bound, sin browser) | | Cancelación | cancelPrompt() para abortar prompts desde fuera | | Inactividad | globalInactivityTimeout — cancela prompts si el usuario no responde |


Instalación

npm install zap-wa

Uso Rápido

import { ZapClient, phone, text } from 'zap-wa'

const client = new ZapClient({
  antiBan: { enabled: true },
  showQR: true
})

client.on('status', async (status) => {
  if (status === 'connected') {
    await client.sendText(phone('5491112345678'), '¡Hola desde zap-wa!')
  }
})

Anti-ban

El motor anti-ban protege tu cuenta simulando comportamientos humanos reales.

const client = new ZapClient({
  antiBan: {
    enabled: true,
    simulateTyping: true,
    typingSpeed: 'human',             // 'slow' | 'human' | 'fast' | number (cps)
    preDelay: { min: 300, max: 900 },
    postDelay: { min: 500, max: 2000 },
    maxMessagesPerMinute: 15,
    jitter: true,
    readBeforeReply: true,
  }
})

Pipeline de envío:

checkRateLimit → maybeRandomPause → preDelay → typing → sleep(proporcional) → paused → ENVIAR → postDelay

Eventos de debugging:

client.on('antiban.typing', (chatId, ms) => {})
client.on('antiban.delay', (chatId, ms, reason) => {})
client.on('antiban.ratelimit', (chatId, ms) => {})

Estimar tiempo / Saltar anti-ban:

client.estimateSendTime({ type: 'text', body: 'tu mensaje' })
await client.sendText(jid, 'Urgente', { skipAntiBan: true })

Prompts Interactivos (ask)

Envía preguntas y espera respuestas válidas con reintentos automáticos.

const depto = await client.ask(jid, {
  question: '¿Qué departamento buscas?',
  options: ['Ventas', 'Soporte', 'Facturación'],
  retryMessage: '❌ Opción inválida.',
  maxRetries: 3,           // undefined = infinitos
  validate: (text) => {
    const idx = parseInt(text) - 1
    if (idx >= 0 && idx < 3) return ['ventas', 'soporte', 'facturación'][idx]
    throw new Error('Inválido')
  }
})

Cancelar un prompt desde fuera:

const promise = client.ask(jid, { question: 'Esperando...' })
client.cancelPrompt(jid, 'INACTIVITY') // → promise rechaza con error INACTIVITY

Flujos de Conversación (ZapFlow)

Encadena múltiples preguntas con lógica de ramificación dinámica.

import { ZapFlow } from 'zap-wa'

const flujo = new ZapFlow()
  .addStep('nombre', {
    question: '¿Cuál es tu nombre?'
  })
  .addStep('edad', {
    question: (res) => `Hola ${res.nombre}, ¿cuántos años tenés?`,
    validate: (val) => {
      const n = parseInt(val)
      if (isNaN(n) || n < 1) throw new Error('Edad inválida')
      return n
    }
  })
  .addStep('area', {
    question: '¿Qué área te interesa?',
    options: ['Desarrollo', 'Diseño', 'Marketing']
  })
  .onStep((stepId, value, results) => {
    console.log(`Paso ${stepId} completado: ${value}`)
  })

const resultados = await client.runFlow(jid, flujo)
// { nombre: 'Juan', edad: 25, area: 'Desarrollo' }

Cola de Mensajes (ZapQueue)

Cuando useQueue: true (default), los mensajes se procesan uno por uno respetando el anti-ban.

const client = new ZapClient({ useQueue: true })

// Estos 3 se encolan y se envían secuencialmente
const p1 = client.sendText(jid, 'Mensaje 1')
const p2 = client.sendText(jid, 'Mensaje 2')
const p3 = client.sendText(jid, 'Mensaje 3')

await Promise.all([p1, p2, p3])

Webhooks

Despacha eventos a una URL externa por POST.

const client = new ZapClient({
  webhook: {
    url: 'https://mi-servidor.com/webhook',
    events: ['message', 'status'],
    headers: { 'Authorization': 'Bearer token' }
  }
})

Broadcast (Difusión Masiva)

const targets = [phone('111'), phone('222'), phone('333')]

const res = await client.broadcast(targets, text('¡Promo!'), {
  delayBetween: 1000,   // ms entre envíos
  jitter: 0.3            // ±30% variación
})

console.log(res) // { total: 3, success: 3, failed: 0, errors: [] }

Mensajes Programados (sendAt)

const taskId = await client.sendAt(
  Date.now() + 60_000,           // dentro de 1 minuto
  jid,
  text('¡Recordatorio!')
)

client.cancelScheduled(taskId)   // cancelar si es necesario

Auto-respuesta fuera de horario

const client = new ZapClient({
  autoResponse: {
    enabled: true,
    onlyDMs: true,
    message: (msg) => `Hola ${msg.pushName || ''}! Estamos fuera de horario.`,
    schedule: [
      { day: 1, periods: [{ start: '09:00', end: '18:00' }] }, // Lunes
      { day: 2, periods: [{ start: '09:00', end: '18:00' }] },
      { day: 3, periods: [{ start: '09:00', end: '18:00' }] },
      { day: 4, periods: [{ start: '09:00', end: '18:00' }] },
      { day: 5, periods: [{ start: '09:00', end: '18:00' }] },
    ]
  }
})

Metadata por Chat

Almacena estado persistente por chat (FSM, preferencias, etc.).

client.setMetadata(jid, 'step', 'payment')
client.getMetadata(jid, 'step')   // → 'payment'
client.clearMetadata(jid)

Inactividad Global

Cancela automáticamente prompts y flujos pendientes si el usuario no responde.

const client = new ZapClient({
  globalInactivityTimeout: 120_000 // 2 minutos
})

Persistencia de Sesión

import { serializeSession, deserializeSession } from 'zap-wa'

// Guardar
client.on('session.saved', (session) => {
  const raw = serializeSession(session)
  await db.sessions.save(session.id, raw)
})

// Cargar (evita pedir QR de nuevo)
const raw = await db.sessions.get(id)
const client = new ZapClient({ session: deserializeSession(raw) })

QR como Buffer / API

import * as QRCode from 'qrcode'

client.on('qr', async (qrString) => {
  const buffer = await QRCode.toBuffer(qrString)
  // Servir por Express, guardar como PNG, etc.
})

Multi-Instancia

zap-wa es I/O Bound. Sin browsers reales, escala a cientos de sesiones por proceso.

const sessions = await db.sessions.findAll()
for (const s of sessions) {
  const bot = new ZapClient({ session: deserializeSession(s.data) })
  bot.on('message', handleMessage)
}

API Completa

Crear cliente

const client = new ZapClient(config?: ZapConfig)

Propiedades

client.me              // { id, phone, name } | null
client.status          // 'idle' | 'connecting' | 'connected' | 'disconnected' | 'reconnecting'
client.connected       // boolean
client.session         // ZapSession | null
client.antiBanEnabled  // boolean
client.store           // ZapStore (mensajes, chats, contactos, grupos)

Envío de mensajes

await client.sendText(jid, 'texto', opts?)
await client.sendImage(jid, srcUrlOrBuffer, caption?, opts?)
await client.sendVoice(jid, srcUrlOrBuffer, duration?, opts?)
await client.sendLocation(jid, lat, lng, name?, opts?)
await client.react(jid, messageId, emoji)
await client.deleteMessage(jid, messageId, forEveryone?)
await client.send(jid, content, opts?)

SendOptions

{
  quote?: ZapMessage
  ephemeral?: number
  id?: string
  mentions?: ZapID[]
  skipAntiBan?: boolean
  forceDelay?: number
  forwarded?: boolean
}

Prompts y Flujos

await client.ask(jid, options: PromptOptions)
await client.runFlow(jid, flow: ZapFlow)
client.cancelPrompt(jid, reason?)

Programación y Broadcast

await client.sendAt(timestamp, jid, content)
client.cancelScheduled(taskId)
await client.broadcast(targets, content, opts?: BroadcastOptions)

Media

await client.downloadMedia(msg) // → Uint8Array

Metadata

client.setMetadata(jid, key, value)
client.getMetadata(jid, key)
client.clearMetadata(jid)

Operaciones de chat

await client.markRead(chatId)
await client.archiveChat(chatId, true)
await client.pinChat(chatId, true)
await client.muteChat(chatId, 8 * 60 * 60 * 1000)
await client.deleteChat(chatId)
client.getChat(chatId)
client.listChats()

Grupos

await client.createGroup('Nombre', [jid1, jid2])
await client.addMembers(grupoId, [jid3])
await client.removeMembers(grupoId, [jid2])
await client.promoteMembers(grupoId, [jid1])
await client.demoteMembers(grupoId, [jid1])
await client.leaveGroup(grupoId)
await client.renameGroup(grupoId, 'Nuevo nombre')
await client.describeGroup(grupoId, 'Descripción')
await client.setGroupAnnouncement(grupoId, true)
await client.getGroupInviteLink(grupoId)

Contactos y Presencia

client.getContact(jid)
client.displayName(jid)
await client.blockContact(jid)
await client.unblockContact(jid)
await client.updateStatus('Disponible')
await client.setPresence(chatId, 'typing')

Eventos

client.on('status', (status, info) => {})
client.on('qr', (qr, attempt) => {})
client.on('message', (msg) => {})
client.on('message.sent', (msg) => {})
client.on('message.update', (id, chatId, update) => {})
client.on('message.delete', (id, chatId, by) => {})
client.on('message.reaction', (reaction, msgId, from) => {})
client.on('chat.new', (chat) => {})
client.on('chat.update', (chatId, changes) => {})
client.on('chat.delete', (chatId) => {})
client.on('contact.update', (contact) => {})
client.on('group.new', (group) => {})
client.on('group.update', (groupId, changes) => {})
client.on('group.join', (groupId, members) => {})
client.on('group.leave', (groupId, members) => {})
client.on('group.promote', (groupId, members) => {})
client.on('group.demote', (groupId, members) => {})
client.on('presence', (chatId, userId, presence) => {})
client.on('session.saved', (session) => {})
client.on('antiban.typing', (chatId, ms) => {})
client.on('antiban.delay', (chatId, ms, reason) => {})
client.on('antiban.ratelimit', (chatId, ms) => {})

Helpers de simulación (testing)

await client.simulate(jid, content)
await client.simulateQRScan('5491199887766')
await client.simulateIncomingTyping(jid, 2000)
await client.simulateDrop(true)
client.simulateContact(jid, 'Juan Pérez')
client.simulateGroup('Mi Grupo', [jid1, jid2])
client.trigger('message.reaction', reaction, 'msg123', jid)

ID Helpers

import { phone, group, isGroup, isDM, rawNumber, sameUser } from 'zap-wa'

phone('5491199887766')     // → '[email protected]'
group('123456789-9876543') // → '[email protected]'
isGroup('[email protected]')    // → true
isDM('[email protected]')         // → true
rawNumber('[email protected]')  // → '54911'
sameUser(jid1, jid2)       // → boolean

Content Builders

import { text, image, video, audio, voice, doc, location, react, poll } from 'zap-wa'

text('Hola!', mentions?)
image('https://...', caption?)
video('https://...', caption?)
audio('https://...')
voice('https://...', durationSecs?)
doc('https://...', 'application/pdf', 'archivo.pdf')
location(-34.6, -58.3, 'Buenos Aires')
react('❤️', 'messageId')
poll('¿Cuándo?', ['Viernes', 'Sábado'], allowMultiple?)

Estructura del Proyecto

src/
├── index.ts            ← Exports públicos
├── types/              ← Tipos TypeScript
├── core/
│   ├── client.ts       ← ZapClient (lógica principal)
│   ├── session.ts      ← Gestión de sesión
│   ├── flow.ts         ← ZapFlow (conversaciones multi-paso)
│   └── queue.ts        ← ZapQueue (cola secuencial)
├── plugins/
│   └── antiban.ts      ← Motor anti-ban
├── store/              ← Stores en memoria
└── utils/              ← Helpers, builders, generadores

Licencia MIT.