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

@crysnovax/plug

v1.0.1

Published

Premium Baileys fork ⚉ Meta compositing, bot planning replay, welcome flow, rich messages (code, table, LaTeX, reels), interactive messages, albums, and more.

Readme

const crysnova = {
    name: "CRYSNOVA AI",
    location: "🌍 Global",
    role: "Full Stack Developer & Bot Creator",
    stack: ["JavaScript", "Node.js", "Cloudflare Workers", "WhatsApp Bot Development"],
    interests: ["AI", "Automation", "Open Source", "Building Cool Stuff"],
    currentFocus: "Building the most powerful WhatsApp bot ecosystem 🚀",
    funFact: "My code runs 24/7 on Cloudflare's edge network ⚡"
};

| 🚀 Project | ⭐ Stars | 📝 Description | |-----------|---------|---------------| | CRYSNOVA AI | Stars | 550+ Command WhatsApp Bot with AI Integration | | API Gateway | Stars | Appex Endpoints Unified API Gateway | | FREE WEB AI | Stars | Realistic Virtual Economy with Banking System |

Table of Contents

| # | Section | Description | |---|---------|-------------| | 1 | Overview | What this package is and what sets it apart | | 2 | Installation | NPM, GitHub, ESM & CJS imports | | 3 | Optional: Original Baileys | Using the upstream crysnovax/baileys source | | 4 | Quick Start | Minimal working connection example | | 5 | Authentication | Auth states, pairing codes, SQLite | | 6 | Sending Messages | Text, mentions, reactions, contacts, locations, events, polls, forward, edit | | 7 | Media Messages | Images, videos, audio, documents, stickers, albums | | 8 | Interactive Messages | Buttons, lists, native flows, carousels, hydrated templates | | 9 | Rich Content | Code blocks, tables, inline entities, rich response arrays | | 10 | Meta AI Features | Meta typing, meta compositing, replay planning | | 11 | Welcome Flow | Auto-greet new contacts with FAQ menus | | 12 | Payment Messages | Invites, invoices, orders, payment requests | | 13 | Message Options | AI icon, ephemeral, spoilers, view once, raw, and more | | 14 | Newsletter Management | Create, update, follow, react, delete newsletters | | 15 | Group Management | Create, settings, participants, invites, metadata | | 16 | Community Management | Communities, subgroups, linked groups | | 17 | Profile & Business | Profile pictures, business catalog, products, quick replies | | 18 | Privacy & Security | Last seen, online, status, read receipts, calls | | 19 | Events Reference | Full list of socket event listeners | | 20 | Image Processing | Sharp, NAPI-RS, Jimp auto-detection | | 21 | Condition of Use | Channel follow agreement | | 22 | Credits | Attribution and enhancements |


Overview

@crysnovax/plug is a powerful, production-ready WhatsApp API wrapper for Node.js, built on top of the Baileys protocol. It extends the core with rich messaging capabilities, Meta AI-style compositing, and a streamlined developer experience.

What Sets It Apart

| Feature | Status | Notes | |---------|--------|-------| | Rich Messages | ✅ | Code blocks, tables, inline entities, carousel cards | | Meta AI Style | ✅ | Live thinking indicators without "edited" badges | | Welcome Flow | ✅ | Auto-greet new contacts with interactive FAQ menus | | No Obfuscation | ✅ | Fully readable, auditable source code | | Newsletter Ready | ✅ | Full media support, quoting, quiz polls | | Image Processing | ✅ | Auto-detects sharp, @napi-rs/image, or jimp | | Safe FFmpeg | ✅ | Uses spawn instead of exec | | In-Memory Store | ✅ | Reintroduced with minimal ESM adaptation |


Installation

# NPM
npm install @crysnovax/plug

# GitHub
npm install github:crysnovax/plug

Import (ESM & CJS)

// ESM
import { makeWASocket } from '@crysnovax/plug'

// CJS (Node.js 24+)
const { makeWASocket } = require('@crysnovax/plug')

Optional: Original Baileys

This package (@crysnovax/plug) is built on top of the original crysnovax/baileys fork. If you prefer to use the upstream source directly without the plug enhancements:

#NPM
npm install @crysnovax/baileys

# GitHub
npm install github:crysnovax/baileys
import { makeWASocket } from '@crysnovax/baileys'

Note: The original baileys fork does not include rich messages, Meta AI compositing, welcome flow, or other plug-specific enhancements. Use @crysnovax/plug for the full feature set.


Quick Start

import { makeWASocket, delay, DisconnectReason, useMultiFileAuthState } from '@crysnovax/plug'
import { Boom } from '@hapi/boom'
import pino from 'pino'

const myPhoneNumber = '6288888888888'
const logger = pino({ level: 'silent' })

const connectToWhatsApp = async () => {
  const { state, saveCreds } = await useMultiFileAuthState('session')

  const sock = makeWASocket({
    logger,
    auth: state
  })

  sock.ev.on('creds.update', saveCreds)

  sock.ev.on('connection.update', async (update) => {
    const { connection, lastDisconnect } = update

    if (connection === 'connecting' && !sock.authState.creds.registered) {
      await delay(1500)
      const code = await sock.requestPairingCode(myPhoneNumber)
      console.log('Pairing code:', code)
    }
    else if (connection === 'close') {
      const shouldReconnect = new Boom(lastDisconnect?.error)?.output?.statusCode !== DisconnectReason.loggedOut
      console.log('Connection closed, reconnecting:', shouldReconnect)
      if (shouldReconnect) connectToWhatsApp()
    }
    else if (connection === 'open') {
      console.log('Connected to WhatsApp')
    }
  })

  sock.ev.on('messages.upsert', async ({ messages }) => {
    for (const msg of messages) {
      if (!msg.message) continue
      await sock.sendMessage(msg.key.remoteJid, { text: 'Hello world!' })
    }
  })
}

connectToWhatsApp()

Authentication

Multi-File Auth State (Recommended)

import { useMultiFileAuthState } from '@crysnovax/plug'

const { state, saveCreds } = await useMultiFileAuthState('session')

Single-File Auth State (Experimental)

import { useSingleFileAuthState } from '@crysnovax/plug'

const { state, saveCreds } = await useSingleFileAuthState('session.json')
// Already includes internal caching — no need for makeCacheableSignalKeyStore

SQLite Auth State (Experimental)

import { useSqliteAuthState } from '@crysnovax/plug'

const { state, saveCreds } = await useSqliteAuthState('session.db')

Custom Pairing Code

const phoneNumber = '6281111111111'
const customCode = 'STARFALL'

await sock.requestPairingCode(phoneNumber, customCode)
console.log('Pairing code:', customCode)

Sending Messages

Text & Mentions

// Plain text
sock.sendMessage(jid, { text: 'Hello!' }, { quoted: message })

// With link preview
const url = 'https://www.npmjs.com/package/@crysnovax/plug'
sock.sendMessage(jid, {
  text: url + ' Check it out!',
  linkPreview: {
    'matched-text': url,
    title: '@crysnovax/plug',
    description: 'WhatsApp API for Node.js',
    previewType: 0,
    jpegThumbnail: fs.readFileSync('./thumb.jpg')
  }
})

// Large link preview with favicon
import { prepareWAMessageMedia } from '@crysnovax/plug'

const { imageMessage: image } = await prepareWAMessageMedia(
  { image: { url: './thumb.jpg' } },
  { upload: sock.waUploadToServer, mediaTypeOverride: 'thumbnail-link' }
)

image.height = 720
image.width = 480

sock.sendMessage(jid, {
  text: url + ' Check it out!',
  linkPreview: {
    'matched-text': url,
    title: '@crysnovax/plug',
    description: 'WhatsApp API for Node.js',
    previewType: 0,
    jpegThumbnail: fs.readFileSync('./thumb.jpg'),
    highQualityThumbnail: image,
    linkPreviewMetadata: {
      linkMediaDuration: 0,
      socialMediaPostType: 1 // 0=NONE, 1=REEL, 2=LIVE, 3=LONG, 4=IMAGE, 5=CAROUSEL
    }
  },
  favicon: { url: './favicon.ico' }
})

// Mention specific users
sock.sendMessage(jid, {
  text: 'Hello @628123456789',
  mentions: ['[email protected]']
})

// Mention all group participants
sock.sendMessage(jid, {
  text: 'Hello @all',
  mentionAll: true
})

Reactions & Pins

// Reaction
sock.sendMessage(jid, {
  react: { key: message.key, text: '✨' }
})

// Pin message (86400=1d, 604800=7d, 2592000=30d)
sock.sendMessage(jid, {
  pin: message.key,
  time: 86400,
  type: 1 // 1=pin, 2=unpin
})

// Keep chat (disappearing messages only)
sock.sendMessage(jid, {
  keep: message.key,
  type: 1 // 1=keep, 2=remove
})

Contacts & Locations

// Contact card
const vcard = 'BEGIN:VCARD\nVERSION:3.0\nFN:John Doe\nORG:Company;\nTEL;type=CELL;type=VOICE;waid=628123456789:+62 8123 4567 89\nEND:VCARD'

sock.sendMessage(jid, {
  contacts: {
    displayName: 'John Doe',
    contacts: [{ vcard }]
  }
})

// Location
sock.sendMessage(jid, {
  location: {
    degreesLatitude: 24.121231,
    degreesLongitude: 55.1121221,
    name: 'I am here'
  }
})

// Group invite
const inviteCode = groupUrl.split('chat.whatsapp.com/')[1]?.split('?')[0]

sock.sendMessage(jid, {
  groupInvite: {
    inviteCode,
    inviteExpiration: Date.now() + 86400000,
    text: 'Join our group!',
    jid: groupJid,
    subject: groupName
  }
})

Events & Polls

// Calendar event
sock.sendMessage(jid, {
  event: {
    name: 'Meet & Mingle Party',
    description: 'A fun gathering to connect and chat.',
    call: 'audio', // or 'video'
    startDate: new Date(Date.now() + 3600000),
    endDate: new Date(Date.now() + 28800000),
    isCancelled: false,
    isScheduleCall: false,
    extraGuestsAllowed: false,
    location: {
      name: 'Jakarta',
      degreesLatitude: -6.2,
      degreesLongitude: 106.8
    }
  }
})

// Poll
sock.sendMessage(jid, {
  poll: {
    name: 'Voting time',
    values: ['Yes', 'No'],
    selectableCount: 1,
    toAnnouncementGroup: false,
    endDate: new Date(Date.now() + 28800000),
    hideVoter: false,
    canAddOption: false
  }
})

// Quiz (newsletter only)
sock.sendMessage('1211111111111@newsletter', {
  poll: {
    name: 'Quiz',
    values: ['Yes', 'No'],
    correctAnswer: 'Yes',
    pollType: 1
  }
})

// Poll result
sock.sendMessage(jid, {
  pollResult: {
    name: 'Poll Result',
    votes: [
      { name: 'Nice', voteCount: 10 },
      { name: 'Nah', voteCount: 2 }
    ],
    pollType: 0
  }
})

// Poll update
sock.sendMessage(jid, {
  pollUpdate: {
    metadata: {},
    key: message.key,
    vote: { enclv: buffer, encPayload: buffer }
  }
})

Forward & Edit

// Forward
sock.sendMessage(jid, {
  forward: message,
  force: true
})

// Delete
sock.sendMessage(jid, { delete: message.key })

// Edit text
sock.sendMessage(jid, {
  text: 'Updated text!',
  edit: message.key
})

// Edit media caption
sock.sendMessage(jid, {
  caption: 'Updated caption!',
  edit: message.key
})

Media Messages

Images & Videos

// Image
sock.sendMessage(jid, {
  image: { url: './image.jpg' },
  caption: 'Check this out!'
})

// Video
sock.sendMessage(jid, {
  video: { url: './video.mp4' },
  gifPlayback: false, // true = send as GIF
  ptv: false,         // true = send as PTV
  caption: 'Watch this!'
})

// Status mention (multiple jids)
sock.sendMessage([jidA, jidB, jidC], {
  text: 'Hello!'
})

Audio & Documents

// Audio
sock.sendMessage(jid, {
  audio: { url: './audio.mp3' },
  ptt: false // true = voice note
})

// Document
sock.sendMessage(jid, {
  document: { url: './file.pdf' },
  mimetype: 'application/pdf',
  caption: 'My document'
})

Stickers & Albums

// Sticker
sock.sendMessage(jid, {
  sticker: { url: './sticker.webp' }
})

// Album (images + videos)
sock.sendMessage(jid, {
  album: [
    { image: { url: './img1.jpg' }, caption: 'First image' },
    { video: { url: './vid1.mp4' }, caption: 'First video' },
    { image: { url: './img2.jpg' }, caption: 'Second image' }
  ]
})

// Sticker pack
sock.sendMessage(jid, {
  cover: { url: './cover.webp' },
  stickers: [
    { data: { url: './sticker1.webp' } },
    { data: { url: './sticker2.webp' } }
  ],
  name: 'My Sticker Pack',
  publisher: 'CRYSNOVA',
  description: '@crysnovax/plug'
})

Interactive Messages

Buttons & Lists

// Buttons
sock.sendMessage(jid, {
  text: 'Choose an option!',
  footer: '@crysnovax/plug',
  buttons: [
    { text: 'Sign Up', id: '#SignUp' }
  ]
})

// Buttons with media and native flow
sock.sendMessage(jid, {
  image: { url: './image.jpg' },
  caption: 'Interactive buttons!',
  footer: '@crysnovax/plug',
  buttons: [
    { text: 'Rating', id: '#Rating' },
    {
      text: 'Select',
      sections: [
        {
          title: 'Section 1',
          rows: [
            { header: '', title: 'Secret Ingredient', description: '', id: '#SecretIngredient' }
          ]
        },
        {
          title: 'Section 2',
          highlight_label: 'Popular',
          rows: [
            { header: '', title: 'Coupon', description: '', id: '#CouponCode' }
          ]
        }
      ]
    }
  ]
})

// List (private chat only)
sock.sendMessage(jid, {
  text: 'List menu!',
  footer: '@crysnovax/plug',
  buttonText: 'Select',
  title: 'Hello',
  sections: [
    {
      title: 'Menu 1',
      rows: [
        { title: 'AI', description: '', rowId: '#AI' }
      ]
    },
    {
      title: 'Menu 2',
      rows: [
        { title: 'Search', description: '', rowId: '#Search' }
      ]
    }
  ]
})

Native Flows & Carousels

// Native flow with options
sock.sendMessage(jid, {
  image: { url: './image.jpg' },
  caption: 'Interactive!',
  footer: '@crysnovax/plug',
  optionText: 'Select Options',
  optionTitle: 'Select Options',
  offerText: 'New Coupon!',
  offerCode: 'SAVE20',
  offerUrl: 'https://example.com',
  offerExpiration: Date.now() + 3600000,
  nativeFlow: [
    { text: 'Greeting', id: '#Greeting', icon: 'review' },
    { text: 'Call', call: '628123456789' },
    { text: 'Copy', copy: '@crysnovax/plug' },
    { text: 'Source', url: 'https://example.com', useWebview: true },
    {
      text: 'Select',
      sections: [
        {
          title: 'Section 1',
          rows: [
            { header: '', title: 'Coupon', description: '', id: '#CouponCode' }
          ]
        }
      ],
      icon: 'default'
    }
  ],
  interactiveAsTemplate: false
})

// Carousel with cards
sock.sendMessage(jid, {
  text: 'Carousel!',
  footer: '@crysnovax/plug',
  cards: [
    {
      image: { url: './img1.jpg' },
      caption: 'Image 1',
      footer: 'Pinterest',
      nativeFlow: [
        { text: 'Source', url: 'https://example.com', useWebview: true }
      ]
    },
    {
      image: { url: './img2.jpg' },
      caption: 'Image 2',
      footer: 'Pinterest',
      offerText: 'New Coupon!',
      offerCode: 'SAVE20',
      offerUrl: 'https://example.com',
      offerExpiration: Date.now() + 3600000,
      nativeFlow: [
        { text: 'Source', url: 'https://example.com' }
      ]
    }
  ]
})

// Native flow with audio footer
sock.sendMessage(jid, {
  text: 'Music in footer!',
  audioFooter: { url: './audio.mp3' },
  nativeFlow: [
    { text: 'Good, next', id: '#Next', icon: 'review' },
    { text: 'Skip', id: '#Skip', icon: 'default' }
  ]
})

Hydrated Templates

sock.sendMessage(jid, {
  title: 'Hello',
  image: { url: './image.jpg' },
  caption: 'Template!',
  footer: '@crysnovax/plug',
  templateButtons: [
    { text: 'Tap Here', id: '#Order' },
    { text: 'Source', url: 'https://example.com' },
    { text: 'Call', call: '628123456789' }
  ]
})

Rich Content

Code Blocks

// Built-in tokenizer
sock.sendMessage(jid, {
  disclaimerText: 'Code Block',
  headerText: '## Example Usage',
  contentText: '---',
  code: 'console.log("Hello, World!")',
  language: 'javascript',
  footerText: 'Pretty simple, right?'
})

// Supported languages: css, html, javascript, typescript, python, golang, rust, c, c#, c++, bash, bat, powershell

// Manual tokenization
import { tokenizeCode } from '@crysnovax/plug'

const language = 'javascript'
const code = 'console.log("Hello, World!")'

sock.sendMessage(jid, {
  disclaimerText: 'Tokenized Code',
  richResponse: [
    { text: 'Example Usage' },
    { language, code: tokenizeCode(code, language) },
    { text: 'Pretty simple, right?' }
  ]
})

Tables & Inline Entities

// Table message
sock.sendMessage(jid, {
  disclaimerText: 'Table',
  headerText: '## Comparison',
  contentText: '---',
  title: 'Runtime Comparison',
  table: [
    ['', 'Node.js', 'Bun', 'Deno'],
    ['Engine', 'V8', 'JavaScriptCore', 'V8'],
    ['Performance', '4/5', '5/5', '4/5']
  ],
  noHeading: false,
  footerText: 'Does this help?'
})

// Inline entities (links)
sock.sendMessage(jid, {
  disclaimerText: 'Inline Entities',
  headerText: '## Check Out!',
  contentText: '---',
  links: [
    { text: '1. Google', title: 'Search Engine', url: 'https://google.com' },
    { text: '2. YouTube', title: 'Streaming', url: 'https://youtube.com' }
  ],
  footerText: '---'
})

Rich Response Arrays

// Full rich response with mixed content
sock.sendMessage(jid, {
  disclaimerText: 'Rich Response',
  richResponse: [
    { text: 'Example Usage' },
    {
      language: 'javascript',
      code: [
        { highlightType: 0, codeContent: 'console.log("Hello, World!")' }
      ]
    },
    { text: 'Pretty simple, right?\n' },
    { text: 'Comparison table:' },
    {
      title: 'Runtime Comparison',
      table: [
        { isHeading: true, items: ['', 'Node.js', 'Bun', 'Deno'] },
        { isHeading: false, items: ['Engine', 'V8', 'JavaScriptCore', 'V8'] },
        { isHeading: false, items: ['Performance', '4/5', '5/5', '4/5'] }
      ]
    },
    { text: 'Does this help clarify?' }
  ]
})

Meta AI Features

Meta Typing Indicator

Show a live thinking indicator that you control. Delete it manually when ready — no "edited" badge ever appears.

import { metaTyping, buildSteps, PlanningStepStatus } from '@crysnovax/plug'

const placeholder = await metaTyping(sock, jid, {
  description: 'Thinking…',
  steps: buildSteps(['Reading your message…', 'Writing response…'])
})

// Do your work here…

// Delete the indicator cleanly
await sock.sendMessage(jid, { delete: placeholder.key })

// Send the real message
await sock.sendMessage(jid, { text: 'Here is your answer!' })

Meta Compositing

Full flow: indicator shows → auto-deletes → clean final message lands. Works with every rich content type.

import { sendMetaComposited, PlanningStepStatus } from '@crysnovax/plug'

// With code block
await sendMetaComposited(
  sock, jid,
  { code: 'const x = 1 + 1', language: 'javascript' },
  {
    thinkingMs: 3000,
    description: 'Analyzing…',
    steps: [
      { title: 'Reading context…', status: PlanningStepStatus.DONE },
      { title: 'Writing code…', status: PlanningStepStatus.IN_PROGRESS }
    ]
  }
)

// With table
await sendMetaComposited(
  sock, jid,
  {
    title: 'Comparison',
    table: [
      ['Feature', 'Baileys', 'Crysnovax'],
      ['Rich Messages', '❌', '✅'],
      ['Meta Compositing', '❌', '✅']
    ]
  },
  { thinkingMs: 2500, description: 'Building table…' }
)

// With rich response array
await sendMetaComposited(
  sock, jid,
  {
    richResponse: [
      { text: 'Here is your result:' },
      { code: 'console.log("hello")', language: 'javascript' },
      { text: 'Run it with `node index.js`' }
    ]
  },
  { thinkingMs: 2000 }
)

Replay Planning

Live Meta AI-style reasoning feed — each step visibly completes in real time, then the final rich message lands clean.

import { replayPlanning, mixedSteps } from '@crysnovax/plug'

await replayPlanning(
  sock, jid,

  // Steps — status managed automatically
  mixedSteps([
    { title: 'Understanding your question…', type: 'reasoning' },
    { title: 'Searching for data…', type: 'search' },
    { title: 'Writing the answer…' }
  ]),

  // Final rich message
  { code: 'const answer = 42', language: 'javascript' },

  // Options
  {
    description: 'Thinking…',
    stepDelayMs: 900,
    finalPauseMs: 600
  }
)

Step type helpers:

import {
  buildReasoningSteps,  // isReasoning: true
  buildSearchSteps,     // isEnhancedSearch: true
  mixedSteps,           // mix any combination
  buildSteps            // plain steps
} from '@crysnovax/plug'

// All reasoning
buildReasoningSteps(['Analyzing the problem…', 'Checking edge cases…'])

// All search
buildSearchSteps(['Searching the web…', 'Reading top results…'])

// Mixed — most realistic Meta AI look
mixedSteps([
  { title: 'Reading your message…', type: 'reasoning' },
  { title: 'Searching sources…', type: 'search' },
  { title: 'Composing response…' }
])

Replay planning only (no final message):

import { replayPlanningOnly, buildSearchSteps } from '@crysnovax/plug'

await replayPlanningOnly(
  sock, jid,
  buildSearchSteps(['Looking up prices…', 'Comparing results…']),
  { stepDelayMs: 1200 }
)

// Send whatever you want after — no badge, no trace
await sock.sendMessage(jid, { text: 'Here are the results!' })

Options reference:

| Option | Type | Default | Description | |--------|------|---------|-------------| | description | string | 'Thinking…' | Top label on the indicator bubble | | placeholderText | string | '' | Body text while steps run | | stepDelayMs | number | 900 | Ms between each step completing | | finalPauseMs | number | 600 | Ms to hold after all steps done | | abortOnDisconnect | boolean | true | Stops cleanly if socket closes | | sendOptions | object | {} | Extra options for final sendMessage |


Welcome Flow

Auto-greet new contacts with an interactive FAQ menu. Fires once per contact, never repeats.

Basic Setup

import { createWelcomeFlow } from '@crysnovax/plug'

const welcome = createWelcomeFlow(sock, {
  greeting: 'Welcome! How can I help you today?',
  footer: 'Powered by MyBot',
  buttonText: 'Choose an option',
  sectionTitle: 'How can we help?',
  faqs: [
    { id: 'order', title: 'Track my order', description: 'Check order status' },
    { id: 'billing', title: 'Billing & payments', description: 'Payment issues' },
    { id: 'support', title: 'Technical support', description: 'Get help' },
    { id: 'human', title: 'Talk to a human', description: 'Connect with staff' }
  ]
})

welcome.listen() // start

With Callbacks

const welcome = createWelcomeFlow(sock, {
  greeting: 'Hi there! What brings you here today?',
  faqs: [
    { id: 'pricing', title: 'Pricing', description: 'Plans and costs' },
    { id: 'demo', title: 'Request demo', description: 'See it in action' },
    { id: 'support', title: 'Support', description: 'Get help' }
  ],

  onGreet: async (jid, message) => {
    console.log(`Greeted new contact: ${jid}`)
  },

  onFaqReply: async (jid, faqId, message) => {
    switch (faqId) {
      case 'pricing':
        await sock.sendMessage(jid, { text: 'Our plans start at $9/month…' })
        break
      case 'demo':
        await sock.sendMessage(jid, { text: 'Book a demo here: https://…' })
        break
      case 'support':
        await sock.sendMessage(jid, { text: 'Describe your issue and we will help!' })
        break
    }
  }
})

welcome.listen()

Persist Across Restarts

const welcome = createWelcomeFlow(sock, {
  greeting: 'Welcome!',
  faqs: [...],
  persistPath: './data/greeted-contacts.json'
})

Control Methods

welcome.listen()         // start listening
welcome.stop()           // stop listening
welcome.reset(jid)       // force re-greet one contact
welcome.resetAll()       // clear all seen contacts
welcome.hasGreeted(jid)  // check if greeted → boolean

Config Reference

| Option | Type | Default | Description | |--------|------|---------|-------------| | greeting | string | 'Welcome!…' | Body text of welcome message | | footer | string | 'Powered by @crysnovax/plug' | Footer text | | buttonText | string | 'Choose an option' | List button label | | sectionTitle | string | 'How can we help?' | Section header | | faqs | Array | 4 defaults | { id, title, description } | | typingDelayMs | number | 1200 | Typing indicator duration | | persistPath | string\|null | null | JSON file to persist JIDs | | ignoreGroups | boolean | true | Skip group chats | | ignoreNewsletter | boolean | true | Skip newsletters | | ignoreBroadcast | boolean | true | Skip broadcasts | | onGreet | async fn | null | Called after greeting sent | | onFaqReply | async fn | null | Called on FAQ selection |


Payment Messages

// Payment invite
sock.sendMessage(jid, {
  paymentInviteServiceType: 3 // 1, 2, or 3
})

// Invoice (not supported yet)
sock.sendMessage(jid, {
  image: { url: './image.jpg' },
  invoiceNote: 'Invoice'
})

// Order
sock.sendMessage(chat, {
  orderText: 'Order',
  thumbnail: fs.readFileSync('./image.jpg')
})

// Request payment
sock.sendMessage(jid, {
  text: 'Request Payment',
  requestPaymentFrom: '[email protected]'
})

Message Options

// AI icon (private chat only)
sock.sendMessage(jid, {
  image: { url: './image.jpg' },
  caption: 'With AI icon!',
  ai: true
})

// Ephemeral
sock.sendMessage(jid, {
  image: { url: './image.jpg' },
  caption: 'Ephemeral',
  ephemeral: true
})

// External ad reply
sock.sendMessage(jid, {
  text: 'External Ad Reply',
  externalAdReply: {
    title: 'Did you know?',
    body: 'I dont know',
    thumbnail: fs.readFileSync('./image.jpg'),
    largeThumbnail: false,
    url: 'https://example.com'
  }
})

// Group status (group chat only)
sock.sendMessage(jid, {
  image: { url: './image.jpg' },
  caption: 'Group Status!',
  groupStatus: true
})

// Lottie sticker
sock.sendMessage(jid, {
  sticker: { url: './sticker.webp' },
  isLottie: true
})

// Raw (build manually)
sock.sendMessage(jid, {
  extendedTextMessage: {
    text: 'Built manually',
    contextInfo: {
      externalAdReply: {
        title: '@crysnovax/plug',
        thumbnail: fs.readFileSync('./image.jpg'),
        sourceApp: 'whatsapp',
        showAdAttribution: true,
        mediaType: 1
      }
    }
  },
  raw: true
})

// Secure meta service label
sock.sendMessage(jid, {
  text: 'Just a label!',
  secureMetaServiceLabel: true
})

// Spoiler
sock.sendMessage(jid, {
  image: { url: './image.jpg' },
  caption: 'Spoiler',
  spoiler: true
})

// View once
sock.sendMessage(jid, {
  image: { url: './image.jpg' },
  caption: 'View Once',
  viewOnce: true
})

// View once v2
sock.sendMessage(jid, {
  image: { url: './image.jpg' },
  caption: 'View Once V2',
  viewOnceV2: true
})

// View once v2 extension
sock.sendMessage(jid, {
  image: { url: './image.jpg' },
  caption: 'View Once V2 Extension',
  viewOnceV2Extension: true
})

Newsletter Management

// Create
sock.newsletterCreate('@crysnovax/plug', 'Fresh updates weekly')

// Metadata
const metadata = await sock.newsletterMetadata('1231111111111@newsletter')

// Subscribers count
const subscribers = await sock.newsletterSubscribers('1231111111111@newsletter')

// Follow / Unfollow
sock.newsletterFollow('1231111111111@newsletter')
sock.newsletterUnfollow('1231111111111@newsletter')

// Mute / Unmute
sock.newsletterMute('1231111111111@newsletter')
sock.newsletterUnmute('1231111111111@newsletter')

// Admin management
sock.newsletterDemote('1231111111111@newsletter', '[email protected]')
sock.newsletterChangeOwner('1231111111111@newsletter', '[email protected]')

// Update
sock.newsletterUpdate('1231111111111@newsletter', { name: '@crysnovax/plug' })
sock.newsletterUpdateName('1231111111111@newsletter', '@crysnovax/plug')
sock.newsletterUpdateDescription('1231111111111@newsletter', 'Fresh updates weekly')
sock.newsletterUpdatePicture('1231111111111@newsletter', { url: 'path/to/image.jpg' })
sock.newsletterRemovePicture('1231111111111@newsletter')

// React to message
sock.newsletterReactMessage('1231111111111@newsletter', '100', '💛')

// Admin count
const count = await sock.newsletterAdminCount('1231111111111@newsletter')

// Get all subscribed
const newsletters = await sock.newsletterSubscribed()

// Fetch messages
const messages = sock.newsletterFetchMessages('jid', '1231111111111@newsletter', 50, 0, 0)

// Delete
sock.newsletterDelete('1231111111111@newsletter')

Group Management

// Create
const group = await sock.groupCreate('@crysnovax/plug', ['[email protected]'])

// Metadata
const metadata = await sock.groupMetadata(jid)

// Invite
const inviteCode = await sock.groupInviteCode(jid)
sock.groupRevokeInvite(jid)
sock.groupAcceptInvite(inviteCode)

// Leave
sock.groupLeave(jid)

// Participants
sock.groupParticipantsUpdate(jid, ['[email protected]'], 'add')
sock.groupParticipantsUpdate(jid, ['[email protected]'], 'remove')
sock.groupParticipantsUpdate(jid, ['[email protected]'], 'promote')
sock.groupParticipantsUpdate(jid, ['[email protected]'], 'demote')

// Join requests
sock.groupRequestParticipantsUpdate(jid, ['[email protected]'], 'approve')

// Info updates
sock.groupUpdateSubject(jid, '@crysnovax/plug')
sock.groupUpdateDescription(jid, 'Updated description')
sock.updateProfilePicture(jid, { url: 'path/to/image.jpg' })
sock.removeProfilePicture(jid)

// Settings
sock.groupSettingUpdate(jid, 'announcement')      // admin only chat
sock.groupSettingUpdate(jid, 'not_announcement')  // open chat
sock.groupSettingUpdate(jid, 'locked')              // admin only edit info
sock.groupSettingUpdate(jid, 'unlocked')            // all edit info

// Add mode
sock.groupMemberAddMode(jid, 'admin_add')
sock.groupMemberAddMode(jid, 'all_member_add')

// Ephemeral
sock.groupToggleEphemeral(jid, 86400)  // enable
sock.groupToggleEphemeral(jid, 0)        // disable

// Approval mode
sock.groupJoinApprovalMode(jid, 'on')
sock.groupJoinApprovalMode(jid, 'off')

// Fetch all
const groups = await sock.groupFetchAllParticipating()

// Pending requests
const requests = await sock.groupRequestParticipantsList(jid)

// Invite info
const group = await sock.groupGetInviteInfo('ABC123456789')

// Member label
sock.updateMemberLabel(jid, '@crysnovax/plug')

Community Management

// Create
const community = await sock.communityCreate('@crysnovax/plug', 'Fresh updates weekly')

// Create subgroup
const group = await sock.communityCreateGroup(
  'Announcements',
  ['[email protected]'],
  communityJid
)

// Link / Unlink
sock.communityLinkGroup(groupJid, communityJid)
sock.communityUnlinkGroup(groupJid, communityJid)

// Metadata
const metadata = await sock.communityMetadata(jid)

// Invite
const inviteCode = await sock.communityInviteCode(jid)
sock.communityRevokeInvite(jid)
sock.communityAcceptInvite(inviteCode)

// Leave
sock.communityLeave(jid)

// Join requests
sock.communityRequestParticipantsUpdate(jid, ['[email protected]'], 'approve')

// Updates
sock.communityUpdateSubject(jid, '@crysnovax/plug')
sock.communityUpdateDescription(jid, 'Updated description')

// Settings
sock.communitySettingUpdate(jid, 'announcement')
sock.communitySettingUpdate(jid, 'not_announcement')
sock.communitySettingUpdate(jid, 'locked')
sock.communitySettingUpdate(jid, 'unlocked')

// Add mode
sock.communityMemberAddMode(jid, 'admin_add')
sock.communityMemberAddMode(jid, 'all_member_add')

// Ephemeral
sock.communityToggleEphemeral(jid, 86400)
sock.communityToggleEphemeral(jid, 0)

// Approval mode
sock.communityJoinApprovalMode(jid, 'on')
sock.communityJoinApprovalMode(jid, 'off')

// Fetch all
const communities = await sock.communityFetchAllParticipating()

// Linked groups
const linked = await sock.communityFetchLinkedGroups(jid)

// Pending requests
const requests = await sock.communityRequestParticipantsList(jid)

// Invite info
const community = await sock.communityGetInviteInfo('ABC123456789')

Profile & Business

// Profile picture
const url = await sock.profilePictureUrl(jid, 'image')
sock.updateProfilePicture(jid, buffer)
sock.updateProfilePicture(jid, { url })
sock.removeProfilePicture(jid)

// Profile info
sock.updateProfileName('My Name')
sock.updateProfileStatus('Available')

// Presence
sock.sendPresenceUpdate('available', jid)
sock.presenceSubscribe(jid)

// Read receipts
sock.readMessages([message.key])
sock.sendReceipt(jid, participant, [messageId], 'read')

// Block / Unblock
sock.updateBlockStatus(jid, 'block')
sock.updateBlockStatus(jid, 'unblock')

// Blocklist
const blocked = await sock.fetchBlocklist()

// Chat modify
sock.chatModify({
  archive: true,
  lastMessageOrig: message,
  lastMessage: message
}, jid)

// Star
sock.star(jid, [{ id: messageId, fromMe: true }], true)

// Contact
sock.addOrEditContact(jid, { displayName: 'Name' })
sock.removeContact(jid)

// Labels
sock.addChatLabel(jid, labelId)
sock.removeChatLabel(jid, labelId)
sock.addMessageLabel(jid, messageId, labelId)

// App state sync
sock.resyncAppState(['regular', 'critical_block'], true)

// Business profile
const profile = await sock.getBusinessProfile(jid)

// Product create
const product = await sock.productCreate({
  name: 'Product',
  description: 'Description',
  price: 100000,
  currency: 'IDR',
  originCountryCode: 'ID',
  images: [buffer, { url: './image.jpg' }]
})

// Product update
await sock.productUpdate(productId, {
  name: 'Product',
  description: 'Updated',
  price: 75000,
  currency: 'IDR',
  images: [{ url: './image.jpg' }]
})

// Product delete
sock.productDelete([productId])

// Catalog
const { products, nextPageCursor } = await sock.getCatalog({
  jid: '[email protected]',
  limit: 10
})

// Collections
const collections = await sock.getCollections('[email protected]', 10)

// Order details
const order = await sock.getOrderDetails(orderId, tokenBase64)

// Business profile update
await sock.updateBusinessProfile({
  address: 'Jakarta, Indonesia',
  description: 'Official Store',
  websites: ['https://example.com'],
  email: '[email protected]',
  hours: {
    timezone: 'Asia/Jakarta',
    days: [{ day: 'mon', mode: 'open_24h' }]
  }
})

// Cover photo
sock.updateCoverPhoto({ url: './image.jpg' })
sock.removeCoverPhoto(coverId)

// Quick replies
sock.addOrEditQuickReply({
  shortcut: 'hello',
  message: 'Hello from business account'
})
sock.removeQuickReply(timestamp)

Privacy & Security

// Last seen
sock.updateLastSeenPrivacy('all')
sock.updateLastSeenPrivacy('contacts')
sock.updateLastSeenPrivacy('contact_blacklist')
sock.updateLastSeenPrivacy('nobody')

// Online
sock.updateOnlinePrivacy('all')
sock.updateOnlinePrivacy('match_last_seen')

// Profile picture
sock.updateProfilePicturePrivacy('contacts')

// Status
sock.updateStatusPrivacy('contacts')

// Read receipts
sock.updateReadReceiptsPrivacy('all')
sock.updateReadReceiptsPrivacy('none')

// Groups add
sock.updateGroupsAddPrivacy('all')
sock.updateGroupsAddPrivacy('contacts')

// Messages
sock.updateMessagesPrivacy('all')
sock.updateMessagesPrivacy('contacts')
sock.updateMessagesPrivacy('nobody')

// Call
sock.updateCallPrivacy('everyone')

// Default disappearing mode
sock.updateDefaultDisappearingMode(86400)

// Link previews
sock.updateDisableLinkPreviewsPrivacy(true)

Events Reference

sock.ev.on('connection.update', (update) => {})
sock.ev.on('creds.update', (update) => {})
sock.ev.on('messaging-history.set', (update) => {})
sock.ev.on('messaging-history.status', (update) => {})
sock.ev.on('chats.upsert', (update) => {})
sock.ev.on('chats.update', (update) => {})
sock.ev.on('chats.delete', (update) => {})
sock.ev.on('chats.lock', (update) => {})
sock.ev.on('lid-mapping.update', (update) => {})
sock.ev.on('presence.update', (update) => {})
sock.ev.on('contacts.upsert', (update) => {})
sock.ev.on('contacts.update', (update) => {})
sock.ev.on('messages.delete', (update) => {})
sock.ev.on('messages.update', (update) => {})
sock.ev.on('messages.media-update', (update) => {})
sock.ev.on('messages.upsert', (update) => {})
sock.ev.on('messages.reaction', (update) => {})
sock.ev.on('message-receipt.update', (update) => {})
sock.ev.on('groups.upsert', (update) => {})
sock.ev.on('groups.update', (update) => {})
sock.ev.on('group-participants.update', (update) => {})
sock.ev.on('group.join-request', (update) => {})
sock.ev.on('group.member-tag.update', (update) => {})
sock.ev.on('blocklist.set', (update) => {})
sock.ev.on('blocklist.update', (update) => {})
sock.ev.on('call', (update) => {})
sock.ev.on('labels.edit', (update) => {})
sock.ev.on('labels.association', (update) => {})
sock.ev.on('newsletter.reaction', (update) => {})
sock.ev.on('newsletter.view', (update) => {})
sock.ev.on('newsletter-participants.update', (update) => {})
sock.ev.on('newsletter-settings.update', (update) => {})
sock.ev.on('settings.update', (update) => {})

Image Processing

Auto-detects available libraries: sharp, @napi-rs/image, or jimp.

import { getImageProcessingLibrary } from '@crysnovax/plug'
import { readFile } from 'fs/promises'

const lib = await getImageProcessingLibrary()
const bufferOrFilePath = './image.jpg'
const width = 512
let output

// Sharp
if (lib.sharp?.default) {
  const img = lib.sharp.default(bufferOrFilePath)
  output = await img.resize(width).jpeg({ quality: 80 }).toBuffer()
}

// NAPI-RS Image
else if (lib.image?.Transformer) {
  const inputBuffer = Buffer.isBuffer(bufferOrFilePath)
    ? bufferOrFilePath
    : await readFile(bufferOrFilePath)
  const img = new lib.image.Transformer(inputBuffer)
  output = await img.resize(width, undefined, 0).jpeg(50)
}

// Jimp
else if (lib.jimp?.Jimp) {
  const img = await lib.jimp.Jimp.read(bufferOrFilePath)
  output = await img
    .resize({ w: width, mode: lib.jimp.ResizeStrategy.BILINEAR })
    .getBuffer('image/jpeg', { quality: 50 })
}

else {
  throw new Error('No image processing library available')
}

Condition of Use

By using @crysnovax/plug you agree to the following condition:

This fork will automatically follow the two official CRYSNOVA WhatsApp channels on first connection. This happens once and will not repeat on reconnects.

The channels are:

This is how you stay updated with new features, patches, and releases. If you do not agree to this condition, do not use this fork.


Credits

This package is built on top of the Baileys protocol by WhiskeySockets.

Full credit to the original maintainers and contributors:

  • purpshell
  • jlucaso1
  • adiwajshing
  • itsliaaa/baileys (@lia wyn)

Protocol Buffer definitions maintained by WPP Connect via wa-proto.

CRYSNOVA enhancements:

  • Rich message types (code blocks, tables, inline entities)
  • Meta AI-style compositing and replay planning
  • Welcome flow with FAQ auto-greeting
  • Newsletter media fixes and quiz support
  • Image processing auto-detection
  • Safe FFmpeg spawn-based execution
  • In-memory store ESM adaptation