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

@neoxr/zapo

v1.2.13

Published

TypeScript connector for building WhatsApp Bot (ZapoJS)

Readme

@neoxr/zapo

@neoxr/zapo is a TypeScript connector for building WhatsApp bots on top of zapo-js. It creates and manages the WhatsApp connection, forwards client events, serializes incoming and outgoing messages, loads optional plugins, and adds high-level helpers for common message types.

Table of Contents


Quick Start

Get a working bot that replies to ping in about five minutes.

1. Requirements

  • Node.js 20 or newer (LTS recommended)
  • FFmpeg installed and available in your PATH (needed for stickers, voice notes and video helpers)
  • A WhatsApp account to use as the bot number

2. Create a project and install

mkdir my-bot && cd my-bot
npm init -y
npm install @neoxr/zapo

The examples below use ES modules, so add this to your package.json:

{
  "type": "module"
}

3. Create index.js

import { Client } from '@neoxr/zapo'

const waSocket = new Client({
   plugsdir: 'plugins',
   presence: true,
   online: true,
   pairing: {
      state: true, // set to false to log in with a QR code instead
      number: '6285xxxxxxx' // bot number with country code, no "+" or spaces
   },
   create_session: { type: 'sqlite', session: 'bot-session' },
   custom_id: 'neoxr',
   engines: [], // optional: add store / voip engines here
   debug: false
})

waSocket.on('connect', () => console.log('Connecting to WhatsApp...'))
waSocket.on('ready', () => console.log('Bot is ready!'))
waSocket.on('error', error => console.error(error))

waSocket.on('message', async ctx => {
   if (ctx.fromMe) return
   if (ctx.text?.toLowerCase() === 'ping') {
      await waSocket.sock.reply(ctx.chat, 'Pong!', ctx)
   }
})

4. Run

node index.js

5. Link your WhatsApp account

  • Pairing code (pairing.state: true): the console prints an 8-character code. On your phone open WhatsApp > Linked devices > Link a device > Link with phone number instead and enter it. You can choose your own code by setting pairing.code to 8 alphanumeric characters (for example NEOXRBOT).
  • QR code (pairing.state: false): scan the QR code printed in the console from WhatsApp > Linked devices > Link a device.

After linking, send ping to the bot number. It should answer Pong!.

The session is saved by create_session. As long as the session file or database is kept, you do not have to pair again on the next start. Delete it to log in with a different number.

6. Keep it running (optional)

npm install -g pm2
pm2 start index.js --name neoxr-bot
pm2 save
pm2 logs neoxr-bot

Next steps

  • Add commands as plugins in the plugins folder (see the plugsdir option).
  • Browse the Messaging Functions to send images, stickers, buttons and more.
  • Want a ready-made bot with downloader, group management and utilities? See Running the Full Bot Base.

Connection Options

The following is the default configuration used when initializing a new Zapo connection. It covers session management, plugin loading and bot-specific behavior.

type SessionType = 'mongo' | 'postgres' | 'local' | 'mysql' | 'sqlite' | 'redis'

export interface ConnectionOpts {
   online?: boolean,
   presence?: boolean,
   stealth?: string,
   custom_id: string,
   pairing?: {
      state: boolean,
      number: string,
      code?: string
   },
   multiple?: boolean,
   create_session?: {
      type: SessionType,
      session: string,
      config?: any,
      number?: string | number,
      owner?: string | number
   },
   setting?: any,
   engines: any[],
   debug: boolean,
}

| Option | Type | Description | | --- | --- | --- | | plugsdir | string | Folder that contains your plugins. | | online | boolean | Mark the bot as online after connecting. | | presence | boolean | Receive and emit presence updates. | | stealth | string | Pretend to be another device to avoid bot detection. One of ios, android, web, desktop. | | custom_id | string | Identifier for this bot instance. Required. | | pairing.state | boolean | true for pairing code, false for QR scan. | | pairing.number | string | Bot number with country code. | | pairing.code | string | Optional custom pairing code (8 alphanumeric characters). | | multiple | boolean | Allow several bot sessions in one process. | | create_session | object | Where and how the session is stored. See Session Storage. | | setting | any | Extra settings passed to your bot. | | engines | any[] | Optional engines such as store or voip. Use [] if you do not need them. | | debug | boolean | Print internal logs. Set to true to see how the module works. |

Full example with all common options:

import { Client, Utils, Config } from '@neoxr/zapo'

const waSocket = new Client({
   plugsdir: 'plugins',
   presence: true,
   online: true,
   pairing: {
      state: true,
      number: '6285xxxxxxx',
      code: 'NEOXRBOT'
   },
   create_session: { type: 'sqlite', session: 'bot-session' },
   // stealth: 'ios',
   custom_id: 'neoxr',
   engines: [store, voip], // only if you have initialized them
   debug: false
})

Session Storage

create_session.type decides where the login session is kept:

| Type | Typical use | | --- | --- | | local | Plain files in a local folder. Simplest option for development. | | sqlite | Single-file database. Good default for one bot on one server. | | mysql / postgres | Shared SQL database, useful for several instances. | | mongo | MongoDB, for example a hosted Atlas cluster. | | redis | Fast in-memory storage, for example on ephemeral hosting. |

For anything other than local and sqlite, pass the connection details in create_session.config.


Events

Built-in events

waSocket.on('connect', () => console.log('connecting'))
waSocket.on('error', error => console.log(error))
waSocket.on('ready', () => console.log('ready'))
waSocket.on('stories', ctx => console.log(ctx))
waSocket.on('message', ctx => console.log(ctx))
waSocket.on('message.delete', ctx => console.log(ctx))
waSocket.on('group.add', ctx => console.log(ctx))
waSocket.on('group.remove', ctx => console.log(ctx))
waSocket.on('group.promote', ctx => console.log(ctx))
waSocket.on('group.demote', ctx => console.log(ctx))
waSocket.on('group.request', ctx => console.log(ctx))
waSocket.on('presence', ctx => console.log(ctx))

Event piping

You can also listen to the raw events emitted by zapo-js through waSocket.ev:

waSocket.ev.on('voip_call_incoming', update => console.log(update))
waSocket.ev.on('voip_call_state', update => console.log(update))
waSocket.ev.on('voip_call_ended', update => console.log(update))
waSocket.ev.on('voip_call_error', update => console.log(update))
waSocket.ev.on('voip_call_outbound_audio_finished', update => console.log(update))
waSocket.ev.on('voip_call_inbound_video', update => console.log(update))
waSocket.ev.on('voip_call_inbound_video_rtp', update => console.log(update))
waSocket.ev.on('message', update => console.log(update))
waSocket.ev.on('message_send', update => console.log(update))
waSocket.ev.on('message_addon', update => console.log(update))
waSocket.ev.on('message_protocol', update => console.log(update))
waSocket.ev.on('message_bot_chunk', update => console.log(update))
waSocket.ev.on('message_unavailable', update => console.log(update))
waSocket.ev.on('receipt', update => console.log(update))
waSocket.ev.on('presence', update => console.log(update))
waSocket.ev.on('chatstate', update => console.log(update))
waSocket.ev.on('call', update => console.log(update))
waSocket.ev.on('group', update => console.log(update))
waSocket.ev.on('newsletter', update => console.log(update))
waSocket.ev.on('newsletter_message_update', update => console.log(update))
waSocket.ev.on('business', update => console.log(update))
waSocket.ev.on('picture', update => console.log(update))
waSocket.ev.on('privacy', update => console.log(update))
waSocket.ev.on('blocklist', update => console.log(update))
waSocket.ev.on('own_username', update => console.log(update))
waSocket.ev.on('mutation', update => console.log(update))
waSocket.ev.on('mutation_send', update => console.log(update))
waSocket.ev.on('history_sync_chunk', update => console.log(update))
waSocket.ev.on('group_history_bundle', update => console.log(update))
waSocket.ev.on('offline_resume', update => console.log(update))
waSocket.ev.on('offline_thread_metadata', update => console.log(update))
waSocket.ev.on('mex_notification', update => console.log(update))
waSocket.ev.on('stream_failure', update => console.log(update))
waSocket.ev.on('stanza_error', update => console.log(update))

Message Object

Every message event gives you a serialized message (m or ctx). Commonly used fields:

| Field | Description | | --- | --- | | m.chat | Chat JID where the message was sent. | | m.sender | Sender JID (phone-number form). | | m.text | Text content of the message. | | m.mtype | Message type, for example extendedTextMessage. | | m.isGroup | true when sent in a group. | | m.fromMe | true when sent by the bot itself. | | m.quoted | The quoted message, or null. | | m.mentionedJid | List of mentioned JIDs. | | m.key | Message key (remoteJid, id, fromMe, participant, ...). | | m.reply(...) | Reply to the message. | | m.react(...) | React to the message. | | m.download() | Download attached media. |

Example of a full object (group text message):

{
   rawNode: {
      tag: 'message',
      attrs: {
         from: '[email protected]',
         type: 'text',
         id: 'A512151D2087CBD787C23C1129D69EE5',
         participant: '8792385306797@lid',
         sts: '1790580592042831',
         offline: '0',
         verified_level: 'high',
         notify: 'Neoxr Creative',
         addressing_mode: 'lid',
         expiration: '86400',
         verified_name: '4892374714914454331',
         participant_pn: '[email protected]',
         t: '1790580591'
      },
      content: [[Object], [Object], [Object], [Object]]
   },
   key: {
      remoteJid: '[email protected]',
      id: 'A512151D2087CBD787C23C1129D69EE5',
      fromMe: false,
      isGroup: true,
      isBroadcast: false,
      isNewsletter: false,
      participantAlt: '[email protected]',
      senderDevice: 0,
      participant: '8792385306797@lid'
   },
   stanzaType: 'text',
   offline: true,
   timestampSeconds: 1790580591,
   expirationSeconds: 86400,
   pushName: 'Neoxr Creative',
   message: {
      extendedTextMessage: {
         endCardTiles: [],
         text: '=> m',
         previewType: 0,
         contextInfo: [Object],
         inviteLinkGroupTypeV2: 0
      },
      messageContextInfo: {
         threadId: [],
         messageSecret: [Uint8Array],
         botMetadata: [Object]
      }
   },
   id: 'A512151D2087CBD787C23C1129D69EE5',
   isBot: false,
   fromMe: false,
   chat: '[email protected]',
   sender: '[email protected]',
   isGroup: true,
   mtype: 'extendedTextMessage',
   quoted: null,
   mentionedJid: [],
   expiration: 86400,
   text: '=> m',
   reply: [Function],
   react: [AsyncFunction],
   download: [AsyncFunction]
}

Messaging Functions

All helpers live on the socket client:

const client = waSocket.sock

Text and reactions

// relay a raw message (like Baileys)
client.relayMessage(m.chat, {
   extendedTextMessage: { text: 'Hi' }
}, {
   additionalAttributes: {},
   additionalNodes: [],
   ...options
})

// send a text message (auto tagged)
client.reply(m.chat, 'Test!', m)

// send a reaction
client.sendReact(m.chat, '💀', m.key)

// send a text message with a custom thumbnail
client.sendMessageModify(m.chat, 'Test!', m, {
   title: '© neoxr-bot',
   largeThumb: true,
   ads: false,
   thumbnail: 'https://iili.io/HP3ODj2.jpg', // Buffer or URL
   url: 'https://chat.whatsapp.com/HYknAquOTrECm9KPJJQO1V'
})

// same, as a link preview
client.sendMessageModify(m.chat, 'Test!', m, {
   title: '© neoxr-bot',
   largeThumb: true,
   type: 'preview-link',
   ratio: 'landscape', // landscape (default), portrait, square
   thumbnail: 'https://iili.io/HP3ODj2.jpg', // Buffer or URL
   url: 'https://chat.whatsapp.com/HYknAquOTrECm9KPJJQO1V'
})

Files and media

All file helpers accept a path, URL or Buffer, and detect the extension automatically.

// send a file
client.sendFile(m.chat, 'https://iili.io/HP3ODj2.jpg', 'image.jpg', 'Test!', m)

// send as a document
client.sendFile(m.chat, 'https://iili.io/HP3ODj2.jpg', 'image.jpg', 'Test!', m, {
   document: true
})

// send a live photo (video source)
client.sendFile(m.chat, 'https://cdn.videy.co/7QUk0zRO1.mp4', 'video.mp4', 'Test!', m, {
   photo_live: true,
   // thumbnail: Buffer | URL | Path
})

// send a voice note
client.sendFile(m.chat, './media/audio/ah.mp3', '', '', m, {
   ptt: true
})

// send audio with a cover image in the audio tag
client.sendFile(m.chat, './media/audio/ah.mp3', '', '', m, {
   audio_tag: {
      APIC: coverBuffer // Buffer
   }
})

// send a PTV (round video) message, 10 seconds max
client.sendPtv(m.chat, './media/video/yemete.mp4')

// send an album
client.sendAlbumMessage(m.chat, [{
   url: 'https://i.pinimg.com/736x/6f/a3/6a/6fa36aa2c367da06b2a4c8ae1cf9ee02.jpg',
   caption: 'Content 1st', // optional
   type: 'image' // optional
}, {
   url: 'https://i.pinimg.com/736x/0b/97/6f/0b976f0a7aa1aa43870e1812eee5a55d.jpg',
   caption: 'Content 2nd',
   type: 'image'
}, {
   url: 'https://i.pinimg.com/736x/8c/6d/db/8c6ddb5fe6600fcc4b183cb2ee228eb7.jpg',
   caption: 'Content 3rd',
   type: 'image'
}], m)

Stickers

Source can be a URL or Buffer.

const sticker = {
   packname: 'Sticker by',
   author: '© neoxr.js'
}

client.sendSticker(m.chat, 'https://iili.io/HP3ODj2.jpg', m, sticker)                    // normal
client.sendSticker(m.chat, 'https://iili.io/HP3ODj2.jpg', m, { ...sticker, meta: true })    // AI sticker
client.sendSticker(m.chat, 'https://iili.io/HP3ODj2.jpg', m, { ...sticker, lock: true })    // lock sticker
client.sendSticker(m.chat, 'https://iili.io/HP3ODj2.jpg', m, { ...sticker, premium: true }) // premium sticker

Polls and contacts

// poll result
client.pollResult(m.chat, {
   name: 'Demo Poll Result',
   votes: [{
      name: 'Option A',
      count: 1500
   }, {
      name: 'Option B',
      count: 200
   }]
}, m)

// contact card
client.sendContact(m.chat, [{
   name: 'Wildan Izzudin',
   number: '6285887776722',
   about: 'Owner & Creator'
}], m, {
   org: 'Neoxr Network',
   website: 'https://api.neoxr.my.id',
   email: '[email protected]'
})

Interactive buttons

Interactive buttons depend on WhatsApp client behavior and may stop working at any time. Use at your own risk.

const buttons = [{
   name: 'quick_reply',
   buttonParamsJson: JSON.stringify({
      display_text: 'OWNER',
      id: '.owner'
   }),
}, {
   name: 'cta_url',
   buttonParamsJson: JSON.stringify({
      display_text: 'Rest API',
      url: 'https://api.neoxr.my.id',
      merchant_url: 'https://api.neoxr.my.id'
   })
}, {
   name: 'cta_copy',
   buttonParamsJson: JSON.stringify({
      display_text: 'Copy',
      copy_code: '123456'
   })
}, {
   name: 'cta_call',
   buttonParamsJson: JSON.stringify({
      display_text: 'Call',
      phone_number: '6285887776722'
   })
}, {
   name: 'single_select',
   buttonParamsJson: JSON.stringify({
      title: 'Tap!',
      sections: [{
         rows: [{
            title: 'Owner',
            description: 'X',
            id: '.owner'
         }, {
            title: 'Runtime',
            description: 'Y',
            id: '.run'
         }]
      }]
   })
}]

// buttons and list
client.replyButton(m.chat, buttons, m, {
   header: '',
   content: 'Hi!',
   // v2: true, // product style with image
   type: 'interactive',
   footer: '',
   media: global.db.setting.cover // video or image link
})

// buttons and list (grouped in a single select menu)
client.replyButton(m.chat, buttons, m, {
   header: '',
   content: 'Hi!',
   footer: '',
   media: global.db.setting.cover,
   multiple: {
      name: 'オートメーション',
      code: 'neoxr-bot',
      list_title: 'Select Menu',
      button_title: 'Tap Here!'
   },
   type: 'interactive'
})

// carousel
const cards = [{
   header: {
      image: global.db.setting.cover,
      hasMediaAttachment: true,
   },
   body: { text: 'P' },
   nativeFlowMessage: {
      buttons: [{
         name: 'cta_url',
         buttonParamsJson: JSON.stringify({
            display_text: 'Contact Owner',
            url: 'https://api.neoxr.eu',
            webview_presentation: null
         })
      }]
   }
}, {
   header: {
      image: global.db.setting.cover,
      hasMediaAttachment: true,
   },
   body: { text: 'P' },
   nativeFlowMessage: {
      buttons: [{
         name: 'cta_url',
         buttonParamsJson: JSON.stringify({
            display_text: 'Contact Owner',
            url: 'https://api.neoxr.eu',
            webview_presentation: null
         })
      }]
   }
}]

client.replyButton(m.chat, [], m, {
   title: global.header,
   type: 'interactive',
   content: 'Hi! @0',
   cards
})

Legacy buttons

text and footer must not be empty.

const buttons = [{
   text: 'Runtime',
   command: '.runtime'
}, {
   text: 'Statistic',
   command: '.stat'
}]

// text
client.replyButton(m.chat, buttons, m, {
   text: 'Hi @0',
   footer: global.footer
})

// image or video
client.replyButton(m.chat, buttons, m, {
   text: 'Hi @0',
   footer: global.footer,
   media: global.db.setting.cover
})

// document
client.replyButton(m.chat, buttons, m, {
   text: 'Hi @0',
   footer: global.footer,
   media: global.db.setting.cover, // any file link
   document: {
      filename: 'neoxr.jpg'
   }
})

// location
client.replyButton(m.chat, buttons, m, {
   text: 'Hi @0',
   footer: global.footer,
   media: global.db.setting.cover,
   location: {
      name: global.header,
      description: 'オートメーション'
   }
})

// legacy buttons + native flow
client.replyButton(m.chat, [{
   text: 'Runtime',
   command: '.runtime'
}, {
   name: 'single_select',
   params: {
      title: 'Tap!',
      sections: [{
         rows: [{
            title: 'Runtime',
            description: '---',
            id: '.runtime'
         }, {
            title: 'Statistic',
            description: '---',
            id: '.stat'
         }]
      }]
   }
}], m, {
   text: 'Hi @0',
   footer: global.footer,
   media: global.db.setting.cover
})

Group status

// image or video
client.groupStatus(m.chat, {
   media: 'https://i.pinimg.com/736x/0b/97/6f/0b976f0a7aa1aa43870e1812eee5a55d.jpg', // URL or Buffer
   caption: 'Hi!'
})

// audio
client.groupStatus(m.chat, {
   media: 'https://example.com/audio.mp3', // URL or Buffer
   background: '#FF0000'
})

// text
client.groupStatus(m.chat, {
   text: 'Hi!',
   background: '#FF0000',
   color: '#222222'
})

// close friends: add the `private` option to any of the above
client.groupStatus(m.chat, {
   text: 'Hi!',
   background: '#FF0000',
   color: '#222222'
}, {
   private: {
      name: 'Neoxr Creative',
      emoji: '🔥'
   }
})

// instant: forward a quoted message as status
client.groupStatus(m.chat, m.quoted.fakeObj, {
   private: {
      name: 'Neoxr Creative',
      emoji: '🔥'
   }
})

Meta (rich) messages

sendMetaMsg takes a list of blocks. Which blocks work depends on the version.

v1: text with mentions, tables and code

client.sendMetaMsg(m.chat, [{
   text: `Hi @${m.sender.replace(/@.+/, '')} ✨, this is a simple meta message that supports *mentions*, *tables* and *code format*.`
}, {
   code: {
      language: 'javascript',
      code: fs.readFileSync('./error.js', 'utf-8')
   }
}, {
   text: 'And this is an example of a table.'
}, {
   table: {
      title: 'Data',
      headers: ['Code', 'Artist'],
      rows: [
         ['SSID-738', 'Artist A'],
         ['RTXU-849', `@${m.sender.replace(/@.+/, '')}`]
      ]
   }
}, {
   text: 'You can add text, tables and code blocks as you like. 😎'
}], m, {
   title: global.header,
   mentions: [m.sender]
})

v2: citations, links, muted text, suggestions and sources (no mentions or tables)

client.sendMetaMsg(m.chat, [{
   text: 'This meta message supports citations [](https://api.neoxr.eu) and links [Neoxr API](https://api.neoxr.eu).\n\nCode formatting still works:'
}, {
   code: {
      language: 'javascript',
      code: fs.readFileSync('./error.js', 'utf-8')
   }
}, {
   muted: 'There is also muted text like this.'
}, {
   suggestions: ['N', 'E', 'O', 'X', 'R', 'B', 'O', 'T']
}, {
   sources: [{
      icon: 'https://i.pinimg.com/736x/b4/e0/12/b4e012b9e55bc101eb2c89655888e2f1.jpg',
      title: 'Github',
      url: 'https://github.com/neoxr/neoxr-bot'
   }]
}], m, {
   title: global.header
})

v3: reels, posts and products (no mentions or tables)

client.sendMetaMsg(m.chat, [{
   text: 'Photo and video blocks are supported.\n\n---\n\nHere is an example of reels:'
}, {
   reels: [
      'https://i.pinimg.com/736x/48/58/17/485817189f76066e8b22637d1310d3b4.jpg',
      'https://i.pinimg.com/736x/0e/32/e3/0e32e35b0324a541ba7fe705330febde.jpg',
      'https://i.pinimg.com/736x/88/78/b1/8878b18952e3587dd5c7502f35155731.jpg'
   ].map(image => ({
      creator: 'Neoxr Creative',
      avatar: 'https://avatars.githubusercontent.com/u/52621597?v=4',
      verified: true,
      thumbnail: image,
      url: 'https://api.neoxr.eu',
      source: 'IG'
   }))
}, {
   text: '\n\n---\n\nSocial media posts:'
}, {
   posts: [{
      media: 'https://i.pinimg.com/736x/ba/fd/92/bafd92cf5c8fbd802cf59c51286fc13f.jpg',
      caption: 'Caption one',
      source: 'FACEBOOK'
   }, {
      media: 'https://i.pinimg.com/1200x/ec/0a/fd/ec0afd363645fa73d9d17df1136fddd4.jpg',
      caption: 'Caption two',
      source: 'THREADS'
   }, {
      media: 'https://i.pinimg.com/736x/53/dd/f9/53ddf9fcf881b704548f2b6c676b07a6.jpg',
      caption: 'Caption three',
      source: 'INSTAGRAM'
   }].map(v => ({
      username: 'Neoxr Creative',
      avatar: 'https://avatars.githubusercontent.com/u/52621597?v=4',
      verified: true,
      caption: v.caption,
      url: 'https://api.neoxr.eu',
      thumbnail: v.media,
      source: v.source,
      post_type: 'PHOTO'
   }))
}, {
   text: '\n\n---\n\nA single product (no slide):\n\n'
}, {
   products: {
      title: 'Script Selfbot (WhatsApp Bot) [Free Update]',
      image: 'https://imgkub.com/images/2026/01/14/image586735a7ca2a894a.jpg',
      sale_price: 'Rp. 65.000',
      brand: 'Neoxr Creative',
      url: `https://wa.me/${Config.owner}`
   }
}, {
   text: '\n\n---\n\nProducts as a slide:'
}, {
   products: [{
      title: 'Script Premium (WhatsApp Bot) V5.1-Optima',
      image: 'https://imgkub.com/images/2025/12/07/image6e1f9b94ba9ced64.jpg',
      sale_price: 'Rp. 150.000'
   }, {
      title: 'Script E-Commerce (NeoCommerce)',
      image: 'https://imgkub.com/images/2025/12/07/image42981ad8a7441ff7.jpg',
      price: 'Rp. 175.000',
      sale_price: 'Rp. 150.000'
   }, {
      title: 'Script Selfbot (WhatsApp Bot)',
      image: 'https://imgkub.com/images/2026/01/14/image3b73a38fbc0a64c6.jpg',
      sale_price: 'Rp. 55.000'
   }].map(v => ({
      ...v,
      brand: 'Neoxr Creative',
      url: `https://wa.me/${Config.owner}`
   }))
}], m, {
   title: global.header
})

Running the Full Bot Base (neoxr-bot)

If you prefer a ready-made bot (downloader, group management and utilities) instead of starting from scratch, use the neoxr-bot 5.0-ZAPO branch, which is built on this library.

git clone -b 5.0-ZAPO https://github.com/neoxr/neoxr-bot
cd neoxr-bot
npm install

Fill in your bot number and settings in the configuration files, then start it:

# direct
npm start

# or with pm2
pm2 start pm2.config.cjs && pm2 logs neoxr

Docker:

docker build -t bot .
docker run -d --name neoxr bot && docker logs -f neoxr

# stop
docker stop neoxr

Some features (such as downloaders) need an API key from api.neoxr.my.id. Check the repository's own README for the exact configuration keys of this branch.


Troubleshooting

| Problem | What to try | | --- | --- | | Pairing code never appears | Make sure pairing.state is true and pairing.number has the country code with digits only. | | Asked to pair on every start | The session is not persisted. Check that create_session points to a writable location. | | Stickers, voice notes or video fail | Install FFmpeg and confirm ffmpeg -version works in the same shell. | | Cannot use import statement outside a module | Add "type": "module" to package.json or rename the file to .mjs. | | Bot is logged out or blocked | Avoid spamming and unofficial interactive messages; consider the stealth option. | | Need more detail | Set debug: true to see what the library is doing. |