@kyyinfinite/forge
v1.0.0
Published
Forge — A modern WhatsApp Framework built on Baileys
Downloads
16
Readme
@kyyinfinite/forge
Forge — A modern WhatsApp framework built on Baileys.
Forge wraps the Baileys engine with a clean, chainable API for building interactive WhatsApp bots. It does not replace Baileys. It does not reimplement any protocol. It gives you a better interface over the same engine.
Install
npm install @kyyinfinite/forgeRequires Node.js >= 20 and a working @whiskeysockets/baileys installation.
Features
- Clean chainable builder API for buttons, carousels, and AI rich responses
- Full WhatsApp native flow protocol support (interactive messages, selections, CTAs)
- AI Rich Response with markdown, code highlighting, tables, images, video, reels, and more
- Carousel messages with per-card media headers
- ButtonV2 (legacy buttons message format)
- Media toolkit: resize, fetch, resolve, upload, mp4 duration/preview
- Manual TypeScript declarations for every public API
- Zero lock-in — engine methods remain available alongside every alias
- ES Module, tree-shakeable, Node.js 20+
Quick Start
- If you use Baileys Costume, use an alias when installing the dependencies, for example like the code below
{
"name": "my-project",
"version": "1.0.0",
"description": "",
"type": "module",
"main": "index.js",
"scripts": {
"start": "node index.js",
"dev": "node --watch index.js"
},
"dependencies": {
"@whiskeysockets/baileys": "npm:@kyyinfinite/baileys"
}
"author": "",
"license": "MIT"
}
import makeWASocket from '@whiskeysockets/baileys';
import { Bot } from '@kyyinfinite/forge';
const sock = makeWASocket({ /* your config */ });
const bot = new Bot(sock);
sock.ev.on('messages.upsert', async ({ messages }) => {
const msg = messages[0];
if (!msg.message) return;
const jid = msg.key.remoteJid;
await bot.text(jid, 'Hello from Forge!');
});Basic Usage
Bot is your entry point. Pass it an active Baileys socket once and use it everywhere.
const bot = new Bot(sock);
// Plain messages
await bot.text(jid, 'Hello');
await bot.image(jid, 'https://example.com/photo.jpg', { caption: 'A photo' });
await bot.video(jid, './clip.mp4', { caption: 'Watch this' });
await bot.audio(jid, './voice.ogg', { ptt: true });
await bot.document(jid, './report.pdf', { fileName: 'report.pdf' });
await bot.sticker(jid, './sticker.webp');
// Utility
await bot.reply(jid, 'Got it!', quotedMessage);
await bot.react(jid, quotedMessage.key, '👍');
await bot.delete(jid, quotedMessage.key);
await bot.edit(jid, sentMessage.key, 'Updated text');
await bot.forward(jid, quotedMessage); // shows "Forwarded"
await bot.copy(jid, quotedMessage); // no "Forwarded" label
// Location and contact
await bot.location(jid, 37.7749, -122.4194, { name: 'San Francisco' });
await bot.contact(jid, vcardString, { name: 'John Doe' });
// Any message can be sent as a reply using the quoted option
await bot.image(jid, './photo.jpg', { caption: 'Here you go', quoted: msg });Button
Interactive messages with native flow. Chain methods, call .send().
await bot.button()
.title('Choose a plan')
.body('Select the plan that works for you.')
.footer('Powered by Forge')
.image('https://example.com/banner.jpg')
.reply('Starter', 'plan_starter')
.reply('Pro', 'plan_pro')
.url('View pricing', 'https://example.com/pricing', 'FULL')
.send(jid);Reply buttons
await bot.button()
.body('What would you like to do?')
.reply('Track order', 'track')
.reply('Contact support', 'support')
.reply('Cancel order', 'cancel')
.send(jid);CTA buttons
await bot.button()
.body('Ready to proceed?')
.url('Open website', 'https://example.com', 'FULL')
.copy('Copy code', 'PROMO2025')
.call('Call us', '+15550001234')
.send(jid);Selection list
await bot.button()
.body('Choose a category')
.footer('Scroll to see all options')
.selection('Browse categories')
.section('Electronics')
.row('', 'Phones', 'Latest smartphones', 'cat_phones')
.row('', 'Laptops', 'Work and gaming', 'cat_laptops')
.section('Clothing')
.row('', 'Men', 'Men\'s collection', 'cat_men')
.row('', 'Women', 'Women\'s collection', 'cat_women')
.send(jid);Send as card (for Carousel)
const card = await bot.button()
.title('Product name')
.body('Description here')
.image('https://example.com/product.jpg')
.reply('Buy now', 'buy_1')
.toCard();ButtonV2
Legacy button format using buttonsMessage.
await bot.buttonV2()
.body('Pick one')
.footer('Only one choice allowed')
.thumbnail('https://example.com/thumb.jpg')
.button('Option A', 'opt_a')
.button('Option B', 'opt_b')
.button('Option C', 'opt_c')
.send(jid);Carousel
A horizontally scrollable set of cards. Each card is built with bot.button().toCard().
const card1 = await bot.button()
.title('Nike Air Max')
.body('Classic streetwear comfort')
.footer('$129')
.image('https://example.com/shoe1.jpg')
.reply('Add to cart', 'cart_1')
.url('View details', 'https://example.com/shoe1', 'FULL')
.toCard();
const card2 = await bot.button()
.title('Adidas Ultraboost')
.body('Performance running shoe')
.footer('$189')
.image('https://example.com/shoe2.jpg')
.reply('Add to cart', 'cart_2')
.url('View details', 'https://example.com/shoe2', 'FULL')
.toCard();
await bot.carousel()
.body('Our top picks for you')
.card(card1)
.card(card2)
.send(jid);You can also pass an array of cards at once:
await bot.carousel()
.card([card1, card2, card3])
.send(jid);AI Rich
The most powerful builder. Composes rich AI-style responses with mixed content primitives. All primitives are chainable. Lazy promises are resolved automatically at send time.
await bot.ai()
.title('Forge AI')
.text('Here is a summary of your request.')
.code('javascript', `function greet(name) {\n return 'Hello, ' + name;\n}`)
.suggest(['Tell me more', 'Start over', 'Help'])
.send(jid);Text with inline entities
await bot.ai()
.text('Visit [OpenAI](https://openai.com) for more details.')
.text('This result cites [](https://wikipedia.org/wiki/AI).')
.send(jid);Code block
await bot.ai()
.text('Here is a sorting function:')
.code('python', `def bubble_sort(arr):\n n = len(arr)\n for i in range(n):\n for j in range(0, n-i-1):\n if arr[j] > arr[j+1]:\n arr[j], arr[j+1] = arr[j+1], arr[j]\n return arr`)
.send(jid);Table
await bot.ai()
.text('Comparison table:')
.table([
['Feature', 'Starter', 'Pro', 'Enterprise'],
['Messages', '1,000', '10,000', 'Unlimited'],
['AI Rich', '✗', '✓', '✓'],
['Carousel', '✗', '✓', '✓'],
['Support', 'Email', 'Priority', 'Dedicated'],
])
.send(jid);Images
await bot.ai()
.text('Generated artwork:')
.image('https://example.com/art.jpg')
.send(jid);
// Multiple images
await bot.ai()
.image(['https://example.com/1.jpg', 'https://example.com/2.jpg'])
.send(jid);Video
// Simple URL
await bot.ai()
.video('https://example.com/clip.mp4')
.send(jid);
// Auto-extract duration and thumbnail from the buffer
await bot.ai()
.video('https://example.com/clip.mp4', { autoFill: true })
.send(jid);
// Manual metadata
await bot.ai()
.video({ url: 'https://example.com/clip.mp4', duration: 30, mime_type: 'video/mp4' })
.send(jid);Search sources
await bot.ai()
.text('Based on these sources:')
.source([
['https://google.com/favicon.ico', 'https://google.com/article', 'Google — AI Overview'],
['https://wiki.org/favicon.ico', 'https://wikipedia.org/wiki/AI', 'Wikipedia — Artificial intelligence'],
])
.send(jid);Suggestions
// Single
await bot.ai()
.suggest('Tell me more')
.send(jid);
// Multiple — horizontal scroll
await bot.ai()
.suggest(['Summarize', 'Translate', 'Explain simply', 'Give examples'], { scroll: true })
.send(jid);
// Multiple — action row (no scroll)
await bot.ai()
.suggest(['Yes', 'No', 'Maybe'], { scroll: false })
.send(jid);Products
await bot.ai()
.text('Here are some recommendations:')
.product([
{ image: 'https://example.com/p1.jpg', title: 'Widget A', price: '$19.99' },
{ image: 'https://example.com/p2.jpg', title: 'Widget B', price: '$34.99' },
])
.send(jid);Build options
// Forwarded message appearance
await bot.ai()
.text('Forwarded answer')
.send(jid, { forwarded: true });
// With notification transparency metadata
await bot.ai()
.text('System notification')
.send(jid, { notification: true });
// Quote another message
await bot.ai()
.text('In reply to your question:')
.send(jid, { quoted: originalMessage });Toolkit
Static utilities used internally. Also available for your own media pipelines.
import { Toolkit } from '@kyyinfinite/forge';
// Resize an image buffer
const resized = await Toolkit.resize(buffer, 300, 300);
// Download any URL as a Buffer
const buf = await Toolkit.fetchBuffer('https://example.com/photo.jpg');
// Download silently (returns empty Buffer on failure instead of throwing)
const buf = await Toolkit.fetchBuffer(url, {}, { silent: true });
// Upload a Buffer to the WhatsApp CDN, get back a WA CDN URL
const cdnUrl = await Toolkit.toUrl(sock, imageBuffer, 'image');
// Resolve any media input to a CDN URL
const url = await Toolkit.resolveMedia(sock, 'https://example.com/img.jpg', 'image', {
result: 'url',
});
// Resolve and resize
const url = await Toolkit.resolveMedia(sock, buffer, 'image', {
resize: true, width: 640, height: 480, result: 'url',
});
// Get MP4 duration in seconds
const duration = Toolkit.getMp4Duration(mp4Buffer);
// Extract a frame from an MP4
const frame = await Toolkit.getMp4Preview(mp4Buffer, { result: 'buffer' });
// Parse inline entities from text
const { text, inline_entities } = Toolkit.extractIE(
'Check out [this link](https://example.com)'
);Migration
From raw Baileys
// Before
await sock.sendMessage(jid, {
viewOnceMessage: {
message: {
buttonsMessage: {
contentText: 'Pick one',
buttons: [
{ buttonId: 'opt_a', buttonText: { displayText: 'Option A' }, type: 1 },
],
},
},
},
});
// After
await bot.buttonV2()
.body('Pick one')
.button('Option A', 'opt_a')
.send(jid);From the engine directly
Every engine method is still available. You only need to update the method names if you want the cleaner API.
// Engine API (still works)
const btn = new Button(sock);
btn.setTitle('Hello').addReply('Yes', 'yes');
// Wrapper API (same result)
const btn = bot.button();
btn.title('Hello').reply('Yes', 'yes');FAQ
Does this change any WhatsApp protocol behavior?
No. Every builder delegates 100% of execution to the engine. The generated WAProto payloads are identical.
Can I mix engine methods and wrapper aliases in the same chain?
Yes. Because the wrapper extends the engine class, both sets of methods exist on the same instance.
await bot.button()
.title('Mixed') // wrapper alias
.addReply('Ok', 'ok') // engine method
.send(jid);Does bot.ai() use sendMessage?
No. AIRich.send() calls relayMessage() directly with the raw botForwardedMessage content. This is intentional and required by the protocol.
How do I access the raw message output without sending?
Use .build() instead of .send().
const msg = await bot.button().title('Hello').reply('Ok', 'ok').build(jid);
// msg is a WAMessage — inspect or relay manuallyCan I use Toolkit independently from Bot?
Yes. Import it directly:
import { Toolkit } from '@kyyinfinite/forge';Best Practice
Always await .send(). It resolves all lazy media promises and performs uploads before sending.
// Correct
await bot.ai().video('https://example.com/clip.mp4', { autoFill: true }).send(jid);
// Wrong — fire-and-forget drops errors and may cause partial sends
bot.ai().video('https://example.com/clip.mp4').send(jid);Build carousel cards before constructing the carousel, since toCard() performs media uploads.
// Correct — upload cards first, then build the carousel
const cards = await Promise.all([
bot.button().image(url1).reply('Buy', 'b1').toCard(),
bot.button().image(url2).reply('Buy', 'b2').toCard(),
]);
await bot.carousel().card(cards).send(jid);Reuse the Bot instance. It is stateless — you can share one instance across your entire application.
// Create once
export const bot = new Bot(sock);
// Use anywhere
import { bot } from './bot.js';
await bot.text(jid, 'Hello');