Skip to content

XdKing2/malvin-btns

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

1 Commit
Β 
Β 

Repository files navigation

malvin-btns

A powerful, production-ready interactive button plugin for the Baileys WhatsApp library.

npm version License: ISC Node.js GitHub

Send interactive buttons, quick replies, URL buttons, copy buttons, call buttons, and single-select menus β€” with optional image headers β€” without touching Baileys core files.


The Problem

By default, WhiskeySockets/Baileys cannot send interactive buttons. The root cause is that Baileys lacks the required binary node wrappers (biz, interactive, native_flow) that WhatsApp expects for interactive messages.

The Solution

malvin-btns fills the gap by:

  1. Detecting button messages using WhatsApp's expected format
  2. Converting simple button definitions to the correct protobuf structure
  3. Injecting missing binary nodes (biz, interactive, native_flow, bot) via additionalNodes
  4. Automatically handling private vs. group chat requirements
  5. Processing images - automatically downloads, encrypts, and uploads images to WhatsApp's CDN

No core file edits. No monkey-patching. Just drop it in and go.


Features

Feature Status
No modifications to Baileys core βœ…
Automatic binary node injection βœ…
Private chat support (bot node with biz_bot: '1') βœ…
Group chat support (biz node only) βœ…
Quick reply buttons βœ…
URL, Copy, Call CTA buttons βœ…
Single-select menus βœ…
Send location button βœ…
Image header support (auto-upload & encryption) βœ…
Input validation with detailed errors βœ…
Works with multiple Baileys forks βœ…
Regular messages pass through unchanged βœ…

Installation

npm install malvin-btns

You also need a Baileys package:

# pick one
npm install mrxd-baileys
npm install @whiskeysockets/baileys

Quick Start

const { makeWASocket } = require('mrxd-baileys');
const { sendButtons } = require('malvin-btns');

const sock = makeWASocket({ /* your config */ });

await sendButtons(sock, jid, {
  title: 'Hello there!',
  text: 'Pick an option below',
  footer: 'Powered by malvin-btns',
  buttons: [
    { id: 'help',  text: 'πŸ†˜ Help'  },
    { id: 'about', text: 'ℹ️ About' },
    {
      name: 'cta_url',
      buttonParamsJson: JSON.stringify({
        display_text: '🌐 Visit Website',
        url: 'https://host.malvintech.sbs'
      })
    }
  ]
});

Image Header Support

Add an image above your buttons by passing an image object. The image is automatically downloaded, encrypted, and uploaded to WhatsApp's CDN β€” you just provide the URL.

await sendButtons(sock, jid, {
  title: 'Product Showcase',
  text: 'Check out our latest collection',
  image: { url: 'https://example.com/product.jpg' },
  footer: 'Limited time offer',
  buttons: [
    { id: 'shop', text: 'πŸ›’ Shop Now' },
    { id: 'details', text: 'πŸ“‹ Details' }
  ]
});

You can also use a local file path or buffer:

// Local file
image: { path: '/path/to/image.jpg' }

// Buffer
image: { buffer: bufferData }

Handling Button Replies

When a user taps a button, WhatsApp sends back the button's id as a regular message:

sock.ev.on('messages.upsert', async ({ messages }) => {
  const msg = messages[0];
  const text =
    msg.message?.conversation ||
    msg.message?.extendedTextMessage?.text || '';

  if (text === 'help') {
    await sock.sendMessage(msg.key.remoteJid, { text: 'How can I help you?' });
  }

  if (text === 'about') {
    await sock.sendMessage(msg.key.remoteJid, { text: 'Made with malvin-btns πŸš€' });
  }
});

Button Types

Name Purpose Required buttonParamsJson keys
quick_reply Simple reply that sends its id back { display_text, id }
single_select In-button picker list { title, sections: [{ title?, rows: [{ id, title, description?, header? }] }] }
cta_url Open a URL { display_text, url, merchant_url? }
cta_copy Copy text to clipboard { display_text, copy_code }
cta_call Tap to dial { display_text, phone_number }
cta_catalog Open business catalog { display_text? }
send_location Request user location { display_text? }

Advanced Usage

For full control over all button types in one message, use sendInteractiveMessage:

const { sendInteractiveMessage } = require('malvin-btns');

await sendInteractiveMessage(sock, jid, {
  text: 'Advanced button demo',
  footer: 'malvin-btns',
  image: { url: 'https://example.com/header.jpg' },
  interactiveButtons: [
    {
      name: 'quick_reply',
      buttonParamsJson: JSON.stringify({ display_text: 'Reply A', id: 'reply_a' })
    },
    {
      name: 'single_select',
      buttonParamsJson: JSON.stringify({
        title: 'Pick One',
        sections: [{
          title: 'Options',
          rows: [
            { id: 'opt_hello', title: 'Hello', description: 'Say hi' },
            { id: 'opt_bye',   title: 'Bye',   description: 'Say bye' }
          ]
        }]
      })
    }
  ]
});

URL + Copy + Call in One Message

await sendInteractiveMessage(sock, jid, {
  text: 'Contact actions',
  interactiveButtons: [
    {
      name: 'cta_url',
      buttonParamsJson: JSON.stringify({ display_text: '🌐 Docs', url: 'https://host.malvintech.sbs/doc' })
    },
    {
      name: 'cta_copy',
      buttonParamsJson: JSON.stringify({ display_text: 'πŸ“‹ Copy Code', copy_code: 'MALVIN-2025' })
    },
    {
      name: 'cta_call',
      buttonParamsJson: JSON.stringify({ display_text: 'πŸ“ž Call Support', phone_number: '+1234567890' })
    }
  ]
});

Single-Select Menu

await sendInteractiveMessage(sock, jid, {
  text: 'Choose a plan',
  interactiveButtons: [
    {
      name: 'single_select',
      buttonParamsJson: JSON.stringify({
        title: 'Plans',
        sections: [{
          title: 'Available Plans',
          rows: [
            { id: 'plan_free', title: 'Free',    description: '4,000 requests/day' },
            { id: 'plan_pro',  title: 'Premium', description: 'Unlimited β€” one-time payment' }
          ]
        }]
      })
    }
  ]
});

Error Handling

const { sendButtons, InteractiveValidationError } = require('malvin-btns');

try {
  await sendButtons(sock, jid, { text: 'Hi', buttons: [] });
} catch (err) {
  if (err instanceof InteractiveValidationError) {
    console.error(err.formatDetailed());
  }
}

How It Works

What You Write

{
  text: 'Hello',
  footer: 'Footer',
  image: { url: 'https://example.com/img.jpg' },
  interactiveButtons: [{ name, buttonParamsJson }, ...]
}

What Gets Sent to WhatsApp

{
  interactiveMessage: {
    header: {
      hasMediaAttachment: true,
      imageMessage: { url: 'https://mmg.whatsapp.net/...', ... }
    },
    nativeFlowMessage: { buttons: [...] },
    body:   { text: 'Hello' },
    footer: { text: 'Footer' }
  }
}

Binary Nodes Injected

Chat Type Nodes Added
Private biz + interactive/native_flow + bot (biz_bot: '1')
Group biz + interactive/native_flow only

API Reference

sendButtons(sock, jid, data, options?)

Simplified sending for quick replies and basic CTAs.

Parameter Type Description
sock WASocket Baileys socket instance
jid string Recipient JID
data.text string Message body (required)
data.title string Header title (optional)
data.footer string Footer text (optional)
data.image object Image header - { url, path, or buffer } (optional)
data.buttons array Button objects

sendInteractiveMessage(sock, jid, content, options?)

Full control over all button types and structures.

Parameter Type Description
sock WASocket Baileys socket instance
jid string Recipient JID
content.text string Message body (required)
content.footer string Footer text (optional)
content.image object Image header - { url, path, or buffer } (optional)
content.interactiveButtons array Full button definitions

Compatibility

Baileys Package Compatible
mrxd-baileys βœ… Yes
@whiskeysockets/baileys βœ… Yes (7.0.0-rc.2+)
baileys βœ… Yes
@adiwajshing/baileys βœ… Yes

Requirements

  • Node.js v20 or higher
  • Baileys 7.0.0-rc.2+

Author

mrxdking β€” @XdKing2


License

ISC Β© Malvin Tech

About

nothing much

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages