npm.io
1.0.3 • Published 20h ago

schwamm-builder

Licence
MIT
Version
1.0.3
Deps
0
Size
117 kB
Vulns
0
Weekly
0

schwamm-builder

Fluent message builders for Baileys (@whiskeysockets/baileys).

Chainable helpers for interactive buttons, lists, carousels, payments, products, media, and rich AI-style messages.

npm i @whiskeysockets/baileys schwamm-builder
Node.js 18+
Peer dependency @whiskeysockets/baileys (install in your bot; this package does not ship Baileys)
License MIT

Your bot owns the WhatsApp socket. schwamm-builder only builds payloads and sends them through that socket.


Quick start

const Builder = require('schwamm-builder');

// sock = Baileys socket
// jid  = chat id string (or object with id / chat / remoteJid)
await Builder.other()
  .xText('Hello')
  .send(sock, jid);
Entry Purpose
Builder.interactive() Buttons, lists, nested lists, offers, carousel
Builder.payment() Payment request, order, product
Builder.other() Text, image, video, sticker
Builder.rich() Rich / GenAI-style sections (tables, posts, reels, …)
.send(sock, jid)

Interactive

await Builder.interactive()
  .xText('Choose an option')
  .xFooter('Schwamm Bot')
  .xButton('Option 1', 'opt1')
  .xButton('Option 2', 'opt2', 'REVIEW')
  .xUrlButton('Website', 'https://example.com', { icon: 'REVIEW' })
  .xCallButton('Call', '491234567890')
  .xCopyButton('Copy code', 'SAVE20')
  .send(sock, jid);
Button types
Method Type
.xButton(text, id, icon?) Quick reply
.xUrlButton(text, url, { icon, useWebview }) Open URL
.xCallButton(text, phone, icon?) Call
.xCopyButton(text, code, icon?) Copy
.xReminder(text, id?, icon?) Reminder
.xLocation() Request location
.xList(title, sections, icon?) Single-select list
.xListRows(title, rows, sectionTitle?, icon?) List helper
List
.xList('Options', [
  {
    title: 'Menu',
    rows: [
      { title: 'Profile', id: 'profile', description: 'Your profile' },
      { title: 'Help', id: 'help', description: 'Support' }
    ]
  }
], 'REVIEW')
Nested list (bottom sheet)

Call .xNestedList(buttonTitle, listTitle) first, then one or more .xList(...).

await Builder.interactive()
  .xText('Anti system')
  .xFooter('Bot')
  .xNestedList('Options', 'Anti')
  .xList('Antilink', [{
    title: 'Antilink',
    rows: [
      { title: 'On', id: 'al_on' },
      { title: 'Off', id: 'al_off' }
    ]
  }], 'REVIEW')
  .xList('Antispam', [{
    title: 'Antispam',
    rows: [{ title: 'On', id: 'as_on' }]
  }], 'CALL')
  .xButton('Close', 'close')
  .send(sock, jid);
Offer
.xOffer('Limited offer', {
  url: 'https://example.com/pay',
  code: 'SAVE20',
  expiration: Math.floor(Date.now() / 1000) + 3600
})
Image header
.xImage('https://example.com/banner.jpg')
.xTitle('Title')

Each card needs an image. After .xCard(...), chain buttons or lists onto that card:

const img = 'https://example.com/card.jpg';

await Builder.interactive()
  .xText('Shop')
  .xFooter('Bot')
  .xCard({ title: 'Item 1', text: 'Description', image: img })
  .xButton('Buy', 'buy_1', 'REVIEW')
  .xUrlButton('Shop', 'https://example.com')
  .xCard({ title: 'Item 2', text: 'More', image: img })
  .xList('Options', [{
    title: 'Settings',
    rows: [
      { title: 'Enable', id: 'on' },
      { title: 'Disable', id: 'off' }
    ]
  }], 'REVIEW')
  .send(sock, jid);

You can also pass buttons inside the card object, or use .xCards([...]).

Nested list on a card:

.xCard({ title: 'Anti', text: 'Settings', image: img })
.xNestedList('Options', 'Anti')
.xList('Antilink', [/* ... */])
.xList('Antispam', [/* ... */])

Payment

Note: Some payment UIs depend on region, account type, and WhatsApp client. Fake catalog / business IDs often show as “not supported”.

Request payment
await Builder.payment()
  .xRequest({
    amount: 25,
    currency: 'EUR',
    text: 'Please pay 25 EUR',
    from: '0@s.whatsapp.net'
  })
  .send(sock, jid);
Order
await Builder.payment()
  .xOrder({
    text: 'Thanks for your order',
    amount: 67,
    currency: 'EUR',
    itemCount: 1,
    thumbnail: 'https://example.com/thumb.png' // URL or Buffer
  })
  .send(sock, jid);

URLs are loaded to a buffer automatically when needed.

Product
await Builder.payment()
  .xProduct({
    title: 'Premium',
    description: 'One month access',
    amount: 9.99,
    currency: 'EUR',
    image: 'https://example.com/product.png',
    productId: 'sku-1',
    retailerId: '',
    businessOwnerJid: '0@s.whatsapp.net'
  })
  .send(sock, jid);

image is required (URL or Buffer).


Other (text / media)

await Builder.other().xText('Hello').send(sock, jid);

await Builder.other()
  .xImage('https://example.com/pic.jpg', 'Caption')
  .send(sock, jid);

await Builder.other()
  .xVideo('https://example.com/vid.mp4', 'Caption')
  .send(sock, jid);

Rich

Rich messages use unified GenAI-style sections. Rendering is client-dependent.

Text & media
await Builder.rich()
  .xHeader('Title')
  .xBody('Body text')
  .xBanner('https://example.com/banner.png')
  .xPicture('https://example.com/pic.png', { inline: true, width: 200, height: 200 })
  .xPictures(['https://a.png', 'https://b.png'])
  .xTip('Small tip text')
  .xLink('Open site', 'https://example.com')
  .xInfo('Disclaimer under the message')
  .xFooter('Open', 'https://example.com')
  .send(sock, jid);
Tables & code
.xTableV1('Users', [
  ['Name', 'Role'],
  ['Alice', 'Admin'],
  ['Bob', 'User']
])

.xTableV2('Users', [
  ['Name', 'Role'],
  ['Alice', 'Admin']
])

.xCodeV1('console.log("hi")', 'javascript')
.xCodeV2('console.log("hi")', 'javascript')
.xActionRow('Menu', [
  { label: 'Start', toast: 'OK' },
  { label: 'Help', toast: 'Help' }
])

.xCarousel([
  { title: 'Card 1', ctas: [{ label: 'Go', toast: '…' }] },
  { title: 'Card 2', ctas: [{ label: 'More' }] }
])
Posts, reels, products
.xPosts([{
  username: 'user',
  title: 'Post',
  thumbnail_url: 'https://…',
  post_url: 'https://…',
  is_verified: true,
  likes_count: 10
}])

.xReels([{
  username: 'creator',
  videoUrl: 'https://…',
  thumbnailUrl: 'https://…',
  is_verified: true
}])

.xProduct({
  title: 'Item',
  brand: 'Brand',
  price: '9.99 EUR',
  product_url: 'https://…',
  image_url: 'https://…'
})

.xProducts([/* same shape */])
Sources, citations, suggestions
.xSources([
  { name: 'Wikipedia', url: 'https://…', subtitle: 'Web', favicon: 'https://…' }
])
// or tuples: [favicon, url, name, subtitle?]

.xCitations([
  { title: 'Source 1', url: 'https://…', snippet: '…' }
])

.xSuggestions(['Next question', 'Explain more'])

Global flags (all builders)

Available on interactive, payment, other, and rich:

Method Effect
.xAI(true) AI / bot label (private chats only)
.xSecureMeta(true) Secure meta / business attributes
.xForwarded(n) Forwarded style with score n (e.g. 1 or 7)
.xWAStatus(text, type?, opts?) Looks like a reply to a WhatsApp status

xForwarded and xWAStatus are independent:

  • only .xForwarded(1) → forward only
  • only .xWAStatus(...) → status-quote only
  • both → merged
Status quote types
.xWAStatus('Reply text')                       // text
.xWAStatus('Reply', 'image')
.xWAStatus('Reply', 'video')
.xWAStatus('Reply', 'sticker')
.xWAStatus('Reply', 'stickerpack', 'Pack Name')
.xWAStatus('Reply', 'product', { title: 'Abo', price: 9.99, currency: 'EUR' })
.xWAStatus('Reply', 'order', { message: 'Under 1 item', itemCount: 1 })
Examples
await Builder.other().xText('Hi').xAI(true).send(sock, jid);

await Builder.interactive()
  .xText('Menu')
  .xButton('OK', 'ok')
  .xForwarded(1)
  .send(sock, jid);

await Builder.payment()
  .xRequest({ amount: 5, currency: 'EUR', text: 'Pay' })
  .xSecureMeta(true)
  .send(sock, jid);

await Builder.other()
  .xWAStatus('Hello', 'image')
  .xForwarded(7)
  .send(sock, jid);

await Builder.rich()
  .xBody('Answer')
  .xSuggestions(['More'])
  .xAI(true)
  .send(sock, jid);

Notes

  • .xAI(true) is intended for private chats (@s.whatsapp.net / @lid). Groups may throw.
  • AI label, secure meta, status icons, and rich sections depend on the WhatsApp client and Baileys version.
  • Do not mix interactive + payment + rich in a single message. Use one builder per .send(). Global flags may still be chained on any builder.

Bot command example

const Builder = require('schwamm-builder');

module.exports = {
  name: 'menu',
  async run(x, s) {
    try {
      await Builder.interactive()
        .xText('Choose')
        .xButton('A', 'a')
        .xButton('B', 'b')
        .send(x, s.chat);
    } catch (err) {
      await s.m.reply(String(err?.message || err));
    }
  }
};

x / sock is whatever your bot passes as the Baileys socket. The builder does not care about the variable name.


Handling button / list clicks

Clicks are not handled inside the builder. In your messages.upsert handler, read the selected id from interactive / list / native-flow responses and route it to your command logic (e.g. a selected handler).

Typical sources:

  • interactiveResponseMessage
  • listResponseMessage
  • buttonsResponseMessage
  • nativeFlowResponseMessage (paramsJsonid)

License

MIT

Copyright (c) 2026 Schwamm

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

Keywords